इस प्रश्न के सभी उत्तर एक या दूसरे तरीके से गलत हैं।
गलत उत्तर # 1
IFS=', ' read -r -a array <<< "$string"
1: यह एक दुरुपयोग है $IFS
। $IFS
चर का मान एकल चर-लंबाई स्ट्रिंग विभाजक के रूप में नहीं लिया जाता है , बल्कि इसे एकल-वर्ण स्ट्रिंग विभाजक के सेट के रूप में लिया जाता है , जहां इनपुट लाइन से अलग होने वाले प्रत्येक क्षेत्र को सेट में किसी भी वर्ण द्वारा समाप्त किया जा सकता है (अल्पविराम या स्थान, इस उदाहरण में)।read
वास्तव में, वहाँ असली स्टिकर्स के लिए, का पूरा अर्थ $IFS
थोड़ा अधिक शामिल है। से बैश मैनुअल :
शेल आईएफएस के प्रत्येक चरित्र को एक सीमांकक के रूप में मानता है, और अन्य वर्णों के परिणामों को शब्द के रूप में इन वर्णों को फ़ील्ड टर्मिनेटर के रूप में विभाजित करता है। यदि IFS परेशान है, या उसका मान बिल्कुल <space> <tab> <newline> है , तो डिफ़ॉल्ट, फिर <space> , <tab> , और <newline> का क्रम पिछले विस्तार के परिणामों के आरंभ और अंत में है नजरअंदाज कर दिया जाता है, और शुरुआत या अंत में नहीं IFS वर्णों के किसी भी क्रम को शब्दों को सीमांकित करने का कार्य करता है। यदि आईएफएस में डिफ़ॉल्ट के अलावा कोई मूल्य है, तो व्हाट्सएप वर्णों के अनुक्रम <space> , <tab> , और <शब्द की शुरुआत और अंत में नजरअंदाज कर दिया जाता है, जब तक कि व्हाट्सएप चरित्र IFS (एक IFS व्हाट्सएप चरित्र) के मूल्य में नहीं होता है । में किसी भी चरित्र आईएफएस कि नहीं है आईएफएस खाली स्थान के लिए, किसी भी आसन्न के साथ-साथ भारतीय विदेश सेवा खाली स्थान के वर्ण, एक क्षेत्र delimits। IFS व्हॉट्सएप पात्रों का एक अनुक्रम भी एक सीमांकक के रूप में माना जाता है। यदि IFS का मान शून्य है, तो कोई शब्द विभाजन नहीं होता है।
मूल रूप से, के गैर-डिफ़ॉल्ट गैर शून्य मान के लिए $IFS
, क्षेत्रों के साथ अलग किया जा सकता है या तो (1) एक या अधिक वर्ण है कि सभी "आईएफएस खाली स्थान के पात्रों" का सेट है कि (से कर रहे हैं के एक दृश्य, की जो भी <अंतरिक्ष> , <tab> , और <newline> ("newline" अर्थ लाइन फीड (LF) ) कहीं भी मौजूद हैं $IFS
), या (2) कोई भी गैर- "IFS व्हॉट्सएप चरित्र" जो कि "IFS व्हॉट्सएप वर्ण" $IFS
के साथ मौजूद है, उसे घेर लेते हैं इनपुट लाइन में।
ओपी के लिए, यह संभव है कि मैंने पिछले पैराग्राफ में जो दूसरा सेपरेशन मोड बताया था, वह ठीक उसी तरह का हो, जैसा वह अपने इनपुट स्ट्रिंग के लिए चाहता है, लेकिन हम इस बात से काफी आश्वस्त हो सकते हैं कि मैंने जो पहला सेपरेशन मोड बताया था, वह बिल्कुल भी सही नहीं है। उदाहरण के लिए, क्या होगा यदि उसका इनपुट स्ट्रिंग था 'Los Angeles, United States, North America'
?
IFS=', ' read -ra a <<<'Los Angeles, United States, North America'; declare -p a;
## declare -a a=([0]="Los" [1]="Angeles" [2]="United" [3]="States" [4]="North" [5]="America")
2: आप (जैसे अपने आप में एक अल्पविराम, कि कोई भी निम्नलिखित अंतरिक्ष या अन्य सामान के साथ है, के रूप में) एक एकल चरित्र विभाजक के साथ इस समाधान का उपयोग करने के लिए करता है, तो के मूल्य के थे यहां तक कि अगर $string
किसी भी LFS को रोकने के लिए चर होता है, तो read
होगा पहले LF का सामना करने के बाद प्रोसेसिंग बंद करें। read
Builtin केवल मंगलाचरण प्रति एक लाइन संसाधित करता है। यह तब भी सही है जब आप केवलread
कथन के लिए इनपुट या पुनर्निर्देशन कर रहे हों, जैसा कि हम यहां इस तंत्र में कर रहे हैं , और इस प्रकार अप्रमाणित इनपुट खो जाने की गारंटी है। कोड जो पावर को read
निर्मित करता है उसे कमांड संरचना वाले डेटा प्रवाह का कोई ज्ञान नहीं होता है।
आप तर्क दे सकते हैं कि यह एक समस्या पैदा करने की संभावना नहीं है, लेकिन फिर भी, यह एक सूक्ष्म खतरा है जिसे यदि संभव हो तो टाला जाना चाहिए। यह इस तथ्य के कारण होता है कि read
बिल्टिन वास्तव में इनपुट विभाजन के दो स्तर करता है: पहले लाइनों में, फिर खेतों में। चूंकि ओपी केवल विभाजन का एक स्तर चाहता है, इसलिए read
बिलिन का यह उपयोग उचित नहीं है, और हमें इससे बचना चाहिए।
3: इस समाधान के साथ एक गैर-स्पष्ट संभावित मुद्दा यह है कि read
यदि यह खाली है, तो हमेशा पीछे चल रहे क्षेत्र को गिरा देता है, हालांकि यह खाली क्षेत्रों को संरक्षित करता है अन्यथा। यहाँ एक डेमो है:
string=', , a, , b, c, , , '; IFS=', ' read -ra a <<<"$string"; declare -p a;
## declare -a a=([0]="" [1]="" [2]="a" [3]="" [4]="b" [5]="c" [6]="" [7]="")
शायद ओपी इस बारे में परवाह नहीं करेगा, लेकिन इसके बारे में जानने के लायक अभी भी एक सीमा है। यह समाधान की मजबूती और व्यापकता को कम करता है।
यह समस्या एक डमी ट्रेलिंग सीमांकक को इनपुट स्ट्रिंग से जोड़कर हल की जा सकती है read
, इसे पहले खिलाने के लिए , जैसा कि मैं बाद में प्रदर्शित करूंगा।
गलत उत्तर # 2
string="1:2:3:4:5"
set -f # avoid globbing (expansion of *).
array=(${string//:/ })
समान विचार:
t="one,two,three"
a=($(echo $t | tr ',' "\n"))
(नोट: मैंने कमांड प्रतिस्थापन के आसपास लापता कोष्ठकों को जोड़ा है जो उत्तर देने वाले को छोड़ दिया गया लगता है।)
समान विचार:
string="1,2,3,4"
array=(`echo $string | sed 's/,/\n/g'`)
ये समाधान स्ट्रिंग असाइनमेंट को फ़ील्ड में विभाजित करने के लिए एक सरणी असाइनमेंट में विभाजित शब्द का लाभ उठाते हैं। मजेदार रूप से पर्याप्त है, ठीक वैसे ही read
, सामान्य शब्द विभाजन भी $IFS
विशेष चर का उपयोग करता है , हालांकि इस मामले में यह निहित है कि यह इसके डिफ़ॉल्ट मान पर सेट है <space> <टैब> <newline> , और इसलिए एक या एक से अधिक IFS के किसी भी अनुक्रम वर्ण (जो अब सभी व्हाट्सएप पात्र हैं) को क्षेत्र परिसीमन माना जाता है।
यह विभाजन के दो स्तरों की समस्या को हल करता है read
, क्योंकि शब्द विभाजन अपने आप में विभाजन के केवल एक स्तर का गठन करता है। लेकिन पहले की तरह, यहां समस्या यह है कि इनपुट स्ट्रिंग में अलग-अलग फ़ील्ड में पहले से ही $IFS
अक्षर हो सकते हैं , और इस प्रकार वे शब्द विभाजन ऑपरेशन के दौरान अनुचित रूप से विभाजित हो जाएंगे। ऐसा इन उत्तरदाताओं द्वारा प्रदान किए गए किसी भी नमूना इनपुट स्ट्रिंग्स के मामले में नहीं होता है (कितना सुविधाजनक ...), लेकिन निश्चित रूप से इस तथ्य को नहीं बदलता है कि इस मुहावरे का उपयोग करने वाला कोई भी कोड आधार फिर जोखिम उठाएगा यदि इस धारणा का उल्लंघन कभी लाइन के नीचे किसी बिंदु पर किया गया हो तो उड़ाना। एक बार फिर, मेरे 'Los Angeles, United States, North America'
(या 'Los Angeles:United States:North America'
) के प्रतिरूप पर विचार करें ।
इसके अलावा, शब्द बंटवारे सामान्य रूप से द्वारा पीछा किया जाता फ़ाइल नाम विस्तार ( उर्फ पथ विस्तार उर्फ , जो, अगर किया, वर्ण युक्त संभवतः भ्रष्ट शब्द हैं ग्लोबिंग) *
, ?
या [
के बाद ]
(और, यदि extglob
सेट किया गया है, parenthesized टुकड़े से पहले ?
, *
, +
, @
, या !
) फ़ाइल सिस्टम ऑब्जेक्ट्स के खिलाफ उन्हें मेल करके और तदनुसार शब्द ("ग्लब्स") का विस्तार करना। इन तीन उत्तरदाताओं में से पहले ने चालाकी set -f
से ग्लोबिंग को अक्षम करने के लिए पहले से ही इस समस्या को कम कर दिया है । तकनीकी रूप से यह काम करता है (हालांकि आपको शायद जोड़ना चाहिएset +f
बाद के कोड के लिए फिर से चमकाने योग्य ग्लोबबिंग जो इस पर निर्भर हो सकता है), लेकिन स्थानीय कोड में एक बुनियादी स्ट्रिंग-टू-सरणी पार्सिंग ऑपरेशन को हैक करने के लिए वैश्विक शेल सेटिंग्स के साथ गड़बड़ करना अवांछनीय है।
इस उत्तर के साथ एक और मुद्दा यह है कि सभी खाली फ़ील्ड खो जाएंगे। आवेदन के आधार पर यह समस्या हो सकती है या नहीं भी हो सकती है।
नोट: यदि आप इस समाधान का उपयोग करने जा रहे हैं, तो कमांड प्रतिस्थापन (जो कि शेल की तलाश करता है), एक पाइपलाइन को शुरू करने की मुसीबत में जाने के बजाय, पैरामीटर विस्तार के ${string//:/ }
"पैटर्न प्रतिस्थापन" रूप का उपयोग करना बेहतर है , और बाहरी निष्पादन योग्य ( या ) चल रहा है , क्योंकि पैरामीटर विस्तार विशुद्ध रूप से एक शेल-आंतरिक ऑपरेशन है। (इसके अलावा, और समाधान के लिए, इनपुट चर को कमांड प्रतिस्थापन के अंदर डबल-उद्धृत किया जाना चाहिए; अन्यथा शब्द विभाजन कमांड में प्रभावी होगा और संभावित रूप से फ़ील्ड मान के साथ गड़बड़ करेगा। इसके अलावा, कमांड प्रतिस्थापन का रूप पुराने के लिए बेहतर है।tr
sed
tr
sed
echo
$(...)
`...`
फॉर्म चूंकि यह कमांड प्रतिस्थापन के घोंसले के शिकार को सरल करता है और पाठ संपादकों द्वारा बेहतर वाक्य रचना हाइलाइटिंग के लिए अनुमति देता है।)
गलत उत्तर # 3
str="a, b, c, d" # assuming there is a space after ',' as in Q
arr=(${str//,/}) # delete all occurrences of ','
यह उत्तर लगभग # 2 जैसा ही है । अंतर यह है कि उत्तरदाता ने यह धारणा बनाई है कि फ़ील्ड दो वर्णों द्वारा सीमांकित हैं, जिनमें से एक को डिफ़ॉल्ट में दर्शाया जा रहा है $IFS
, और दूसरा नहीं। उन्होंने गैर-आईएफएस-प्रतिनिधित्व वाले चरित्र को हटाकर एक पैटर्न प्रतिस्थापन विस्तार का उपयोग करके और फिर बचे हुए आईएफएस-प्रतिनिधित्व वाले सीमांत चरित्र पर फ़ील्ड को विभाजित करने के लिए शब्द विभाजन का उपयोग करके इसे हल किया है।
यह बहुत सामान्य समाधान नहीं है। इसके अलावा, यह तर्क दिया जा सकता है कि अल्पविराम वास्तव में "प्राथमिक" सीमांकक चरित्र है यहां, और यह कि इसे अलग करना और फिर क्षेत्र विभाजन के लिए अंतरिक्ष चरित्र के आधार पर बस गलत है। एक बार फिर, मेरे प्रतिवाद पर विचार करें 'Los Angeles, United States, North America'
:।
इसके अलावा, फिर से, फ़ाइल नाम विस्तार से विस्तारित शब्दों को दूषित कर सकता है, लेकिन इसके साथ set -f
और फिर असाइनमेंट के लिए अस्थायी रूप से अक्षम करने से रोका जा सकता है set +f
।
इसके अलावा, फिर से, सभी खाली फ़ील्ड खो जाएंगे, जो कि आवेदन के आधार पर समस्या हो सकती है या नहीं।
गलत उत्तर # 4
string='first line
second line
third line'
oldIFS="$IFS"
IFS='
'
IFS=${IFS:0:1} # this is useful to format your code with tabs
lines=( $string )
IFS="$oldIFS"
यह # 2 और # 3 के समान है जिसमें यह काम करने के लिए शब्द बंटवारे का उपयोग करता है, केवल अब कोड स्पष्ट रूप $IFS
से इनपुट स्ट्रिंग में मौजूद केवल एकल-वर्ण फ़ील्ड सीमांकक को सेट करता है। यह दोहराया जाना चाहिए कि यह ओपी के कॉमा-स्पेस सीमांकक जैसे मल्टीचैकर फील्ड डेलिमिटर के लिए काम नहीं कर सकता है। लेकिन इस उदाहरण में उपयोग किए जाने वाले एलएफ जैसे एकल-चरित्र परिसीमन के लिए, यह वास्तव में परिपूर्ण होने के करीब आता है। खेतों को बीच में अनजाने में विभाजित नहीं किया जा सकता है जैसा कि हमने पिछले गलत उत्तरों के साथ देखा था, और आवश्यकतानुसार विभाजन का केवल एक स्तर है।
एक समस्या यह है कि फ़ाइल नाम विस्तार, पहले वर्णित के रूप में भ्रष्ट प्रभावित शब्द जाएगा, हालांकि एक बार फिर से इस में महत्वपूर्ण बयान लपेटकर द्वारा हल किया जा सकता है set -f
और set +f
।
एक अन्य संभावित समस्या यह है कि, चूंकि LF एक "IFS व्हाट्सएप चरित्र" के रूप में अर्हता प्राप्त करता है, जैसा कि पहले परिभाषित किया गया था, सभी खाली फ़ील्ड खो जाएंगे, जैसे # 2 और # 3 में । यह निश्चित रूप से एक समस्या नहीं होगी यदि सीमांकक एक गैर "IFS व्हाट्सएप चरित्र" होता है, और आवेदन के आधार पर यह वैसे भी मायने नहीं रखता है, लेकिन यह समाधान की व्यापकता को मिटा देता है।
तो, योग करने के लिए, यह सोचते हैं आप एक चरित्र सीमांकक है, और इसे या तो एक गैर- "आईएफएस खाली स्थान के चरित्र" है या आप खाली क्षेत्रों के बारे में परवाह नहीं है, और आप में महत्वपूर्ण बयान लपेट set -f
और set +f
है, तो इस समाधान काम करता है , लेकिन अन्यथा नहीं।
(इसके अलावा, जानकारी के लिए, एलएफ़ को एक बैश में वैरिएबल में असाइन करना $'...'
सिंटैक्स, उदाहरण के साथ अधिक आसानी से किया जा सकता है IFS=$'\n';
।)
गलत उत्तर # 5
countries='Paris, France, Europe'
OIFS="$IFS"
IFS=', ' array=($countries)
IFS="$OIFS"
समान विचार:
IFS=', ' eval 'array=($string)'
यह समाधान प्रभावी रूप से # 1 के बीच एक क्रॉस है (इसमें यह $IFS
अल्पविराम-स्थान पर सेट होता है) और # 2-4 (इसमें वह स्ट्रिंग को खेतों में विभाजित करने के लिए शब्द विभाजन का उपयोग करता है)। इस वजह से, यह उन सभी समस्याओं से ग्रस्त है जो उपरोक्त सभी गलत उत्तरों से पीड़ित हैं, सभी दुनिया के सबसे बुरे लोगों की तरह।
इसके अलावा, दूसरे संस्करण के बारे में, ऐसा लग सकता है कि eval
कॉल पूरी तरह से अनावश्यक है, क्योंकि इसका तर्क एकल-उद्धृत स्ट्रिंग शाब्दिक है, और इसलिए इसे सांख्यिकीय रूप से जाना जाता है। लेकिन eval
इस तरह से उपयोग करने के लिए वास्तव में एक बहुत ही गैर-स्पष्ट लाभ है । आम तौर पर, जब आप एक साधारण कमांड चलाते हैं जिसमें केवल एक वेरिएबल असाइनमेंट होता है , जिसका अर्थ होता है बिना वास्तविक कमांड शब्द का पालन किए, असाइनमेंट शेल वातावरण में प्रभावी होता है:
IFS=', '; ## changes $IFS in the shell environment
यह सरल है भले ही साधारण कमांड में कई चर असाइनमेंट शामिल हों ; फिर से, जब तक कि कोई कमांड शब्द न हो, सभी चर असाइनमेंट शेल वातावरण को प्रभावित करते हैं:
IFS=', ' array=($countries); ## changes both $IFS and $array in the shell environment
लेकिन, यदि चर असाइनमेंट एक कमांड नाम से जुड़ा हुआ है (मैं इसे "उपसर्ग असाइनमेंट" कहना पसंद करता हूं) तो यह शेल वातावरण को प्रभावित नहीं करता है, और इसके बजाय केवल निष्पादित कमांड के वातावरण को प्रभावित करता है, भले ही यह एक अंतर्निहित हो या बाहरी:
IFS=', ' :; ## : is a builtin command, the $IFS assignment does not outlive it
IFS=', ' env; ## env is an external command, the $IFS assignment does not outlive it
बैश मैनुअल से प्रासंगिक बोली :
यदि कोई आदेश नाम परिणाम नहीं है, तो चर असाइनमेंट वर्तमान शेल वातावरण को प्रभावित करते हैं। अन्यथा, चर निष्पादित कमांड के वातावरण में जोड़े जाते हैं और वर्तमान शेल पर्यावरण को प्रभावित नहीं करते हैं।
$IFS
केवल अस्थायी रूप से बदलने के लिए चर असाइनमेंट की इस सुविधा का दोहन करना संभव है , जो हमें इस तरह से पूरे सेव-एंड-रिस्टोर गैंबिट से बचने की अनुमति देता है, जो $OIFS
पहले वेरिएंट में वेरिएबल के साथ किया जा रहा है । लेकिन हमारे सामने चुनौती यह है कि हमें जिस कमांड को चलाने की आवश्यकता है वह स्वयं एक मात्र परिवर्तनशील असाइनमेंट है, और इसलिए यह $IFS
असाइनमेंट को अस्थायी बनाने के लिए कमांड शब्द को शामिल नहीं करेगा । आप खुद सोच सकते हैं, ठीक है कि सिर्फ असाइनमेंट को अस्थायी : builtin
बनाने के लिए स्टेटमेंट में नो-ऑप कमांड शब्द क्यों नहीं जोड़ा गया $IFS
? यह काम नहीं करता है क्योंकि यह तब $array
असाइनमेंट को अस्थायी बना देगा :
IFS=', ' array=($countries) :; ## fails; new $array value never escapes the : command
तो, हम प्रभावी रूप से एक गतिरोध पर, एक पकड़ -२२ का एक सा है। लेकिन, जब eval
इसका कोड चलता है, तो यह शेल वातावरण में इसे चलाता है, जैसे कि यह सामान्य, स्थिर स्रोत कोड था, और इसलिए हम तर्क के $array
अंदर असाइनमेंट को चला सकते हैं, eval
क्योंकि शेल वातावरण में इसका प्रभाव पड़ता है, जबकि $IFS
उपसर्ग असाइनमेंट eval
कमांड के लिए उपसर्ग है कमांड को आउटलिव नहीं करेगा eval
। यह ठीक यही चाल है जिसका उपयोग इस समाधान के दूसरे संस्करण में किया जा रहा है:
IFS=', ' eval 'array=($string)'; ## $IFS does not outlive the eval command, but $array does
इसलिए, जैसा कि आप देख सकते हैं, यह वास्तव में काफी चालाक चाल है, और यह बिल्कुल गैर-स्पष्ट तरीके से आवश्यक है (कम से कम असाइनमेंट प्रभाव के संबंध में)। मैं वास्तव में इस चाल के खिलाफ नहीं हूँ, इसमें शामिल होने के बावजूद eval
; सुरक्षा खतरों से बचाव के लिए तर्क स्ट्रिंग को एकल-उद्धरण के लिए सावधान रहें।
लेकिन फिर से, "सभी दुनिया के सबसे खराब" समस्याओं के ढेर के कारण, यह अभी भी ओपी की आवश्यकता का एक गलत जवाब है।
गलत उत्तर # 6
IFS=', '; array=(Paris, France, Europe)
IFS=' ';declare -a array=(Paris France Europe)
उम्म क्या? ओपी में एक स्ट्रिंग चर है जिसे एक सरणी में पार्स करने की आवश्यकता है। यह "उत्तर" इनपुट स्ट्रिंग की शब्दशः सामग्रियों से शुरू होता है, जो एक सरणी शाब्दिक में चिपकाया जाता है। मुझे लगता है कि ऐसा करने का एक तरीका है।
ऐसा लगता है कि उत्तरदाता ने मान लिया होगा कि $IFS
चर सभी संदर्भों में सभी बैश पार्सिंग को प्रभावित करता है, जो सच नहीं है। बैश मैनुअल से:
IFS आंतरिक क्षेत्र विभाजक जिसका उपयोग विस्तार के बाद शब्द विभाजन के लिए किया जाता है और लाइनों को रीड बिलिन कमांड के साथ शब्दों में विभाजित किया जाता है । डिफ़ॉल्ट मान <space> <टैब> <newline> है ।
तो $IFS
विशेष चर वास्तव में केवल दो संदर्भों में उपयोग किया जाता है: (1) शब्द बंटवारा जो विस्तार के बाद किया जाता है ( बिल्ट स्रोत कोड को पार्स करते समय अर्थ नहीं ) और (2) read
बिलियन द्वारा शब्दों में इनपुट लाइनों को विभाजित करने के लिए ।
मुझे यह स्पष्ट करने की कोशिश करें। मुझे लगता है कि पार्सिंग और निष्पादन के बीच अंतर करना अच्छा होगा । बैश को पहले स्रोत कोड को पार्स करना चाहिए , जो स्पष्ट रूप से एक पार्सिंग घटना है, और फिर बाद में यह कोड निष्पादित करता है, जो कि विस्तार से चित्र में आता है। विस्तार वास्तव में एक निष्पादन घटना है। इसके अलावा, मैं उस $IFS
चर के विवरण के साथ समस्या लेता हूं जिसे मैंने अभी ऊपर उद्धृत किया है; यह कहने के बजाय कि शब्द बंटवारे को विस्तार के बाद किया जाता है , मैं कहूंगा कि शब्द विभाजन को विस्तार के दौरान किया जाता है , या, शायद इससे भी अधिक सटीक रूप से, शब्द विभाजन का हिस्सा हैविस्तार की प्रक्रिया। वाक्यांश "शब्द विभाजन" केवल विस्तार के इस चरण को संदर्भित करता है; इसका उपयोग कभी भी bash source कोड के पार्सिंग को संदर्भित करने के लिए नहीं किया जाना चाहिए, हालांकि दुर्भाग्य से डॉक्स "स्प्लिट" और "वर्ड्स" शब्दों के आसपास फेंकने लगते हैं। यहाँ bash मैनुअल के linux.die.net संस्करण का एक प्रासंगिक अंश दिया गया है :
विस्तार को कमांड लाइन पर शब्दों में विभाजित करने के बाद किया जाता है। सात प्रकार के विस्तार प्रदर्शन किए गए हैं: ब्रेस विस्तार , टिल्ड विस्तार , पैरामीटर और चर विस्तार , कमांड प्रतिस्थापन , अंकगणितीय विस्तार , शब्द विभाजन और पथनाम विस्तार ।
विस्तार का क्रम है: ब्रेस विस्तार; टिल्ड विस्तार, पैरामीटर और चर विस्तार, अंकगणितीय विस्तार, और कमांड प्रतिस्थापन (बाएं से दाएं फैशन में किया गया); शब्द विभाजन; और pathname विस्तार।
आप बहस कर सकते हैं कि मैनुअल का GNU संस्करण थोड़ा बेहतर है, क्योंकि यह विस्तार खंड के पहले वाक्य में "शब्द" के बजाय "टोकन" शब्द का विरोध करता है:
विस्तार को कमांड लाइन पर प्रदर्शन के बाद इसे टोकन में विभाजित किया गया है।
महत्वपूर्ण बिंदु यह है कि, $IFS
bars पार्स स्रोत कोड को बदलने का तरीका नहीं बदलता है। बैश स्रोत कोड का पार्सिंग वास्तव में एक बहुत ही जटिल प्रक्रिया है जिसमें शेल व्याकरण के विभिन्न तत्वों की मान्यता शामिल है, जैसे कि कमांड अनुक्रम, कमांड लिस्ट, पाइपलाइन, पैरामीटर विस्तार, अंकगणितीय प्रतिस्थापन और कमांड प्रतिस्थापन। अधिकांश भाग के लिए, बैश पार्सिंग प्रक्रिया को उपयोगकर्ता के स्तर की कार्रवाइयों जैसे परिवर्तनशील कार्य (वास्तव में, इस नियम में कुछ मामूली अपवाद नहीं हैं) द्वारा बदला जा सकता है; उदाहरण के लिए, विभिन्न compatxx
शेल सेटिंग्स देखें, जो पर-मक्खी के पार्सिंग व्यवहार के कुछ पहलुओं को बदल सकता है)। इस जटिल पार्सिंग प्रक्रिया के परिणामस्वरूप होने वाले अपस्ट्रीम "शब्द" / "टोकन" को "विस्तार" की सामान्य प्रक्रिया के अनुसार विस्तारित किया जाता है, जैसा कि ऊपर दिए गए दस्तावेज़ीकरण अंशों में टूट गया है, जहां डाउनस्ट्रीम में विस्तारित (विस्तार?) पाठ का विभाजन होता है? शब्द उस प्रक्रिया का केवल एक चरण है। शब्द विभाजन केवल उस पाठ को स्पर्श करता है जिसे पूर्ववर्ती विस्तार चरण से बाहर थूक दिया गया है; यह शाब्दिक पाठ को प्रभावित नहीं करता है जो स्रोत बायस्ट्रीम से ठीक पहले पार्स किया गया था।
गलत उत्तर # 7
string='first line
second line
third line'
while read -r line; do lines+=("$line"); done <<<"$string"
यह सबसे अच्छे समाधानों में से एक है। ध्यान दें कि हम वापस उपयोग कर रहे हैं read
। क्या मैंने पहले नहीं कहा कि read
यह अनुचित है क्योंकि यह विभाजन के दो स्तरों को पूरा करता है, जब हमें केवल एक की आवश्यकता होती है? यहां ट्रिक यह है कि आप read
इस तरह से कॉल कर सकते हैं कि यह प्रभावी रूप से केवल एक स्तर के विभाजन को करता है, विशेष रूप से प्रति व्यक्ति केवल एक क्षेत्र को विभाजित करके, जिसे लूप में बार-बार कॉल करने की लागत की आवश्यकता होती है। यह हाथ का एक सा है, लेकिन यह काम करता है।
लेकिन समस्याएं हैं। पहला: जब आप कम से कम एक NAME तर्क प्रदान करते हैं read
, तो यह स्वचालित रूप से इनपुट स्ट्रिंग से विभाजित होने वाले प्रत्येक क्षेत्र में अग्रणी और अनुगामी व्हाट्सएप की उपेक्षा करता है। यह $IFS
इस पोस्ट में पहले बताए अनुसार तयशुदा मान के लिए सेट है या नहीं, यह होता है। अब, ओपी अपने विशिष्ट उपयोग-मामले के लिए इस बारे में परवाह नहीं कर सकता है, और वास्तव में, यह पार्स व्यवहार की एक वांछनीय विशेषता हो सकती है। लेकिन हर कोई जो खेतों में एक तार को पार्स करना चाहता है, वह यह नहीं चाहेगा। एक समाधान है, हालांकि: read
शून्य नाम तर्क को पारित करने के लिए एक गैर-स्पष्ट उपयोग है । इस स्थिति में, read
संपूर्ण इनपुट लाइन को संग्रहीत करेगा जो इसे इनपुट स्ट्रीम से नामांकित चर में मिलती है $REPLY
, और बोनस के रूप में, यह नहीं हैमूल्य से व्हाट्सएप की पट्टी अग्रणी और अनुगामी। यह एक बहुत ही मजबूत उपयोग है read
जिसका मैंने अपने शेल प्रोग्रामिंग कैरियर में अक्सर शोषण किया है। यहाँ व्यवहार में अंतर का प्रदर्शन है:
string=$' a b \n c d \n e f '; ## input string
a=(); while read -r line; do a+=("$line"); done <<<"$string"; declare -p a;
## declare -a a=([0]="a b" [1]="c d" [2]="e f") ## read trimmed surrounding whitespace
a=(); while read -r; do a+=("$REPLY"); done <<<"$string"; declare -p a;
## declare -a a=([0]=" a b " [1]=" c d " [2]=" e f ") ## no trimming
इस समाधान के साथ दूसरा मुद्दा यह है कि यह वास्तव में कस्टम क्षेत्र विभाजक के मामले को संबोधित नहीं करता है, जैसे कि ओपी का अल्पविराम-स्थान। पहले की तरह, मल्टीचैकर विभाजक समर्थित नहीं हैं, जो इस समाधान की एक दुर्भाग्यपूर्ण सीमा है। हम -d
विकल्प को विभाजक निर्दिष्ट करके अल्पविराम पर कम से कम विभाजित करने की कोशिश कर सकते हैं , लेकिन देखो क्या होता है:
string='Paris, France, Europe';
a=(); while read -rd,; do a+=("$REPLY"); done <<<"$string"; declare -p a;
## declare -a a=([0]="Paris" [1]=" France")
मुख्य रूप से, व्हाट्सएप के आस-पास के बेहिसाब क्षेत्र के मूल्यों में खींच लिया गया है, और इसलिए इसे ट्रिमिंग संचालन के माध्यम से बाद में ठीक किया जाना चाहिए (यह सीधे लूप में भी किया जा सकता है)। लेकिन एक और स्पष्ट त्रुटि है: यूरोप गायब है! इसका क्या हुआ? इसका उत्तर यह है कि read
यदि कोई फ़ाइनल-ऑफ-फ़ाइल (इस मामले में हम इसे एंड-ऑफ़-स्ट्रिंग कह सकते हैं) को अंतिम फ़ील्ड पर अंतिम फ़ील्ड टर्मिनेटर से मुठभेड़ किए बिना विफल कर देता है। यह समय-पाश को समय से पहले तोड़ने का कारण बनता है और हम अंतिम क्षेत्र को खो देते हैं।
तकनीकी रूप से इसी त्रुटि ने पिछले उदाहरणों को भी प्रभावित किया; अंतर यह है कि क्षेत्र विभाजक को LF होने के लिए लिया गया था, जो कि डिफ़ॉल्ट है जब आप -d
विकल्प को निर्दिष्ट नहीं करते हैं , और <<<
("यहाँ-स्ट्रिंग") तंत्र स्वचालित रूप से स्ट्रिंग को LF भेजता है इससे पहले कि वह इसे खिलाती है कमांड को इनपुट। इसलिए, उन मामलों में, हम की तरह गलती से हल एक की समस्या अनजाने इनपुट के लिए एक अतिरिक्त डमी टर्मिनेटर जोड़कर अंतिम क्षेत्र गिरा दिया। आइए इस समाधान को "डमी-टर्मिनेटर" समाधान कहते हैं। हम डमी-टर्मिनेटर समाधान को किसी भी कस्टम सीमांकक के लिए मैन्युअल रूप से लागू कर सकते हैं इसे इनपुट स्ट्रिंग के खिलाफ स्वयं करके जब इसे यहां-स्ट्रिंग में त्वरित किया जा सकता है:
a=(); while read -rd,; do a+=("$REPLY"); done <<<"$string,"; declare -p a;
declare -a a=([0]="Paris" [1]=" France" [2]=" Europe")
वहां, समस्या हल हो गई। एक अन्य उपाय केवल लूप को तोड़ना है यदि दोनों (1) read
विफलता लौटे और (2) $REPLY
खाली है, जिसका अर्थ है read
कि किसी भी वर्ण को अंत-फ़ाइल को मारने से पहले पढ़ने में सक्षम नहीं था। डेमो:
a=(); while read -rd,|| [[ -n "$REPLY" ]]; do a+=("$REPLY"); done <<<"$string"; declare -p a;
## declare -a a=([0]="Paris" [1]=" France" [2]=$' Europe\n')
इस दृष्टिकोण से गुप्त एलएफ का भी पता चलता है जो <<<
पुनर्निर्देशन ऑपरेटर द्वारा स्वचालित रूप से यहां-स्ट्रिंग से जुड़ जाता है । यह निश्चित रूप से एक स्पष्ट ट्रिमिंग ऑपरेशन के माध्यम से अलग से छीन लिया जा सकता है जैसा कि एक पल पहले वर्णित है, लेकिन जाहिर है कि मैनुअल डमी-टर्मिनेटर दृष्टिकोण इसे सीधे हल करता है, इसलिए हम बस उसी के साथ जा सकते हैं। मैनुअल डमी-टर्मिनेटर समाधान वास्तव में काफी सुविधाजनक है कि यह एक ही बार में इन दोनों समस्याओं (गिरा-अंतिम-क्षेत्र समस्या और संलग्न-एलएफ समस्या) को हल करता है।
तो, कुल मिलाकर, यह काफी शक्तिशाली समाधान है। यह केवल शेष कमजोरी है जो मल्टीचैकर डेलिमिटर के लिए समर्थन की कमी है, जिसे मैं बाद में संबोधित करूंगा।
गलत उत्तर # 8
string='first line
second line
third line'
readarray -t lines <<<"$string"
(यह वास्तव में # 7 के समान पोस्ट से है ; उत्तर देने वाले ने एक ही पोस्ट में दो समाधान प्रदान किए।)
readarray
अंतर्निहित है, जो के लिए एक पर्याय है mapfile
, आदर्श है। यह एक अंतर्निहित कमांड है जो एक शॉट में एक सरणी चर में एक बाइटस्ट्रेस को पार्स करता है; छोरों, सशर्तियों, प्रतिस्थापनों, या किसी अन्य चीज़ के साथ कोई खिलवाड़ नहीं। और यह इनपुट स्ट्रिंग से किसी भी व्हाट्सएप को विशेष रूप से पट्टी नहीं करता है। और (यदि -O
नहीं दिया गया है) तो यह असाइन करने से पहले लक्ष्य सरणी को आसानी से साफ़ कर देता है। लेकिन यह अभी भी सही नहीं है, इसलिए "गलत उत्तर" के रूप में मेरी आलोचना।
सबसे पहले, बस इसे बाहर निकालने के लिए, ध्यान दें कि, read
फ़ील्ड-पार्सिंग करते समय व्यवहार की तरह , readarray
खाली होने पर ट्रेलिंग फ़ील्ड को छोड़ देता है। फिर, यह शायद ओपी के लिए चिंता का विषय नहीं है, लेकिन यह कुछ उपयोग-मामलों के लिए हो सकता है। मैं एक पल में इस पर वापस आता हूँ।
दूसरा, पहले की तरह, यह मल्टीचैकर डेलिमिटर का समर्थन नहीं करता है। मैं इसके लिए एक पल में एक तय कर दूँगा।
तीसरा, लिखित समाधान ओपी के इनपुट स्ट्रिंग को पार्स नहीं करता है, और वास्तव में, इसका उपयोग नहीं किया जा सकता है-इसे पार्स करने के लिए। मैं इस पल के रूप में अच्छी तरह से विस्तार करेंगे।
उपरोक्त कारणों से, मैं अभी भी इसे ओपी के सवाल का "गलत जवाब" मानता हूं। नीचे मैं वही दूंगा जो मैं सही उत्तर मानता हूं।
सही उत्तर
यहां केवल विकल्प को निर्दिष्ट करके # 8 कार्य करने का एक भोला प्रयास है -d
:
string='Paris, France, Europe';
readarray -td, a <<<"$string"; declare -p a;
## declare -a a=([0]="Paris" [1]=" France" [2]=$' Europe\n')
हम देखते हैं कि परिणाम # 7read
में चर्चा किए गए लूपिंग समाधान के दोहरे-सशर्त दृष्टिकोण से प्राप्त परिणाम के समान है । हम मैनुअल डमी-टर्मिनेटर ट्रिक से इसे लगभग हल कर सकते हैं :
readarray -td, a <<<"$string,"; declare -p a;
## declare -a a=([0]="Paris" [1]=" France" [2]=" Europe" [3]=$'\n')
यहाँ समस्या यह है कि readarray
अनुगामी क्षेत्र को संरक्षित किया गया है, क्योंकि <<<
पुनर्निर्देशन ऑपरेटर ने इनपुट स्ट्रिंग में एलएफ को जोड़ा है, और इसलिए अनुगामी क्षेत्र खाली नहीं था (अन्यथा इसे गिरा दिया गया था)। हम इस तथ्य के बाद अंतिम सरणी तत्व को स्पष्ट रूप से परेशान करके इसका ध्यान रख सकते हैं:
readarray -td, a <<<"$string,"; unset 'a[-1]'; declare -p a;
## declare -a a=([0]="Paris" [1]=" France" [2]=" Europe")
केवल दो समस्याएं जो बनी हुई हैं, जो वास्तव में संबंधित हैं, (1) बहिर्मुखी व्हाट्सएप हैं जिन्हें ट्रिम किया जाना है, और (2) मल्टीचैकर डेलिमिटर के लिए समर्थन की कमी है।
व्हाट्सएप को बेशक बाद में ट्रिम किया जा सकता है (उदाहरण के लिए, देखें कि ब्रास वेरिएबल से व्हॉट्सएप को कैसे ट्रिम किया जाए? )। लेकिन अगर हम एक मल्टीचैकर डिमाइटर को हैक कर सकते हैं, तो यह एक शॉट में दोनों समस्याओं को हल करेगा।
दुर्भाग्य से, काम करने के लिए एक मल्टीचैकर सीमांकक प्राप्त करने का कोई सीधा तरीका नहीं है । सबसे अच्छा समाधान मैंने सोचा है कि मल्टीचैकर डिमाइटर को बदलने के लिए इनपुट स्ट्रिंग को प्रीप्रोसेस करना एक एकल-कैरेक्टर सीमांकक के साथ होता है जिसे इनपुट स्ट्रिंग की सामग्री से टकराने की गारंटी नहीं दी जाएगी। यह गारंटी वाला एकमात्र वर्ण NUL बाइट है । यह इसलिए है, क्योंकि bash में (हालांकि zsh में नहीं, संयोगवश), चर में NUL बाइट नहीं हो सकती। यह प्रीप्रोसेसिंग कदम एक प्रक्रिया प्रतिस्थापन में इनलाइन किया जा सकता है। यहां बताया गया है कि यह awk का उपयोग कैसे करें :
readarray -td '' a < <(awk '{ gsub(/, /,"\0"); print; }' <<<"$string, "); unset 'a[-1]';
declare -p a;
## declare -a a=([0]="Paris" [1]="France" [2]="Europe")
वहाँ, अंत में! यह समाधान गलत तरीके से बीच में विभाजित क्षेत्रों को नहीं करेगा, समय से पहले नहीं काट देगा, खाली खेतों को नहीं गिराएगा, फ़ाइल नाम के विस्तार पर खुद को भ्रष्ट नहीं करेगा, स्वचालित रूप से व्हाट्सएप को अग्रणी और पीछे नहीं छोड़ेगा, अंत में एक स्टोववे एलएफ नहीं छोड़ेगा। छोरों की आवश्यकता नहीं होती है, और एकल-चरित्र सीमांकक के लिए व्यवस्थित नहीं होता है।
ट्रिमिंग समाधान
अंत में, मैं अस्पष्ट -C callback
विकल्प का उपयोग करके अपने खुद के काफी जटिल ट्रिमिंग समाधान का प्रदर्शन करना चाहता था readarray
। दुर्भाग्य से, मैं स्टैक ओवरफ़्लो की ड्रैकियन 30,000 चरित्र पोस्ट सीमा के खिलाफ कमरे से बाहर चला गया हूं, इसलिए मैं इसे स्पष्ट नहीं कर पाऊंगा। मैं पाठक के लिए एक अभ्यास के रूप में छोड़ दूँगा।
function mfcb { local val="$4"; "$1"; eval "$2[$3]=\$val;"; };
function val_ltrim { if [[ "$val" =~ ^[[:space:]]+ ]]; then val="${val:${#BASH_REMATCH[0]}}"; fi; };
function val_rtrim { if [[ "$val" =~ [[:space:]]+$ ]]; then val="${val:0:${#val}-${#BASH_REMATCH[0]}}"; fi; };
function val_trim { val_ltrim; val_rtrim; };
readarray -c1 -C 'mfcb val_trim a' -td, <<<"$string,"; unset 'a[-1]'; declare -p a;
## declare -a a=([0]="Paris" [1]="France" [2]="Europe")
,
कॉमा (अंतरिक्ष) पर परिसीमन के बारे में पूछता है और कॉमा जैसे एक भी चरित्र नहीं है । यदि आप केवल उत्तरार्द्ध में रुचि रखते हैं, तो यहां उत्तर का पालन करना आसान है: stackoverflow.com/questions/918886/…