शेल स्क्रिप्ट में एक चर में एक कमांड कैसे स्टोर करें?


113

मैं एक बाद की अवधि में एक चर में उपयोग करने के लिए एक कमांड स्टोर करना चाहूंगा (कमांड का आउटपुट नहीं, लेकिन कमांड खुद)

मेरे पास एक सरल स्क्रिप्ट इस प्रकार है:

command="ls";
echo "Command: $command"; #Output is: Command: ls

b=`$command`;
echo $b; #Output is: public_html REV test... (command worked successfully)

हालाँकि, जब मैं कुछ अधिक जटिल कोशिश करता हूं, तो यह विफल हो जाता है। उदाहरण के लिए, अगर मैं बनाता हूं

command="ls | grep -c '^'";

आउटपुट है:

Command: ls | grep -c '^'
ls: cannot access |: No such file or directory
ls: cannot access grep: No such file or directory
ls: cannot access '^': No such file or directory

किसी भी विचार मैं बाद में उपयोग के लिए एक चर में इस तरह के कमांड (पाइप / एकाधिक कमांड के साथ) कैसे स्टोर कर सकता है?


10
एक फ़ंक्शन का उपयोग करें!
ग्नौरफ_गनीउरफ

जवाबों:


146

Eval का प्रयोग करें:

x="ls | wc"
eval "$x"
y=$(eval "$x")
echo "$y"

27
$ (...) अब इसके बजाय अनुशंसित है backticks। y = $ (eval $ x) mywiki.wooledge.org/BashFAQ/082
जेम्स

14
evalयदि आप अपने चर की सामग्री पर भरोसा करते हैं तो ही एक स्वीकार्य अभ्यास है । यदि आप चल रहे हैं, कहते हैं, x="ls $name | wc"(या यहां तक ​​कि x="ls '$name' | wc"), तो यह कोड इंजेक्शन या विशेषाधिकार वृद्धि की कमजोरियों के लिए एक तेज़ ट्रैक है यदि उस चर को कम विशेषाधिकार वाले व्यक्ति द्वारा सेट किया जा सकता है। ( /tmpउदाहरण के लिए, सभी उपनिर्देशिकाओं पर Iterating; आप बेहतर सिस्टम पर हर एक उपयोगकर्ता पर भरोसा कर सकते हैं जिसे एक नाम नहीं दिया जाता है $'/tmp/evil-$(rm -rf $HOME)\'$(rm -rf $HOME)\'/')।
चार्ल्स डफी

9
evalएक विशाल बग चुंबक है जिसे अप्रत्याशित पार्सिंग व्यवहार के जोखिम के बारे में चेतावनी के बिना कभी भी अनुशंसित नहीं किया जाना चाहिए (यहां तक ​​कि दुर्भावनापूर्ण स्ट्रिंग्स के बिना, जैसा कि @ चार्ल्सडफी के उदाहरण में है)। उदाहरण के लिए, कोशिश करें x='echo $(( 6 * 7 ))'और फिर eval $x। आप उम्मीद कर सकते हैं कि "42" प्रिंट करें, लेकिन यह शायद नहीं होगा। क्या आप बता सकते हैं कि यह काम क्यों नहीं करता है? क्या आप बता सकते हैं कि मैंने "शायद" क्यों कहा? यदि उन सवालों के जवाब आपके लिए स्पष्ट नहीं हैं, तो आपको कभी नहीं छूना चाहिए eval
गॉर्डन डेविसन

1
@Student, set -xकमांड चलाने के लिए लॉग ऑन करने के लिए पहले से दौड़ने की कोशिश करें , जिससे यह देखने में आसानी होगी कि क्या हो रहा है।
चार्ल्स डफी

1
@Student मैं सामान्य गलतियों (और बुरी आदतें जो आपको नहीं उठानी चाहिए) को इंगित करने के लिए shellcheck.net की सिफारिश करेगा
गॉर्डन डेविसन

41

करो नहीं का उपयोग eval! इसमें मनमाना कोड निष्पादन शुरू करने का एक बड़ा जोखिम है।

BashFAQ-50 - मैं एक चर में एक कमांड डालने की कोशिश कर रहा हूं, लेकिन जटिल मामले हमेशा विफल होते हैं।

इसे एक सरणी में रखें और वर्ड स्प्लिटिंग के कारण शब्दों को विभाजित "${arr[@]}" करने के लिए डबल-कोट्स के साथ सभी शब्दों का विस्तार करें ।IFS

cmdArgs=()
cmdArgs=('date' '+%H:%M:%S')

और सरणी की सामग्री को अंदर देखें। declare -pआपको अलग-अलग सूचकांकों में प्रत्येक कमांड पैरामीटर के साथ सरणी की सामग्री को देखने की अनुमति देता है। यदि इस तरह के एक तर्क में रिक्त स्थान होते हैं, तो सरणी में जोड़ते हुए अंदर उद्धृत करना वर्ड-स्प्लिटिंग के कारण विभाजित होने से रोक देगा।

