हम एक चर में संग्रहीत कमांड कैसे चला सकते हैं?


35
$ ls -l /tmp/test/my\ dir/
total 0

मैं सोच रहा था कि उपरोक्त कमांड को चलाने के निम्नलिखित तरीके विफल या सफल क्यों हैं?

$ abc='ls -l "/tmp/test/my dir"'

$ $abc
ls: cannot access '"/tmp/test/my': No such file or directory
ls: cannot access 'dir"': No such file or directory

$ "$abc"
bash: ls -l "/tmp/test/my dir": No such file or directory

$ bash -c $abc
'my dir'

$ bash -c "$abc"
total 0

$ eval $abc
total 0

$ eval "$abc"
total 0


जवाबों:


54

इस पर कई सवालों पर चर्चा की गई है। मैं उन सभी मुद्दों को इकट्ठा करने की कोशिश करूंगा, जो मैं यहां ले सकता हूं। अंत में सन्दर्भ।


यह असफल क्यों होता है

कारण यह है कि आप उन समस्याओं का सामना कर रहे हैं शब्द विभाजन और तथ्य यह है कि चर से विस्तारित उद्धरण उद्धरण के रूप में कार्य नहीं करते हैं, लेकिन सिर्फ साधारण अक्षर हैं।

प्रश्न में प्रस्तुत मामले:

$ abc='ls -l "/tmp/test/my dir"'

यहाँ, $abcविभाजित और lsदो तर्क मिलते हैं "/tmp/test/myऔर dir"(पहले के मोर्चे पर उद्धरण के साथ और दूसरे के पीछे):

$ $abc
ls: cannot access '"/tmp/test/my': No such file or directory
ls: cannot access 'dir"': No such file or directory

यहाँ, विस्तार उद्धृत किया गया है, इसलिए इसे एक ही शब्द के रूप में रखा गया है। शेल एक प्रोग्राम को खोजने की कोशिश करता है जिसे कहा जाता है ls -l "/tmp/test/my dir", रिक्त स्थान और उद्धरण शामिल हैं।

$ "$abc"
bash: ls -l "/tmp/test/my dir": No such file or directory

और यहाँ, केवल पहले शब्द या $abcतर्क के रूप में लिया जाता है -c, इसलिए बैश केवल lsवर्तमान निर्देशिका में चलता है। दूसरे शब्दों बैश के लिए तर्क हैं, और भरने के लिए उपयोग किया जाता है $0, $1आदि

$ bash -c $abc
'my dir'

के साथ bash -c "$abc", और eval "$abc", एक अतिरिक्त शेल प्रोसेसिंग स्टेप है, जो उद्धरणों को काम करता है, लेकिन सभी शेल एक्सपेंशन को फिर से संसाधित करने का कारण बनता है , इसलिए उपयोगकर्ता द्वारा प्रदान किए गए डेटा से गलती से कमांड विस्तार से चलने का जोखिम है, जब तक कि आप बहुत अधिक न हों। उद्धृत करने के बारे में सावधान।


इसे करने के बेहतर तरीके

कमांड को स्टोर करने के दो बेहतर तरीके हैं a) इसके बजाय फंक्शन का उपयोग करें, b) एक ऐरे वेरिएबल (या पोजिशनल पैरामीटर) का उपयोग करें।

एक समारोह का उपयोग करना:

बस कमांड के साथ एक फंक्शन की घोषणा करें, और फंक्शन को ऐसे चलाएं जैसे कि यह एक कमांड हो। फ़ंक्शन के भीतर आदेशों में विस्तार केवल तभी संसाधित होता है जब कमांड चलता है, तब नहीं जब इसे परिभाषित किया गया हो, और आपको अलग-अलग कमांडों को उद्धृत करने की आवश्यकता नहीं है।

# define it
myls() {
    ls -l "/tmp/test/my dir"
}

# run it
myls

एक सरणी का उपयोग करना:

