निष्पादित होने पर चर को उद्धृत किया जाना चाहिए?


18

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

हालाँकि, निम्नलिखित की तरह एक समारोह पर विचार करें:

run_this(){
    $@
}

$@वहाँ उद्धृत किया जाना चाहिए या नहीं? मैं इसके साथ थोड़ा खेलता था और ऐसा कोई भी मामला नहीं खोज सका जहां उद्धरणों की कमी के कारण समस्या पैदा हुई हो। दूसरी ओर, उद्धरणों का उपयोग करने से यह तब टूटता है जब एक कमांड को रिक्त स्थान के रूप में पारित किया जाता है:

#!/usr/bin/sh
set -x
run_this(){
    $@
}
run_that(){
    "$@"
}
comm="ls -l"
run_this "$comm"
run_that "$comm"

रिटर्न के ऊपर स्क्रिप्ट चलाना:

$ a.sh
+ comm='ls -l'
+ run_this 'ls -l'
+ ls -l
total 8
-rw-r--r-- 1 terdon users  0 Dec 22 12:58 da
-rw-r--r-- 1 terdon users 45 Dec 22 13:33 file
-rw-r--r-- 1 terdon users 43 Dec 22 12:38 file~
+ run_that 'ls -l'
+ 'ls -l'
/home/terdon/scripts/a.sh: line 7: ls -l: command not found

मुझे लगता है कि अगर मैं run_that $commइसके बजाय का उपयोग कर सकते हैं run_that "$comm", लेकिन run_thisदोनों के साथ काम करता है (unquoted) के बाद से यह सुरक्षित शर्त की तरह लगता है।

तो, $@किसी फ़ंक्शन में उपयोग करने के विशिष्ट मामले में जिसका काम $@कमांड के रूप में निष्पादित करना $@है, उद्धृत किया जाना चाहिए? कृपया समझाएं कि इसे क्यों नहीं उद्धृत किया जाना चाहिए / नहीं और डेटा का एक उदाहरण देना चाहिए जो इसे तोड़ सकता है।


6
run_thatव्यवहार निश्चित रूप से है कि मैं क्या उम्मीद करूँगा (क्या होगा अगर कमांड के पथ में एक स्थान है?)। यदि आप अन्य व्यवहार चाहते हैं, तो निश्चित रूप से आप इसे कॉल- साइट पर अनक्वोट कर देंगे जहां आपको पता है कि डेटा क्या है? मुझे इस फ़ंक्शन को कॉल करने की उम्मीद है run_that ls -l, जो दोनों संस्करणों में समान है। क्या कोई ऐसा मामला है जिससे आपको अलग तरह की उम्मीद है?
माइकल होमर

@MichaelHomer मुझे लगता है कि मेरे संपादन ने यहां यह संकेत दिया: unix.stackexchange.com/a/250985/70524
muru

किसी कारण के लिए @MichaelHomer (शायद क्योंकि मैं अभी भी मेरी दूसरी कप कॉफी नहीं है) मैं कमांड के तर्कों या पथ में रिक्त स्थान पर विचार नहीं किया था, लेकिन केवल कमांड में ही (विकल्प)। जैसा कि अक्सर होता है, रेट्रोस्पेक्ट में यह बहुत स्पष्ट लगता है।
terdon

एक कारण है कि गोले अभी भी फ़ंक्शन को केवल एक सरणी में कमांड भरने और इसे निष्पादित करने के बजाय कार्यों का समर्थन करते हैं ${mycmd[@]}
चेपनर

जवाबों:


20

समस्या यह है कि कमांड को कमांड में कैसे पास किया जाता है:

$ run_this ls -l Untitled\ Document.pdf 
ls: cannot access Untitled: No such file or directory
ls: cannot access Document.pdf: No such file or directory
$ run_that ls -l Untitled\ Document.pdf 
-rw------- 1 muru muru 33879 Dec 20 11:09 Untitled Document.pdf

"$@"सामान्य मामले में उपयोग किया जाना चाहिए जहां आपका run_thisफ़ंक्शन सामान्य रूप से लिखित कमांड के लिए उपसर्ग किया जाता है। run_thisनरक उद्धृत करने की ओर जाता है:

$ run_this 'ls -l Untitled\ Document.pdf'
ls: cannot access Untitled\: No such file or directory
ls: cannot access Document.pdf: No such file or directory
$ run_this 'ls -l "Untitled\ Document.pdf"'
ls: cannot access "Untitled\: No such file or directory
ls: cannot access Document.pdf": No such file or directory
$ run_this 'ls -l Untitled Document.pdf'
ls: cannot access Untitled: No such file or directory
ls: cannot access Document.pdf: No such file or directory
$ run_this 'ls -l' 'Untitled Document.pdf'
ls: cannot access Untitled: No such file or directory
ls: cannot access Document.pdf: No such file or directory

मुझे यकीन नहीं है कि मुझे रिक्त स्थान के साथ फ़ाइल नाम कैसे पारित करना चाहिए run_this


1
यह वास्तव में आपका संपादन था जिसने इसे प्रेरित किया। किसी कारण से यह मेरे लिए रिक्त स्थान के साथ फ़ाइल नाम के साथ परीक्षण करने के लिए नहीं हुआ। मुझे बिल्कुल पता नहीं क्यों नहीं है, लेकिन तुम वहाँ जाओ। आप बिल्कुल सही हैं, मुझे इसके साथ सही तरीके से करने का कोई तरीका नहीं दिखता है run_this
terdon

@terdon के हवाले से एक आदत इतनी अधिक हो गई है कि मैंने मान लिया कि आप $@अकस्मात छोड़ गए हैं। मुझे एक उदाहरण छोड़ना चाहिए था। : डी
मुरु

2
नहीं, यह वास्तव में एक आदत की इतनी अधिक है कि मैंने इसे (गलत तरीके से) परीक्षण किया और निष्कर्ष निकाला कि "हुह, शायद यह उद्धरण की आवश्यकता नहीं है"। एक प्रक्रिया जिसे आमतौर पर ब्रेनफार्ट के रूप में जाना जाता है।
terdon

1
आप रिक्त स्थान के साथ फ़ाइल नाम नहीं दे सकते हैं run_this। यह मूल रूप से वही समस्या है जिसे आप कॉम्प्लेक्स कमांड को स्ट्रिंग्स में स्टफिंग के साथ चलाते हैं जैसा कि Bash FAQ 050 में चर्चा की गई है ।
इटन रिस्नर

9

यह या तो:

interpret_this_shell_code() {
  eval "$1"
}

या:

interpret_the_shell_code_resulting_from_the_concatenation_of_those_strings_with_spaces() {
  eval "$@"
}

या:

execute_this_simple_command_with_these_arguments() {
  "$@"
}

परंतु:

execute_the_simple_command_with_the_arguments_resulting_from_split+glob_applied_to_these_strings() {
  $@
}

ज्यादा मतलब नहीं है।

यदि आप ls -lकमांड को निष्पादित करना चाहते हैं ( lsकमांड के साथ lsऔर -lतर्कों के रूप में नहीं ), तो आप करेंगे:

interpret_this_shell_code '"ls -l"'
execute_this_simple_command_with_these_arguments 'ls -l'

लेकिन अगर (अधिक संभावना है), यह lsकमांड के साथ lsऔर -lतर्कों के रूप में, आप चलाएंगे:

interpret_this_shell_code 'ls -l'
execute_this_simple_command_with_these_arguments ls -l

अब, यदि यह एक साधारण कमांड से अधिक है जिसे आप निष्पादित करना चाहते हैं, यदि आप चर असाइनमेंट, पुनर्निर्देशन, पाइप ... करना चाहते हैं, तो केवल interpret_this_shell_codeकरेंगे:

interpret_this_shell_code 'ls -l 2> /dev/null'

हालांकि आप हमेशा कर सकते हैं:

execute_this_simple_command_with_these_arguments eval '
  ls -l 2> /dev/null'

5

इसे बैश / ksh / zsh परिप्रेक्ष्य से देख रहे हैं , $*और $@सामान्य सरणी विस्तार का एक विशेष मामला है। सरणी विस्तार सामान्य चर विस्तार की तरह नहीं हैं:

$ a=("a b c" "d e" f)
$ printf ' -> %s\n' "${a[*]}"
 -> a b c d e f
