व्हॉट्सएप या अन्य विशेष पात्रों पर मेरी शेल स्क्रिप्ट क्यों चोक करती है?


284

या, फ़ाइल नाम स्क्रिप्ट में पासिंग हैंडलिंग और अन्य स्ट्रिंग को मजबूत करने के लिए एक परिचयात्मक गाइड।

मैंने एक शेल स्क्रिप्ट लिखी थी जो ज्यादातर समय अच्छी तरह से काम करती है। लेकिन यह कुछ इनपुट्स (जैसे कुछ फ़ाइल नामों पर) पर चुटकी लेता है।

मुझे निम्नलिखित जैसी समस्या का सामना करना पड़ा:

  • मेरे पास एक फ़ाइल नाम है जिसमें एक स्थान है hello world, और इसे दो अलग-अलग फ़ाइलों के रूप में माना गया था helloऔर world
  • मेरे पास लगातार दो स्थानों के साथ एक इनपुट लाइन है और वे इनपुट में एक से सिकुड़ गए हैं।
  • व्हाट्सएप का प्रमुख और अनुगामी इनपुट लाइनों से गायब हो जाना।
  • कभी-कभी, जब इनपुट में एक वर्ण होता है \[*?, तो उन्हें कुछ पाठ द्वारा प्रतिस्थापित किया जाता है जो वास्तव में फ़ाइलों का नाम है।
  • इनपुट में एक एपोस्ट्रोफ '(या एक दोहरा उद्धरण ") है और उस बिंदु के बाद चीजें अजीब हो गईं।
  • इनपुट में एक बैकस्लैश है (या: मैं सिग्विन का उपयोग कर रहा हूं और मेरे कुछ फ़ाइल नामों में विंडोज-स्टाइल \सेपरेटर हैं)।

क्या चल रहा है और मैं इसे कैसे ठीक करूं?


16
shellcheckआपको अपने कार्यक्रमों की गुणवत्ता में सुधार करने में मदद करता है।
ऑरेलियन

3
उत्तर में वर्णित सुरक्षात्मक तकनीकों के अलावा, और हालांकि यह संभवतः अधिकांश पाठकों के लिए स्पष्ट है, मुझे लगता है कि यह टिप्पणी करने योग्य हो सकता है कि जब फ़ाइलों को कमांड-लाइन टूल का उपयोग करके संसाधित करने का इरादा होता है, तो यह अच्छा है कि वे फैंसी पात्रों से बचने के लिए अभ्यास करें यदि संभव हो तो पहले स्थान पर नाम।
bli


1
@bli नहीं, कि केवल कीड़े को बारी करने के लिए अधिक समय लगता है। यह आज कीड़े छिपा रहा है। और अब, आप नहीं जानते कि सभी फ़ाइलनाम बाद में आपके कोड के साथ उपयोग किए गए हैं।
वोल्कर सिएगेल

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

जवाबों:


352

हमेशा चर प्रतिस्थापन और आदेश प्रतिस्थापन को दोहरे उद्धरण चिह्नों का उपयोग करें: "$foo","$(foo)"

यदि आप $fooअयोग्य का उपयोग करते हैं , तो आपकी स्क्रिप्ट व्हॉट्सएप $(foo)युक्त इनपुट या मापदंडों (या कमांड आउटपुट, के साथ ) पर चोक हो जाएगी \[*?

वहां, आप पढ़ना बंद कर सकते हैं। ठीक है, ठीक है, यहाँ कुछ और हैं:

  • read- बिलिन के साथ लाइन द्वारा इनपुट लाइन पढ़ने के लिए read,while IFS= read -r line; do …
    प्लेन readबैकस्लैश और व्हाट्सएप का विशेष रूप से उपयोग करता है।
  • xargs- बचनाxargs । यदि आप का उपयोग करना चाहिए xargs, कि बनाओ xargs -0। इसके बजाय find … | xargs, प्राथमिकता देंfind … -exec …
    xargsव्हाट्सएप और पात्रों को \"'विशेष रूप से मानता है ।

इस उत्तर बॉर्न / POSIX शैली गोले (पर लागू होता है sh, ash, dash, bash, ksh, mksh, yash...)। Zsh उपयोगकर्ताओं को इसे छोड़ देना चाहिए और दोहराए जाने की समाप्ति को पढ़ना आवश्यक है? बजाय। यदि आप संपूर्ण नॉटी-ग्रिट्टी चाहते हैं, तो मानक या अपने शेल मैनुअल को पढ़ें


ध्यान दें कि नीचे दिए गए स्पष्टीकरण में कुछ अनुमान शामिल हैं (कथन जो अधिकांश स्थितियों में सत्य हैं लेकिन आसपास के संदर्भ या कॉन्फ़िगरेशन से प्रभावित हो सकते हैं)।

मुझे लिखने की आवश्यकता क्यों है "$foo"? उद्धरणों के बिना क्या होता है?

$fooइसका मतलब यह नहीं है "चर का मूल्य ले लो foo"। इसका अर्थ है कुछ और अधिक जटिल:

  • सबसे पहले, वैरिएबल का मान लें।
  • फ़ील्ड विभाजन: उस मान को व्हाट्सएप द्वारा अलग किए गए फ़ील्ड की सूची के रूप में मानते हैं, और परिणामी सूची का निर्माण करते हैं। उदाहरण के लिए, चर शामिल करता है, तो foo * bar ​उसके बाद इस कदम का परिणाम 3-तत्व सूची है foo, *, bar
  • फ़ाइल नाम पीढ़ी: प्रत्येक फ़ील्ड को एक ग्लोब के रूप में अर्थात वाइल्डकार्ड पैटर्न के रूप में मानें, और इसे फ़ाइल नामों की सूची से प्रतिस्थापित करें जो इस पैटर्न से मेल खाते हैं। यदि पैटर्न किसी भी फाइल से मेल नहीं खाता है, तो इसे अनमॉडिफाइड छोड़ दिया जाता है। हमारे उदाहरण fooमें, वर्तमान निर्देशिका में फ़ाइलों की सूची के बाद, और अंत में, सूची में यह परिणाम है bar। यदि वर्तमान निर्देशिका खाली है, परिणाम है foo, *, bar

ध्यान दें कि परिणाम स्ट्रिंग्स की एक सूची है। शेल सिंटैक्स में दो संदर्भ होते हैं: सूची संदर्भ और स्ट्रिंग संदर्भ। फ़ील्ड विभाजन और फ़ाइल नाम का निर्माण केवल सूची के संदर्भ में होता है, लेकिन यह अधिकतर समय होता है। डबल कोट्स एक स्ट्रिंग संदर्भ का परिसीमन करते हैं: पूरे डबल-कोटेड स्ट्रिंग एक एकल स्ट्रिंग है, विभाजित नहीं किया जाना है। (अपवाद: स्थितिगत "$@"मापदंडों की सूची का विस्तार करने के लिए, उदाहरण के "$@"लिए "$1" "$2" "$3"अगर तीन स्थितीय पैरामीटर हैं तो समतुल्य है । देखें कि $ * और $ @ के बीच क्या अंतर है? )

ऐसा ही $(foo)या उसके साथ या उसके स्थानापन्न के लिए होता है `foo`। साइड नोट पर, उपयोग न करें `foo`: इसके उद्धरण नियम अजीब और गैर-पोर्टेबल हैं, और सभी आधुनिक गोले समर्थन करते हैं $(foo)जो सहज ज्ञान युक्त उद्धरण नियमों को छोड़कर बिल्कुल समान है।

अंकगणितीय प्रतिस्थापन का उत्पादन भी समान विस्तार से गुजरता है, लेकिन यह आमतौर पर एक चिंता का विषय नहीं है क्योंकि इसमें केवल गैर-विस्तार योग्य वर्ण IFSहोते हैं (यह मानते हुए कि अंक या नहीं होते हैं -)।

देखें कि डबल-कोटिंग कब आवश्यक है? उन मामलों के बारे में अधिक जानकारी के लिए जब आप उद्धरण छोड़ सकते हैं।

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

मैं फ़ाइल नामों की सूची कैसे संसाधित करूं?

यदि आप लिखते हैं myfiles="file1 file2", तो फ़ाइलों को अलग करने के लिए रिक्त स्थान के साथ, यह रिक्त स्थान वाले फ़ाइल नामों के साथ काम नहीं कर सकता है। यूनिक्स फ़ाइल नामों में किसी भी वर्ण के अलावा हो सकता है /(जो हमेशा एक निर्देशिका विभाजक है) और अशक्त बाइट्स (जो आप अधिकांश शेल के साथ शेल स्क्रिप्ट में उपयोग नहीं कर सकते हैं)।

के साथ भी यही समस्या है myfiles=*.txt; … process $myfiles। जब आप ऐसा करते हैं, तो चर myfilesमें 5-वर्ण स्ट्रिंग होता है *.txt, और यह तब होता है जब आप लिखते हैं $myfilesकि वाइल्डकार्ड विस्तारित है। यह उदाहरण वास्तव में तब तक काम करेगा, जब तक आप अपनी स्क्रिप्ट को बदल नहीं देते myfiles="$someprefix*.txt"; … process $myfiles। यदि someprefixइसे सेट किया जाता है final report, तो यह काम नहीं करेगा।

किसी भी प्रकार की सूची (जैसे फ़ाइल नाम) को संसाधित करने के लिए, इसे एक सरणी में रखें। इसके लिए mksh, ksh93, yash या bash (या zsh की आवश्यकता होती है, जिसमें ये सभी उद्धृत मुद्दे नहीं हैं); एक सादे POSIX शेल (जैसे राख या डैश) में सरणी चर नहीं हैं।

myfiles=("$someprefix"*.txt)
process "${myfiles[@]}"

Ksh88 में एक भिन्न असाइनमेंट सिंटैक्स के साथ सरणी चर set -A myfiles "someprefix"*.txtहैं ( यदि आपको ksh88 / bash पोर्टेबिलिटी की आवश्यकता है तो अलग ksh वातावरण में असाइनमेंट चर देखें )। बॉर्न / पोसिक्स-शैली के गोले में एक एकल सरणी होती है, "$@"जो कि आपके द्वारा निर्धारित setस्थानीय मापदंडों का सरणी है और जो एक फ़ंक्शन के लिए स्थानीय है:

set -- "$someprefix"*.txt
process -- "$@"

फ़ाइल नाम के बारे में क्या शुरू होता है -?

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

वैकल्पिक रूप से, आप यह सुनिश्चित कर सकते हैं कि आपके फ़ाइल नाम किसी वर्ण के अलावा शुरू हों -। निरपेक्ष फ़ाइल नाम के साथ शुरू होता है /, और आप ./सापेक्ष नामों की शुरुआत में जोड़ सकते हैं । निम्न स्निपेट चर की सामग्री fको उसी फ़ाइल को संदर्भित करने के "सुरक्षित" तरीके से बदल देता है जिसे शुरू नहीं करने की गारंटी है -

case "$f" in -*) "f=./$f";; esac

इस विषय पर एक अंतिम नोट पर, सावधान रहें कि कुछ कमांड -अर्थ इनपुट या मानक आउटपुट के अर्थ के बाद भी व्याख्या करते हैं --। यदि आपको नाम की एक वास्तविक फ़ाइल को संदर्भित करने की आवश्यकता है -, या यदि आप इस तरह के एक कार्यक्रम को बुला रहे हैं और आप नहीं चाहते कि यह स्टडिन से पढ़ें या स्टडआउट को लिखें, तो -ऊपर के रूप में फिर से लिखना सुनिश्चित करें । देखें कि "du -sh *" और "du -sh//*" में क्या अंतर है? आगे की चर्चा के लिए।

मैं एक चर में एक कमांड को कैसे स्टोर कर सकता हूं?

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

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

command_path="$1"

"$command_path" --option --message="hello world"

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

cmd=(/path/to/executable --option --message="hello world" --)
cmd=("${cmd[@]}" "$file1" "$file2")
"${cmd[@]}"

यदि आप बिना सरणियों के खोल का उपयोग कर रहे हैं तो क्या होगा? यदि आप उन्हें संशोधित करने में कोई आपत्ति नहीं करते हैं, तो भी आप स्थितिगत मापदंडों का उपयोग कर सकते हैं।

set -- /path/to/executable --option --message="hello world" --
set -- "$@" "$file1" "$file2"
"$@"

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

code='/path/to/executable --option --message="hello world" -- /path/to/file1 | grep "interesting stuff"'
eval "$code"

नेस्टेड कोट्स को परिभाषा में नोट करें code: सिंगल कोट्स '…'एक स्ट्रिंग शाब्दिक का परिसीमन करते हैं, ताकि वेरिएबल codeका मान स्ट्रिंग हो /path/to/executable --option --message="hello world" -- /path/to/file1evalनिर्मित खोल बताता स्ट्रिंग के रूप में अगर यह स्क्रिप्ट में छपी एक तर्क के रूप में पारित पार्स करने के लिए है, तो उस बिंदु पर उद्धरण और पाइप पार्स कर रहे हैं, आदि

प्रयोग evalमुश्किल है। ध्यान से सोचें कि कब क्या हो जाता है। विशेष रूप से, आप कोड में केवल एक फ़ाइल नाम नहीं भर सकते हैं: आपको इसे उद्धृत करने की आवश्यकता है, ठीक उसी तरह जैसे आप एक स्रोत कोड फ़ाइल में थे। ऐसा करने का कोई सीधा तरीका नहीं है। की तरह कुछ code="$code $filename"टूट जाता है, तो फ़ाइल नाम किसी भी खोल विशेष वर्ण है (रिक्त स्थान, $, ;, |, <, >, आदि)। code="$code \"$filename\""अभी भी टूटता है "$\`। यहां तक ​​कि code="$code '$filename'"अगर फ़ाइल नाम में ए भी हो तो भी टूट जाता है '। दो उपाय हैं।

  • फ़ाइल नाम के आसपास उद्धरणों की एक परत जोड़ें। ऐसा करने का सबसे आसान तरीका है कि इसके चारों ओर एकल उद्धरण जोड़ें, और एकल उद्धरणों को प्रतिस्थापित करें '\''

    quoted_filename=$(printf %s. "$filename" | sed "s/'/'\\\\''/g")
    code="$code '${quoted_filename%.}'"
  • कोड के अंदर वैरिएबल का विस्तार रखें, ताकि कोड के मूल्यांकन के समय यह दिखे, न कि कोड के टुकड़े का निर्माण होने पर। यह सरल है, लेकिन केवल तभी काम करता है जब कोड निष्पादित होने के समय चर उसी मान के साथ होता है, उदाहरण के लिए यदि कोड एक लूप में बनाया गया है।

    code="$code \"\$filename\""

अंत में, क्या आपको वास्तव में एक चर युक्त कोड की आवश्यकता है? एक कोड ब्लॉक को एक नाम देने का सबसे प्राकृतिक तरीका एक फ़ंक्शन को परिभाषित करना है।

क्या हो रहा है read?

बिना -r, readनिरंतरता लाइनों की अनुमति देता है - यह इनपुट की एक एकल तार्किक रेखा है:

hello \
world

read$IFS(बिना -r, बैकस्लैश भी उन से बच जाता है) वर्णों द्वारा सीमांकित क्षेत्रों में इनपुट लाइन को विभाजित करता है । उदाहरण के लिए, यदि इनपुट तीन शब्दों वाली एक पंक्ति है, तो इनपुट के पहले शब्द पर read first second thirdसेट firstहोता है, secondदूसरे शब्द के thirdलिए और तीसरे शब्द के लिए। यदि अधिक शब्द हैं, तो अंतिम चर में वह सब कुछ शामिल है जो पूर्ववर्ती को सेट करने के बाद बचा है। अग्रणी और अनुगामी व्हाट्सएप की छंटनी की जाती है।

IFSखाली स्ट्रिंग पर सेट करना किसी भी ट्रिमिंग से बचा जाता है। देखें कि IFS = के बजाय IFS = read` का उपयोग इतनी बार क्यों किया जाता है; पढ़े जाने के दौरान ..? एक लंबी व्याख्या के लिए।

इसमें गलत क्या है xargs?

का इनपुट प्रारूप xargsव्हॉट्सएप-पृथक स्ट्रिंग्स है, जो वैकल्पिक रूप से एकल- या दोहरे-उद्धृत हो सकता है। कोई भी मानक उपकरण इस प्रारूप को आउटपुट नहीं करता है।

करने के लिए xargs -L1या इनपुट xargs -lलगभग लाइनों की एक सूची है, लेकिन काफी नहीं है - अगर एक पंक्ति के अंत में एक जगह है, तो निम्नलिखित पंक्ति एक निरंतरता रेखा है।

आप xargs -0जहाँ लागू हो (और जहाँ उपलब्ध हो: GNU (Linux, Cygwin), बिजीबॉक्स, BSD, OSX का उपयोग कर सकते हैं , लेकिन यह POSIX में नहीं है)। यह सुरक्षित है, क्योंकि नल बाइट्स ज्यादातर डेटा में, विशेष रूप से फ़ाइल नामों में दिखाई नहीं दे सकते हैं। फ़ाइल नामों की अशक्त-पृथक सूची बनाने के लिए, find … -print0(या आप find … -exec …नीचे बताए अनुसार उपयोग कर सकते हैं )।

मैं किस तरह से मिली फाइलों को प्रोसेस करता हूं find?

find  -exec some_command a_parameter another_parameter {} +

some_commandएक बाहरी कमांड होने की जरूरत है, यह एक शेल फ़ंक्शन या उपनाम नहीं हो सकता है। यदि आपको फ़ाइलों को संसाधित करने के लिए शेल को आह्वान करने की आवश्यकता है, तो shस्पष्ट रूप से कॉल करें ।

find  -exec sh -c '
  for x do
    … # process the file "$x"
  done
' find-sh {} +

मेरा कुछ और सवाल है

इस साइट, या या पर टैग ब्राउज़ करें । (कुछ सामान्य युक्तियों और सामान्य प्रश्नों की एक हाथ से चयनित सूची देखने के लिए "और जानें ..." पर क्लिक करें।) यदि आपने खोज की है और आप कोई उत्तर नहीं खोज सकते हैं, तो पूछें


6
@ John1024 यह केवल एक GNU फीचर है, इसलिए मैं "कोई मानक उपकरण" नहीं रखूंगा।
गिल्स

2
आप आसपास उद्धरण की जरूरत है $(( ... ))(यह भी $[...]में छोड़कर कुछ गोले में) zsh(यहां तक कि श अनुकरण में) और mksh
स्टीफन चेजलस

3
ध्यान दें कि xargs -0POSIX नहीं है। FreeBSD को छोड़कर xargs, आप आम तौर पर xargs -r0इसके बजाय चाहते हैं xargs -0
स्टीफन चेजेलस

2
@ जॉन 1024, नहीं, ls --quoting-style=shell-alwaysसंगत नहीं है xargs। कोशिश करेंtouch $'a\nb'; ls --quoting-style=shell-always | xargs
स्टीफन चेज़लस

3
एक और अच्छा (जीएनयू-मात्र) की सुविधा है xargs -d "\n", ताकि आप जैसे चला सकते हैं locate PATTERN1 |xargs -d "\n" grep PATTERN2फ़ाइल मिलान नाम के लिए खोज करने के लिए PATTERN1 सामग्री मिलान के साथ PATTERN2 । GNU के बिना, आप इसे कर सकते हैं जैसेlocate PATTERN1 |perl -pne 's/\n/\0/' |xargs -0 grep PATTERN1
एडम काट्ज़

26

जबकि गाइल्स का जवाब उत्कृष्ट है, मैं उनके मुख्य बिंदु पर मुद्दा लेता हूं

हमेशा चर प्रतिस्थापन और कमांड प्रतिस्थापन के आसपास दोहरे उद्धरण चिह्नों का उपयोग करें: "$ फू", "$ (फू)"

जब आप एक बैश-जैसे शेल के साथ शुरू कर रहे हैं जो शब्द को विभाजित करता है, तो बेशक सुरक्षित सलाह हमेशा उद्धरण का उपयोग करें। हालाँकि शब्द विभाजन हमेशा नहीं किया जाता है

§ वर्ड स्प्लिटिंग

ये कमांड बिना किसी त्रुटि के चलाए जा सकते हैं

foo=$bar
bar=$(a command)
logfile=$logdir/foo-$(date +%Y%m%d)
PATH=/usr/local/bin:$PATH ./myscript
case $foo in bar) echo bar ;; baz) echo baz ;; esac

मैं उपयोगकर्ताओं को इस व्यवहार को अपनाने के लिए प्रोत्साहित नहीं कर रहा हूं, लेकिन यदि कोई व्यक्ति यह समझता है कि शब्द बंटवारा कब होता है, तो उन्हें उद्धरण का उपयोग करते समय खुद के लिए निर्णय लेने में सक्षम होना चाहिए।


19
जैसा कि मैंने अपने उत्तर में उल्लेख किया है, विवरण के लिए unix.stackexchange.com/questions/68694/… देखें। प्रश्न पर ध्यान दें - "मेरी शेल स्क्रिप्ट चोक क्यों है?"। सबसे आम समस्या (इस साइट और अन्य जगहों पर वर्षों के अनुभव से) दोहरे उद्धरण चिह्नों को याद कर रही है। "हमेशा दोहरे उद्धरण चिह्नों का उपयोग करें" की तुलना में "हमेशा दोहरे उद्धरण चिह्नों का उपयोग करना आसान होता है, इन मामलों को छोड़कर, जहां वे आवश्यक नहीं हैं"।
गाइल्स

14
शुरुआती लोगों के लिए नियमों को समझना मुश्किल है। उदाहरण के लिए, foo=$barठीक है, लेकिन export foo=$barया env foo=$var(कम से कम कुछ गोले में) नहीं हैं। शुरुआत के लिए एक सलाह: हमेशा अपने चर को तब तक उद्धृत करें जब तक आपको पता न हो कि आप क्या कर रहे हैं और आपके पास एक अच्छा कारण है
स्टीफन चेज़लस

5
@StevenPenny क्या यह वास्तव में अधिक सही है? क्या ऐसे उचित मामले हैं जहां उद्धरण स्क्रिप्ट को तोड़ देंगे? ऐसी स्थितियों में जहां आधे मामलों में उद्धरण का उपयोग किया जाना चाहिए, और अन्य आधे उद्धरणों में वैकल्पिक रूप से उपयोग किया जा सकता है - फिर एक सिफारिश "हमेशा उद्धरण का उपयोग करें, बस मामले में" वह है जिसे सोचा जाना चाहिए, क्योंकि यह सही, सरल और कम जोखिम भरा है। शुरुआती लोगों को अपवादों की ऐसी सूचियों को पढ़ाना अच्छी तरह से अप्रभावी (संदर्भ का अभाव है, वे उन्हें याद नहीं करेंगे) और प्रतिशोधी, क्योंकि वे आवश्यक / गैर-उद्धृत उद्धरणों को भ्रमित करेंगे, उनकी स्क्रिप्ट्स को तोड़ेंगे और उन्हें आगे जानने के लिए demotivating करेंगे।
पीटरिस

6
मेरा $ 0.02 होगा कि सब कुछ उद्धृत करने की सिफारिश करना अच्छी सलाह है। गलती से किसी ऐसी चीज को उद्धृत करना जिसकी उसे जरूरत नहीं है, वह हानिरहित है, गलती से किसी चीज को उद्धृत करने में असफल होना, जिसकी जरूरत है वह हानिकारक है। तो, शेल स्क्रिप्ट लेखकों के बहुमत के लिए जो कभी भी शब्द विभाजन के समय होने वाली पेचीदगियों को नहीं समझेंगे, सब कुछ उद्धृत करना केवल जहां आवश्यक हो, वहां उद्धृत करने की तुलना में अधिक सुरक्षित है।
गॉडलीजेक

5
@Peteris और Godlygeek: "क्या ऐसे उचित मामले हैं जहां उद्धरण स्क्रिप्ट को तोड़ देंगे?" यह "उचित" की आपकी परिभाषा पर निर्भर करता है। यदि कोई स्क्रिप्ट सेट करता है criteria="-type f", तो find . $criteriaकाम करता है लेकिन find . "$criteria"नहीं।
जी-मैन

22

जहां तक ​​मुझे पता है, केवल दो मामले हैं जिनमें दो-उद्धरण विस्तार करना आवश्यक है, और उन मामलों में दो विशेष शेल पैरामीटर शामिल हैं "$@"और "$*"- जो दोहरे उद्धरणों में संलग्न होने पर अलग-अलग विस्तार करने के लिए निर्दिष्ट हैं। अन्य सभी मामलों में (विस्तार, शायद, शेल-विशिष्ट सरणी कार्यान्वयन) एक विस्तार का व्यवहार एक विन्यास योग्य चीज है - इसके लिए विकल्प हैं।

यह कहना नहीं है, निश्चित रूप से, कि दोहरे-उद्धरण से बचा जाना चाहिए - इसके विपरीत, यह संभवतः एक विस्तार को परिसीमन करने का सबसे सुविधाजनक और मजबूत तरीका है जो शेल को पेश करना है। लेकिन, मुझे लगता है कि विकल्प के रूप में पहले ही एक्सप्लॉइट हो चुके हैं, इस बात पर चर्चा करने के लिए यह एक शानदार जगह है कि जब शेल किसी वैल्यू का विस्तार करता है तो क्या होता है।

खोल, इसके दिल और आत्मा में (जिनके पास ऐसा है) , एक कमांड-इंटरप्रेटर है - यह एक बड़ा, इंटरैक्टिव की तरह एक पार्सर है sed। अपने खोल बयान है, तो घुट पर खाली स्थान के या इसी तरह तो यह है क्योंकि आप पूरी तरह से खोल की व्याख्या की प्रक्रिया से नहीं समझा गया है बहुत संभावना है - विशेष रूप से कैसे और क्यों यह एक क्रियान्वित आदेश के लिए एक इनपुट बयान अनुवाद करता है। शेल का काम है:

  1. इनपुट स्वीकार करें

  2. व्याख्या और इसे सही ढंग से टोकन इनपुट शब्दों में विभाजित करें

    • इनपुट शब्द शेल सिंटैक्स आइटम हैं जैसे $wordयाecho $words 3 4* 5

    • शब्द हमेशा व्हाट्सएप पर विभाजित होते हैं - यह सिर्फ वाक्यविन्यास है - लेकिन केवल शाब्दिक व्हॉट्सएप वर्ण इसकी इनपुट फ़ाइल में शेल को दिए गए हैं

  3. यदि आवश्यक हो तो कई क्षेत्रों में विस्तार करें

    • फ़ील्ड शब्द विस्तार से परिणाम - वे अंतिम निष्पादन योग्य कमांड बनाते हैं

    • छोड़कर "$@", $IFS फ़ील्ड-विभाजन , और pathname विस्तार एक इनपुट शब्द हमेशा एक क्षेत्र के लिए मूल्यांकन करना चाहिए ।

  4. और फिर परिणामी कमांड को निष्पादित करने के लिए

    • ज्यादातर मामलों में इसमें किसी न किसी रूप में इसकी व्याख्या के परिणामों को पारित करना शामिल है

लोग अक्सर कहते हैं कि शेल एक गोंद है , और, अगर यह सच है, तो यह जो चिपका है वह तर्कों की सूची है - या फ़ील्ड - एक प्रक्रिया या किसी अन्य के लिए जब यह execउन्हें होता है। अधिकांश गोले NULबाइट को अच्छी तरह से नहीं संभालते हैं - यदि बिल्कुल भी - और इसका कारण यह है कि वे पहले से ही इस पर विभाजित कर रहे हैं। शेल में exec बहुत कुछ है और इसे NULतर्कों के एक सीमांकित सरणी के साथ करना होगा जो कि execसमय पर सिस्टम कर्नेल को सौंपता है । यदि आप शेल के सीमांकक को उसके सीमांकित डेटा के साथ जोड़ रहे थे, तो शेल संभवतः इसे खराब कर देगा। इसकी आंतरिक डेटा संरचनाएं - अधिकांश कार्यक्रमों की तरह - उस सीमांकक पर निर्भर करती हैं। zsh, विशेष रूप से, यह पेंच नहीं है।

और वह जहां $IFSआता $IFSहै, वह हमेशा मौजूद होता है - और इसी तरह से सेटेल - शेल पैरामीटर जो यह परिभाषित करता है कि शेल को शेल एक्सपेंशन को शब्द से फ़ील्ड में कैसे विभाजित करना चाहिए - विशेष रूप से उन फ़ील्ड्स को किन मूल्यों पर परिसीमन करना चाहिए। $IFSअलग-अलग NULशब्दों में, शेल के विस्तार पर शेल के विस्तार को विभाजित करता है - या, दूसरे शब्दों में शेल के विकल्प बाइट्स के विस्तार के परिणामस्वरूप होते हैं, जो इसके आंतरिक डेटा-सरणियों में मूल्य के $IFSसाथ मेल खाते हैं NUL। जब आप इसे इस तरह से देखते हैं, तो आप यह देखना शुरू कर सकते हैं कि हर क्षेत्र-विभाजन शेल विस्तार एक $IFS-delimited डेटा सरणी है।

यह समझना महत्वपूर्ण है कि $IFSकेवल ऐसे परिसीमन का विस्तार होता है जो पहले से अन्यथा सीमांकित नहीं हैं - जिसे आप "दोहरे उद्धरणों के साथ कर सकते हैं । जब आप एक विस्तार को उद्धृत करते हैं तो आप इसे सिर पर और कम से कम इसके मूल्य की पूंछ पर परिसीमन करते हैं । उन मामलों में $IFSलागू नहीं होता है क्योंकि अलग करने के लिए कोई फ़ील्ड नहीं हैं। वास्तव में, एक डबल-कोटेड एक्सपोज़र एक खाली मान पर सेट होने पर एक अनकॉनेटेड एक्सटेंशन के समान फ़ील्ड-विभाजन व्यवहार प्रदर्शित करता है IFS=

जब तक उद्धृत नहीं किया जाता, $IFSअपने आप में एक $IFSसीमांकित खोल विस्तार है। यह एक विशिष्ट मान के लिए चूक करता है <space><tab><newline>- जिनमें से तीन विशेष गुणों को प्रदर्शित करते हैं जब भीतर निहित होते हैं $IFS। किसी अन्य मूल्य जबकि के लिए $IFSएक भी करने के लिए मूल्यांकन करने के लिए निर्दिष्ट किया जाता है क्षेत्र विस्तार प्रति घटना , $IFS खाली स्थान के - उन तीन में से किसी - विस्तार प्रति एक भी क्षेत्र के लिए छिपाना करने के लिए निर्दिष्ट किया जाता है अनुक्रम और प्रमुख / अनुगामी दृश्यों पूरी तरह elided कर रहे हैं। उदाहरण के माध्यम से समझना शायद सबसे आसान है।

slashes=///// spaces='     '
IFS=/; printf '<%s>' $slashes$spaces
<><><><><><     >
IFS=' '; printf '<%s>' $slashes$spaces
</////>
IFS=; printf '<%s>' $slashes$spaces
</////     >
unset IFS; printf '<%s>' "$slashes$spaces"
</////     >

लेकिन यह सिर्फ $IFS- केवल शब्द-बंटवारे या व्हाट्सएप के रूप में पूछा गया है, तो विशेष वर्णों का क्या?

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

set -f

यह शेल को ग्लोब करने का निर्देश देता है । Pathname का विस्तार कम से कम तब तक नहीं होगा जब तक कि सेटिंग किसी भी तरह से पूर्ववत नहीं हो जाती है - जैसे कि वर्तमान शेल को किसी अन्य नए शेल प्रक्रिया से बदल दिया जाता है या ...।

set +f

... शेल को जारी किया जाता है। डबल-कोट्स - जैसा कि वे $IFS फील्ड-स्प्लिटिंग के लिए भी करते हैं - इस वैश्विक सेटिंग को प्रति एक्सटेंशन अनावश्यक रूप से रेंडर करते हैं। इसलिए:

echo "*" *

... यदि पथनाम विस्तार वर्तमान में सक्षम है, तो संभवतः प्रति तर्क के अनुसार बहुत भिन्न परिणाम उत्पन्न होंगे - जैसा कि पहले केवल उसके शाब्दिक मूल्य (एकल तारांकन वर्ण, जो कहना है, बिल्कुल नहीं) तक विस्तारित होगा और दूसरा केवल उसी के लिए यदि वर्तमान वर्किंग डायरेक्टरी में कोई फ़ाइल नाम नहीं है, जो मेल कर सकता है (और यह उन सभी से मेल खाता है) । हालाँकि यदि आप करते हैं:

set -f; echo "*" *

... दोनों तर्कों के लिए परिणाम समान हैं - *उस मामले में विस्तार नहीं करता है।


मैं वास्तव में @ StéphaneChazelas से सहमत हूं कि यह (ज्यादातर) मदद करने से ज्यादा चीजों को भ्रमित करता है ... लेकिन मुझे यह मददगार लगा, व्यक्तिगत रूप से, इसलिए मैंने उत्थान किया। अब मेरे पास बेहतर विचार (और कुछ उदाहरण) हैं कि IFSवास्तव में कैसे काम करता है। क्या मैं नहीं है पाने के कारण है कि यह होता है कभी स्थापित करने के लिए एक अच्छा विचार हो IFSडिफ़ॉल्ट के अलावा कुछ करने के लिए।
वाइल्डकार्ड

1
@Wildcard - यह एक क्षेत्र परिसीमन है। यदि आपके पास एक चर में मूल्य है जिसे आप कई क्षेत्रों में विस्तारित करना चाहते हैं जिसे आप इसे विभाजित करते हैं $IFScd /usr/bin; set -f; IFS=/; for path_component in $PWD; do echo $path_component; doneप्रिंट \nतब usr\nतो bin\n। पहला echoखाली है क्योंकि /एक अशक्त क्षेत्र है। पाथ_कंप्यूटरों में न्यूलाइन्स या स्पेस या जो कुछ भी हो सकता है - कोई फर्क नहीं पड़ता क्योंकि घटकों को /डिफ़ॉल्ट मूल्य पर विभाजित नहीं किया गया था। लोग इसे awkवैसे भी w / हर समय करते हैं। आपका शेल भी ऐसा करता है
mikeserv

3

मेरे पास फ़ाइल नाम में रिक्त स्थान और निर्देशिका नामों में रिक्त स्थान के साथ एक बड़ी वीडियो परियोजना थी। जबकि find -type f -print0 | xargs -0कई उद्देश्यों के लिए और विभिन्न गोले भर में काम करता है, मुझे लगता है कि यदि आप बैश का उपयोग कर रहे हैं तो एक कस्टम IFS (इनपुट क्षेत्र विभाजक) आपको अधिक लचीलापन देता है। नीचे का स्निपेट बैश का उपयोग करता है और IFS को सिर्फ एक नई रेखा पर सेट करता है; बशर्ते आपके फ़ाइलनाम में नए लिंक नहीं हैं:

(IFS=$'\n'; for i in $(find -type f -print) ; do
    echo ">>>$i<<<"
done)

IFS के पुनर्परिभाषित को अलग करने के लिए परेंस के उपयोग पर ध्यान दें। मैंने IFS को पुनर्प्राप्त करने के तरीके के बारे में अन्य पोस्ट पढ़े हैं, लेकिन यह सिर्फ आसान है।

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

V=""
V="./Ralphie's Camcorder/STREAM/00123.MTS,04:58,05:52,-vf yadif"
V="$V"$'\n'"./Ralphie's Camcorder/STREAM/00111.MTS,00:00,59:59,-vf yadif"
V="$V"$'\n'"next item goes here..."

और इसके बाद:

(IFS=$'\n'; for v in $V ; do
    echo ">>>$v<<<"
done)

अब मैं नई सूचियों के echo "$V"उत्पादन के लिए दोहरे उद्धरण चिह्नों का उपयोग करके वी की सेटिंग को "सूची" कर सकता हूं । ( स्पष्टीकरण के लिए इस सूत्र को श्रेय $'\n'।)


3
लेकिन फिर भी आपको नई नाम या ग्लोब वर्ण वाले फ़ाइल नाम की समस्याएँ होंगी। इसे भी देखें: लूपिंग क्यों है आउटपुट के खराब होने का पता? । यदि उपयोग किया जाता है zsh, तो आप उपयोग कर सकते हैं IFS=$'\0'और उपयोग कर सकते हैं -print0( zshविस्तार पर ग्लोबिंग नहीं करते हैं ताकि ग्लोब वर्ण वहां कोई समस्या न हो)।
स्टीफन चेज़लस

1
यह रिक्त स्थान वाले फ़ाइल नामों के साथ काम करता है, लेकिन यह संभावित शत्रुतापूर्ण फ़ाइल नामों या आकस्मिक "निरर्थक" फ़ाइल नामों के खिलाफ काम नहीं करता है। आप आसानी से जोड़कर वाइल्डकार्ड वर्णों वाले फ़ाइल नामों की समस्या को ठीक कर सकते हैं set -f। दूसरी ओर, आपका दृष्टिकोण मौलिक रूप से फ़ाइल नाम के साथ विफल रहता है जिसमें नई सूचियाँ होती हैं। फ़ाइल नामों के अलावा अन्य डेटा के साथ काम करते समय, यह खाली वस्तुओं के साथ भी विफल हो जाता है।
गाइल्स

ठीक है, मेरा चेतावनी यह है कि यह फ़ाइल नाम में नई सूचियों के साथ काम नहीं करेगा। हालांकि, मेरा मानना ​​है कि हमें पागलपन के लिए सिर्फ लकीर खींचनी है ;-)
Russ

और मुझे यकीन नहीं है कि यह एक गिरावट क्यों मिली। यह रिक्त स्थान के साथ फाइलनाम पर पुनरावृत्ति करने के लिए एक पूरी तरह से उचित तरीका है। प्रयोग -प्रिंट0 के लिए xargs की आवश्यकता होती है, और ऐसी चीजें हैं जो उस श्रृंखला का उपयोग करना मुश्किल हैं। मुझे खेद है कि कोई मेरे उत्तर से सहमत नहीं है, लेकिन इसका कोई कारण नहीं है।
रस

0

ऊपर उल्लिखित सभी सुरक्षा निहितार्थों को ध्यान में रखते हुए और आप पर विश्वास करते हुए और चर पर नियंत्रण करने से आपके विस्तार में व्हाट्सएप के साथ कई रास्तों का उपयोग संभव है eval। लेकिन सावधान रहना!

$ FILES='"a b" c'
$ eval ls $FILES
ls: a b: No such file or directory
ls: c: No such file or directory
$ FILES='a\ b c'
$ eval ls $FILES
ls: a b: No such file or directory
ls: c: No such file or directory
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.