Arrays बहु-शब्द चर बनाने की अनुमति देता है जहां व्यक्तिगत शब्दों में सफेद स्थान होता है। यहां, अलग-अलग शब्दों को अलग-अलग सरणी तत्वों के रूप में संग्रहीत किया जाता है, और "${array[@]}"विस्तार प्रत्येक तत्व को अलग-अलग शेल शब्दों के रूप में विस्तारित करता है:

# define the array
mycmd=(ls -l "/tmp/test/my dir")

# run the command
"${mycmd[@]}"

वाक्य-विन्यास थोड़ा भयानक है, लेकिन सरणियाँ आपको कमांड लाइन टुकड़ा-दर-टुकड़ा बनाने की अनुमति भी देती हैं। उदाहरण के लिए:

mycmd=(ls)               # initial command
if [ "$want_detail" = 1 ]; then
    mycmd+=(-l)          # optional flag
fi
mycmd+=("$targetdir")    # the filename

"${mycmd[@]}"

या कमांड लाइन के कुछ हिस्सों को स्थिर रखें और सरणी का उपयोग करें, इसके कुछ भाग, विकल्प या फ़ाइल नाम भरें:

options=(-x -v)
files=(file1 "file name with whitespace")
target=/somedir

transmutate "${options[@]}" "${files[@]}" "$target"

सरणियों का नकारात्मक पक्ष यह है कि वे एक मानक विशेषता नहीं हैं, इसलिए सादे POSIX गोले (जैसे dash, /bin/shडेबियन / उबंटू में डिफ़ॉल्ट ) उनका समर्थन नहीं करते हैं (लेकिन नीचे देखें)। हालांकि, बैश, ksh और zsh करते हैं, इसलिए यह संभव है कि आपके सिस्टम में कुछ शेल हो जो ऐरे का समर्थन करता हो।

का उपयोग करते हुए "$@"

नामित सरणियों के समर्थन के बिना गोले में, कोई अभी भी "$@"एक कमांड के तर्कों को रखने के लिए स्थितीय मापदंडों (छद्म-सरणी ) का उपयोग कर सकता है ।

निम्नलिखित पोर्टेबल स्क्रिप्ट बिट्स होना चाहिए जो पिछले अनुभाग में कोड बिट्स के बराबर करते हैं। सरणी को स्थितिगत "$@"मापदंडों की सूची से बदल दिया जाता है । सेटिंग के "$@"साथ किया जाता है set, और आसपास के दोहरे उद्धरण "$@"महत्वपूर्ण होते हैं (ये सूची के तत्वों को व्यक्तिगत रूप से उद्धृत किया जाता है)।

सबसे पहले, केवल तर्कों के साथ एक कमांड स्टोर करना "$@"और इसे चलाना:

set -- ls -l "/tmp/test/my dir"
"$@"

कमांड के लिए कमांड लाइन विकल्प के सक्रिय रूप से भागों की स्थापना:

set -- ls
if [ "$want_detail" = 1 ]; then
    set -- "$@" -l
fi
set -- "$@" "$targetdir"

"$@"

केवल "$@"विकल्प और ऑपरेंड के लिए उपयोग करना:

set -- -x -v
set -- "$@" file1 "file name with whitespace"
set -- "$@" /somedir

transmutate "$@"

(ज़ाहिर है, "$@"आमतौर पर स्क्रिप्ट के लिए तर्कों से भरा होता है, इसलिए आपको पुन: प्रयोजन से पहले उन्हें कहीं और सहेजना होगा "$@"।)


से सावधान eval!

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

read -r filename
cmd="ls -l '$filename'"
eval "$cmd";

लेकिन अगर वे इनपुट देते हैं '$(uname)'.txt, तो आपकी स्क्रिप्ट खुशी से कमांड प्रतिस्थापन चलाती है।

सरणियों के साथ एक संस्करण उस के लिए प्रतिरक्षा है क्योंकि पूरे समय के लिए शब्दों को अलग रखा जाता है, सामग्री की सामग्री के लिए कोई उद्धरण या अन्य प्रसंस्करण नहीं है filename