declare -p cmdArgs
declare -a cmdArgs='([0]="date" [1]="+%H:%M:%S")'

और आदेशों को निष्पादित करें

"${cmdArgs[@]}"
23:15:18

(या) पूरी तरह bashसे कमांड चलाने के लिए एक फ़ंक्शन का उपयोग करें ,

cmd() {
   date '+%H:%M:%S'
}

और फ़ंक्शन को बस कॉल करें

cmd

POSIX shमें कोई सरणियाँ नहीं हैं, इसलिए आप जो निकटतम आ सकते हैं, वह स्थितिगत मापदंडों में तत्वों की एक सूची बनाने के लिए है। shमेल प्रोग्राम चलाने के लिए यहां POSIX तरीका है

# POSIX sh
# Usage: sendto subject address [address ...]
sendto() {
    subject=$1
    shift
    first=1
    for addr; do
        if [ "$first" = 1 ]; then set --; first=0; fi
        set -- "$@" --recipient="$addr"
    done
    if [ "$first" = 1 ]; then
        echo "usage: sendto subject address [address ...]"
        return 1
    fi
    MailTool --subject="$subject" "$@"
}

ध्यान दें कि यह दृष्टिकोण केवल सरल आदेशों को संभाल सकता है जिसमें कोई पुनर्निर्देशन नहीं है। यह लूप्स, पाइपलाइनों को / जबकि लूप, यदि स्टेटमेंट, आदि के लिए हैंडल नहीं कर सकता है

एक और सामान्य उपयोग का मामला है जब curlकई हेडर फ़ील्ड और पेलोड के साथ चल रहा है । आप हमेशा नीचे की तरह आर्ग को परिभाषित कर सकते हैं और curlविस्तारित सरणी सामग्री पर आह्वान कर सकते हैं

curlArgs=('-H' "keyheader: value" '-H' "2ndkeyheader: 2ndvalue")
curl "${curlArgs[@]}"

एक और उदाहरण,

payload='{}'
hostURL='http://google.com'
authToken='someToken'
authHeader='Authorization:Bearer "'"$authToken"'"'

अब जब चर परिभाषित किए जाते हैं, तो अपने कमांड आर्ग को संग्रहीत करने के लिए एक सरणी का उपयोग करें

curlCMD=(-X POST "$hostURL" --data "$payload" -H "Content-Type:application/json" -H "$authHeader")

और अब एक उचित उद्धृत विस्तार करते हैं

curl "${curlCMD[@]}"

यह मेरे लिए काम नहीं करता है, मैंने कोशिश की है Command=('echo aaa | grep a')और "${Command[@]}"उम्मीद है कि यह सचमुच कमांड चलाता है echo aaa | grep a। यह नहीं है मुझे आश्चर्य है कि अगर कोई सुरक्षित तरीका है eval, लेकिन ऐसा लगता है कि प्रत्येक समाधान में समान बल evalहै जो खतरनाक हो सकता है। है ना?
छात्र

संक्षेप में, मूल स्ट्रिंग में पाइप '' | '
छात्र

@Student, यदि आपके मूल स्ट्रिंग में एक पाइप है, तो उस स्ट्रिंग को कोड के रूप में निष्पादित करने के लिए bash parser के असुरक्षित भागों से गुजरना होगा। उस स्थिति में एक स्ट्रिंग का उपयोग न करें; इसके बजाय एक फ़ंक्शन का उपयोग करें: Command() { echo aaa | grep a; }- जिसके बाद आप बस चला सकते हैं Command, या result=$(Command), या पसंद कर सकते हैं।
चार्ल्स डफी

1
@ मजबूत, सही; लेकिन यह जानबूझकर विफल होता है , क्योंकि आप जो करने के लिए कह रहे हैं वह स्वाभाविक रूप से असुरक्षित है
चार्ल्स डफी

1
@Student: मैंने कुछ शर्तों के तहत काम नहीं करने के लिए आखिरी में एक नोट जोड़ा है
Inian

25
var=$(echo "asdf")
echo $var
# => asdf

इस पद्धति का उपयोग करते हुए, कमांड का तुरंत मूल्यांकन किया जाता है और यह रिटर्न वैल्यू संग्रहीत होता है।

stored_date=$(date)
echo $stored_date
# => Thu Jan 15 10:57:16 EST 2015
# (wait a few seconds)
echo $stored_date
# => Thu Jan 15 10:57:16 EST 2015

बैकटिक के साथ भी

stored_date=`date`
echo $stored_date
# => Thu Jan 15 11:02:19 EST 2015
# (wait a few seconds)
echo $stored_date
# => Thu Jan 15 11:02:19 EST 2015

$(...)वसीयत में इस्तेमाल का बाद में मूल्यांकन नहीं किया जाएगा

stored_date=$(eval "date")
echo $stored_date
# => Thu Jan 15 11:05:30 EST 2015
# (wait a few seconds)
echo $stored_date
# => Thu Jan 15 11:05:30 EST 2015

