हमेशा चर प्रतिस्थापन और आदेश प्रतिस्थापन को दोहरे उद्धरण चिह्नों का उपयोग करें: "$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/file1
। eval
निर्मित खोल बताता स्ट्रिंग के रूप में अगर यह स्क्रिप्ट में छपी एक तर्क के रूप में पारित पार्स करने के लिए है, तो उस बिंदु पर उद्धरण और पाइप पार्स कर रहे हैं, आदि
प्रयोग 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 {} +
मेरा कुछ और सवाल है
इस साइट, या शेल या शेल-स्क्रिप्ट पर उद्धृत टैग ब्राउज़ करें । (कुछ सामान्य युक्तियों और सामान्य प्रश्नों की एक हाथ से चयनित सूची देखने के लिए "और जानें ..." पर क्लिक करें।) यदि आपने खोज की है और आप कोई उत्तर नहीं खोज सकते हैं, तो पूछें ।
shellcheck
आपको अपने कार्यक्रमों की गुणवत्ता में सुधार करने में मदद करता है।