$ printf ' -> %s\n' "${a[@]}"
-> a b c
-> d e
-> f
$ printf ' -> %s\n' ${a[*]}
 -> a
 -> b
 -> c
 -> d
 -> e
 -> f
$ printf ' -> %s\n' ${a[@]}
 -> a
 -> b
 -> c
 -> d
 -> e
 -> f

साथ $*/ ${a[*]}विस्तार आप सरणी के पहले मूल्य के साथ शामिल हो पाने IFS-which डिफ़ॉल्ट-में एक विशाल स्ट्रिंग द्वारा जगह नहीं है। यदि आप इसे उद्धृत नहीं करते हैं, तो यह एक सामान्य स्ट्रिंग की तरह विभाजित हो जाता है।

साथ $@/ ${a[@]}विस्तार, व्यवहार पर निर्भर करता $@/ ${a[@]}विस्तार या उद्धृत नहीं किया गया है:

  1. यदि यह उद्धृत किया जाता है ( "$@"या "${a[@]}"), तो आपको "$1" "$2" "$3" #... या के बराबर मिलता है "${a[1]}" "${a[2]}" "${a[3]}" # ...
  2. अगर यह उद्धृत नहीं किया गया है ( $@या ${a[@]}) आप के बराबर मिलता है $1 $2 $3 #... या${a[1]} ${a[2]} ${a[3]} # ...

रैपिंग कमांड के लिए, आप निश्चित रूप से उद्धृत @ विस्तार (1.) चाहते हैं।


बैश (और बाश-जैसे) सरणियों पर अधिक अच्छी जानकारी: https://lukeshu.com/blog/bash-arrays.html


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

4

जब से आप डबल उद्धरण नहीं करते हैं $@, तो आपने अपने फ़ंक्शन को दिए गए लिंक में सभी ग्लोबिंग मुद्दों को छोड़ दिया।

आप एक कमांड नाम कैसे चला सकते हैं *? आप इसके साथ नहीं कर सकते हैं run_this:

$ ls
1 2
$ run_this '*'
dash: 2: 1: not found
$ run_that '*'
dash: 3: *: not found

और आप देखते हैं, जब त्रुटि हुई, तब भी run_thatआपको एक अधिक सार्थक संदेश दिया गया।

$@व्यक्तिगत शब्दों का विस्तार करने का एकमात्र तरीका इसे दोहरे उद्धरण चिह्नों है। यदि आप इसे एक कमांड के रूप में चलाना चाहते हैं, तो आपको कमांड और इसे अलग-अलग शब्दों के रूप में मापदंडों को पास करना चाहिए। वह चीज जो आपने कॉलर फंक्शन पर की थी, आपके फंक्शन के अंदर नहीं।

$ cmd=ls
$ param1=-l
$ run_that "$cmd" "$param1"
total 0
-rw-r--r-- 1 cuonglm cuonglm 0 Dec 23 17:33 1
-rw-r--r-- 1 cuonglm cuonglm 0 Dec 23 17:33 2

एक बेहतर विकल्प है। या यदि आपका शेल ऐरे सपोर्ट करता है:

$ cmd=(ls -l)
$ run_that "${cmd[@]}"
total 0
-rw-r--r-- 1 cuonglm cuonglm 0 Dec 23 17:33 1
-rw-r--r-- 1 cuonglm cuonglm 0 Dec 23 17:33 2

यहां तक ​​कि जब शेल ऐरे का समर्थन नहीं करता है, तब भी आप इसका उपयोग करके खेल"$@" सकते हैं ।


3

चर में निष्पादन bashविफलता-प्रवण तकनीक है। यह एक run_thisसमारोह लिखना असंभव है, जो सभी किनारे के मामलों को सही ढंग से संभालता है, जैसे:

  • पाइपलाइन (जैसे ls | grep filename)
  • इनपुट / आउटपुट पुनर्निर्देशन (उदाहरण के लिए ls > /dev/null)
  • शेल स्टेटमेंट जैसे if whileआदि।

यदि आप सभी कोड पुनरावृत्ति से बचना चाहते हैं, तो आप कार्यों का उपयोग करना बेहतर है। उदाहरण के लिए, इसके बजाय:

run_this(){
    "$@"
}
command="ls -l"
...
run_this "$command"

आपको लिखना चाहिए

command() {
    ls -l
}
...
command

यदि कमांड केवल रन टाइम पर उपलब्ध हैं, तो आपको उपयोग करना चाहिए eval, जिसे विशेष रूप से सभी क्विरक्स को संभालने के लिए डिज़ाइन किया गया है जो run_thisविफल हो जाएंगे :

command="ls -l | grep filename > /dev/null"
...
eval "$command"

ध्यान दें कि evalहै जाना जाता सुरक्षा के मुद्दों के लिए, लेकिन अगर आप अविश्वस्त स्रोतों से चर पारित run_this, तो आप सिर्फ रूप में अच्छी तरह मनमाना कोड निष्पादन का सामना करना पड़ेगा।


1

चुनना आपको है। यदि आप $@इसके किसी भी मूल्य को उद्धृत नहीं करते हैं तो अतिरिक्त विस्तार और व्याख्या से गुजरना पड़ता है। यदि आप इसे उद्धृत करते हैं, तो सभी तर्कों को पारित कर दिया जाता है, तो फ़ंक्शन को इसके विस्तार शब्दशः में पुन: प्रस्तुत किया जाता है। आप कभी भी शेल सिंटैक्स टोकन को मज़बूती से संभाल नहीं पाएंगे, जैसे &>|और जैसे भी हो, वैसे भी अपने आप को तर्कों को पार्स किए बिना - और इसलिए आप अपने फ़ंक्शन को किसी एक को सौंपने के अधिक उचित विकल्पों के साथ छोड़ दिए जाते हैं:

  1. वास्तव में एक ही सरल कमांड के निष्पादन में उपयोग किए जाने वाले शब्द "$@"

... या ...

  1. आपके तर्कों का एक और विस्तृत और व्याख्या किया गया संस्करण, जो तब केवल एक साधारण कमांड के रूप में एक साथ लागू किया जाता है $@

न तो रास्ता गलत है अगर यह जानबूझकर है और यदि आप जो चुनते हैं उसका प्रभाव अच्छी तरह से समझा जाता है। दोनों तरीकों से एक के बाद एक फायदे हैं, हालांकि दूसरे के फायदे शायद ही कभी उपयोगी होते हैं। फिर भी...

(run_this(){ $@; }; IFS=@ run_this 'ls@-dl@/tmp')

drwxrwxrwt 22 root root 660 Dec 28 19:58 /tmp

... यह बेकार नहीं है , बस शायद ही कभी अधिक उपयोग की संभावना है । और एक bashशेल में, क्योंकि bashडिफ़ॉल्ट रूप से अपने वातावरण के लिए एक वैरिएबल परिभाषा को छड़ी नहीं करता है , यहां तक ​​कि तब भी जब परिभाषा एक विशेष बिलिन की कमांड-लाइन या एक फ़ंक्शन के लिए प्रस्तुत की जाती है, के लिए वैश्विक मान $IFSअप्रभावित है, और इसकी घोषणा स्थानीय है केवल run_this()कॉल करने के लिए ।

इसी तरह:

(run_this(){ $@; }; set -f; run_this ls -l \*)

ls: cannot access *: No such file or directory

... ग्लोबिंग भी विन्यास योग्य है। उद्धरण एक उद्देश्य की सेवा करते हैं - वे कुछ भी नहीं के लिए हैं। उनके बिना शेल विस्तार अतिरिक्त व्याख्या से गुजरता है - कॉन्फ़िगर करने योग्य व्याख्या। यह हुआ करता था - कुछ बहुत पुराने गोले के साथ - जो विश्व स्तर पर सभी इनपुट पर लागू किया $IFSगया था , और न केवल विस्तार। वास्तव में, कहा जाता है कि गोले बहुत व्यवहार करते हैं जैसे उन्होंने मूल्य के सभी इनपुट शब्दों को तोड़ दिया । और इसलिए, यदि आप जो खोज रहे हैं, वह बहुत पुराना शेल व्यवहार है, तो आपको उपयोग करना चाहिएrun_this()$IFSrun_this()