read -r filename
cmd=(ls -ld -- "$filename")
"${cmd[@]}"

संदर्भ


2
आप कर सकते हैं eval के आसपास बात कर रही बात से cmd="ls -l $(printf "%q" "$filename")"। सुंदर नहीं है, लेकिन यदि उपयोगकर्ता किसी के उपयोग पर मृत है eval, तो यह मदद करता है। यह कमांड भेजने के लिए भी बहुत उपयोगी है, हालांकि इसी तरह की चीजें, जैसे कि ssh foohost "ls -l $(printf "%q" "$filename")", या इस प्रश्न के मसाले में ssh foohost "$cmd":।
पैट्रिक

सीधे संबंधित नहीं है, लेकिन क्या आपने निर्देशिका को हार्ड-कोड किया है? उस स्थिति में, आप अन्य लोगों को देखना चाह सकते हैं। कुछ इस तरह: $ alias abc='ls -l "/tmp/test/my dir"'
बनी बनी

4

(गैर-तुच्छ) कमांड चलाने का सबसे सुरक्षित तरीका है eval। फिर आप कमांड लाइन पर जैसा आप करेंगे वैसा ही लिख सकते हैं और इसे ठीक वैसे ही निष्पादित किया जाता है जैसे कि आपने इसे दर्ज किया था। लेकिन आपको सब कुछ उद्धृत करना होगा।

साधारण मामला:

abc='ls -l "/tmp/test/my dir"'
eval "$abc"

इतना आसान मामला नहीं:

# command: awk '! a[$0]++ { print "foo: " $0; }' inputfile
abc='awk '\''! a[$0]++ { print "foo: " $0; }'\'' inputfile'
eval "$abc"

3

दूसरी बोली साइन कमांड को तोड़ती है।

जब मैं चलता हूं:

abc="ls -l '/home/wattana/Desktop'"
$abc

इसने मुझे एक त्रुटि दी।

लेकिन जब मैं दौड़ता हूं

abc="ls -l /home/wattana/Desktop"
$abc

कोई त्रुटि नहीं है

उस समय (मेरे लिए) इसे ठीक करने का कोई तरीका नहीं है, लेकिन आप निर्देशिका नाम में जगह नहीं होने से त्रुटि से बच सकते हैं।

इस उत्तर ने कहा कि eval कमांड का उपयोग इसे ठीक करने के लिए किया जा सकता है लेकिन यह मेरे लिए काम नहीं करता है :(


1
हाँ, यह तब तक काम करता है जब तक इसके लिए कोई ज़रूरत नहीं होती है जैसे एम्बेडेड स्पेस वाले फ़ाइलनाम (या ग्लोब कैरेक्टर वाले)।
इलकाचू

0

यदि यह काम नहीं करता है '', तो आपको उपयोग करना चाहिए ``:

abc=`ls -l /tmp/test/my\ dir/`

बेहतर तरीके से अपडेट करें:

abc=$(ls -l /tmp/test/my\ dir/)

यह एक चर में कमांड के परिणाम को संग्रहीत करता है। ओपी (विचित्र रूप से) कमांड को एक वैरिएबल में सेव करना चाहता है। ओह, और आपको $( command... )बैकटिक्स के बजाय वास्तव में उपयोग करना शुरू करना चाहिए ।
रोज़ा

स्पष्टीकरण और सुझावों के लिए बहुत बहुत धन्यवाद!
बालन

0

कैसे एक अजगर 3-लाइनर के बारे में?

bash-4.4# pwd
/home
bash-4.4# CUSTOM="ls -altr"
bash-4.4# python3 -c "import subprocess; subprocess.call(\"$CUSTOM\", shell=True)"
total 8
drwxr-xr-x    2 root     root          4096 Mar  4  2019 .
drwxr-xr-x    1 root     root          4096 Nov 27 16:42 ..
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.