इसका उपयोग करते हुए, इसका मूल्यांकन evalकिया जाता है कि इसका उपयोग कब किया जाता है

stored_date="date" # < storing the command itself
echo $(eval "$stored_date")
# => Thu Jan 15 11:07:05 EST 2015
# (wait a few seconds)
echo $(eval "$stored_date")
# => Thu Jan 15 11:07:16 EST 2015
#                     ^^ Time changed

उपरोक्त उदाहरण में, यदि आपको तर्कों के साथ एक कमांड चलाने की आवश्यकता है, तो उन्हें उस स्ट्रिंग में डालें जिसे आप स्टोर कर रहे हैं

stored_date="date -u"
# ...

बैश स्क्रिप्ट के लिए यह शायद ही कभी प्रासंगिक है, लेकिन एक आखिरी नोट। से सावधान रहें eval। Eval केवल आपके द्वारा नियंत्रित किए जाने वाले तार, कभी भी किसी अविश्वसनीय उपयोगकर्ता से आने वाले तार या अविश्वसनीय उपयोगकर्ता इनपुट से निर्मित नहीं होते हैं।

  • कमांड कोट करने के लिए मुझे याद दिलाने के लिए @CharlesDuffy का शुक्रिया!

यह मूल समस्या को हल नहीं करता है जहां कमांड में एक पाइप होता है '|'
छात्र

@ ध्यान दें, केवल eval $stored_dateतब stored_dateहोता है जब पर्याप्त रूप से ठीक हो सकता है date, लेकिन eval "$stored_date"बहुत अधिक विश्वसनीय है। एक उदाहरण के लिए str=$'printf \' * %s\\n\' *'; eval "$str"अंतिम के आसपास उद्धरणों के साथ और उसके बिना चलाएं "$str"। :)
चार्ल्स डफी

@CharlesDuffy धन्यवाद, मैं उद्धृत करने के बारे में भूल गया। मैं शर्त लगाता हूँ कि मेरे लिंटर ने शिकायत की होगी कि मैंने इसे चलाने के लिए परेशान किया था।
नैट

0

बैश के लिए, अपनी कमांड को इस तरह स्टोर करें:

command="ls | grep -c '^'"

अपना कमांड इस तरह से चलाएं:

echo $command | bash

1
यकीन नहीं हो रहा है लेकिन शायद कमांड चलाने के इस तरीके में वही जोखिम हैं जो 'इवैल' के इस्तेमाल का है।
डेरेक हेज़ल

0

मैंने विभिन्न तरीकों की कोशिश की:

printexec() {
  printf -- "\033[1;37m$\033[0m"
  printf -- " %q" "$@"
  printf -- "\n"
  eval -- "$@"
  eval -- "$*"
  "$@"
  "$*"
}

आउटपुट:

$ printexec echo  -e "foo\n" bar
$ echo -e foo\\n bar
foon bar
foon bar
foo
 bar
bash: echo -e foo\n bar: command not found

जैसा कि आप देख सकते हैं, केवल तीसरे एक "$@"ने, सही परिणाम दिया।


0

एक आदेश के साथ सावधानीपूर्वक पंजीकरण करें: X=$(Command)

इस एक को अभी भी बुलाया जाने से पहले ही क्रियान्वित कर दिया जाता है। इसकी जाँच करने और इसकी पुष्टि करने के लिए, आप यह करेंगे:

echo test;
X=$(for ((c=0; c<=5; c++)); do
sleep 2;
done);
echo note the 5 seconds elapsed

-1
#!/bin/bash
#Note: this script works only when u use Bash. So, don't remove the first line.

TUNECOUNT=$(ifconfig |grep -c -o tune0) #Some command with "Grep".
echo $TUNECOUNT                         #This will return 0 
                                    #if you don't have tune0 interface.
                                    #Or count of installed tune0 interfaces.

-8

यह चर में आदेशों को संग्रहीत करने के लिए आवश्यक नहीं है, भले ही आपको इसे बाद में उपयोग करने की आवश्यकता हो। बस इसे सामान्य रूप से निष्पादित करें। यदि आप वैरिएबल में स्टोर करते हैं, तो आपको किसी प्रकार के evalस्टेटमेंट की आवश्यकता होगी या "अपने वेरिएबल को निष्पादित करने के लिए" कुछ अनावश्यक शेल प्रक्रिया को लागू करना होगा।


1
जो कमांड मैं स्टोर करूंगा वह मेरे द्वारा भेजे जाने वाले विकल्पों पर निर्भर करेगा, इसलिए मेरे कार्यक्रम के थोक में सशर्त बयानों के टन होने के बजाय बाद में उपयोग के लिए आवश्यक कमांड को स्टोर करना बहुत आसान है।
बेंजामिन

1
@ बेंजामिन, तो कम से कम विकल्पों को चर के रूप में संग्रहीत करें, और कमांड नहीं। जैसेvar='*.txt'; find . -name "$var"
कुरुमी
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.