मैं इसकी तलाश नहीं कर रहा हूं, और मैं इस समय इसके लिए एक उपयोगी उदाहरण के साथ आने के लिए काफी सख्त हूं। मैं आम तौर पर उन कमांडों के लिए पसंद करता हूं जो मेरे शेल को चलाता है जो मैं इसे टाइप करता हूं। और इसलिए, पसंद को देखते हुए, मैं लगभग हमेशा होता run_that()। सिवाय इसके कि...

(run_that(){ "$@"; }; IFS=l run_that 'ls' '-ld' '/tmp')

drwxrwxrwt 22 root root 660 Dec 28 19:58 /tmp

बस के बारे में कुछ भी उद्धृत किया जा सकता है। कमांडो उद्धृत किया जाएगा। यह काम करता है क्योंकि जब तक कमांड वास्तव में चलाया जाता है, तब तक सभी इनपुट शब्द पहले ही उद्धरण हटाने से गुजर चुके हैं - जो शेल की इनपुट व्याख्या प्रक्रिया का अंतिम चरण है। इसलिए शेल के बीच की व्याख्या के बीच केवल 'ls'और lsकेवल अंतर हो सकता है - और इसीलिए उद्धरण lsयह सुनिश्चित करता है कि नामित किसी अन्य उपनामls को मेरे उद्धृत lsकमांड शब्द के लिए प्रतिस्थापित नहीं किया गया है। इसके अलावा, केवल वही चीजें जो उद्धरण को प्रभावित करती हैं वे हैं शब्दों का परिसीमन (जो कि कैसे और क्यों चर / इनपुट-व्हाट्सएप उद्धरण काम करता है) , और मेटाचैटर्स और आरक्षित शब्दों की व्याख्या।

इसलिए:

'for' f in ...
 do   :
 done

bash: for: command not found
bash:  do: unexpected token 'do'
bash:  do: unexpected token 'done'

आप कभी भी run_this()या तो ऐसा नहीं कर पाएंगे run_that()

लेकिन फ़ंक्शन के नाम, या $PATH'डी कमांड्स, या बिलिन ठीक उद्धृत या निर्विवाद रूप से निष्पादित होंगे, और यह वास्तव में कैसे run_this()और run_that()पहली जगह में काम करते हैं। आप $<>|&(){}उनमें से किसी के साथ भी उपयोगी कुछ नहीं कर पाएंगे । छोटा है eval, है।

(run_that(){ "$@"; }; run_that eval printf '"%s\n"' '"$@"')

eval
printf
"%s\n"
"$@"

लेकिन इसके बिना, आपके द्वारा उपयोग किए जाने वाले उद्धरणों के आधार पर आप एक साधारण कमांड की सीमा के लिए विवश हैं (यहां तक ​​कि जब आप नहीं करते क्योंकि $@प्रक्रिया की शुरुआत में एक उद्धरण की तरह काम करता है जब कमांड मेटाचैटर्स के लिए पार्स किया जाता है) । एक ही बाधा कमांड-लाइन असाइनमेंट और पुनर्निर्देशन का सच है, जो फ़ंक्शन के कमांड-लाइन तक सीमित हैं। लेकिन यह कोई बड़ी बात नहीं है:

(run_that(){ "$@";}; echo hey | run_that cat)

hey

मुझे आसानी से <पुनर्निर्देशित इनपुट या >आउटपुट मिल सकता है क्योंकि मैंने पाइप खोला था।

वैसे भी, गोल-गोल तरीके से, यहाँ कोई सही या गलत तरीका नहीं है - प्रत्येक तरीके के उपयोग हैं। यह सिर्फ इतना है कि आपको इसे लिखना चाहिए क्योंकि आप इसे इस्तेमाल करने का इरादा रखते हैं, और आपको पता होना चाहिए कि आपका क्या मतलब है। उद्धरण उद्धृत करने का एक उद्देश्य हो सकता है - अन्यथा उद्धरण बिल्कुल नहीं होगा - लेकिन यदि आप उन्हें अपने उद्देश्य से प्रासंगिक कारणों के लिए छोड़ देते हैं, तो आप सिर्फ बुरा कोड लिख रहे हैं। तुम्हारा मतलब क्या है; मैं वैसे भी कोशिश करता हूं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.