बैश में एक सरणी में स्ट्रिंग विभाजित करें


640

बैश स्क्रिप्ट में मैं एक लाइन को टुकड़ों में विभाजित करना और उन्हें एक ऐरे में स्टोर करना चाहूंगा।

रेखा:

Paris, France, Europe

मैं उन्हें इस तरह से एक सरणी में रखना चाहूंगा:

array[0] = Paris
array[1] = France
array[2] = Europe

मैं सरल कोड का उपयोग करना चाहूंगा, कमांड की गति कोई मायने नहीं रखती है। मैं यह कैसे कर सकता हूं?


22
यह # 1 Google हिट है लेकिन उत्तर में विवाद है क्योंकि दुर्भाग्य से सवाल , कॉमा (अंतरिक्ष) पर परिसीमन के बारे में पूछता है और कॉमा जैसे एक भी चरित्र नहीं है । यदि आप केवल उत्तरार्द्ध में रुचि रखते हैं, तो यहां उत्तर का पालन करना आसान है: stackoverflow.com/questions/918886/…
antak

यदि आप एक स्ट्रिंग को मून करना चाहते हैं और इसे एक सरणी के रूप में रखने के बारे में परवाह नहीं करते हैं, तो यह भी ध्यान में रखने के cutलिए एक उपयोगी बैश कमांड है। विभाजक निश्चित है en.wikibooks.org/wiki/Cut आप निश्चित चौड़ाई रिकॉर्ड संरचना से भी डेटा निकाल सकते हैं। en.wikipedia.org/wiki/Cut_(Unix) computerhope.com/unix/ucut.htm
JGFMK

जवाबों:


1088
IFS=', ' read -r -a array <<< "$string"

ध्यान दें कि में पात्रों $IFSविभाजक के रूप में व्यक्तिगत रूप से व्यवहार कर रहे हैं ताकि इस मामले में खेतों के द्वारा अलग किया जा सकता है या तो अल्पविराम या अंतरिक्ष के बजाय दो अक्षर का अनुक्रम। दिलचस्प बात यह है कि इनपुट में कॉमा-स्पेस दिखाई देने पर खाली फ़ील्ड नहीं बनाई जाती है क्योंकि स्पेस विशेष रूप से व्यवहार किया जाता है।

एक व्यक्तिगत तत्व तक पहुँचने के लिए:

echo "${array[0]}"

तत्वों पर पुनरावृति करने के लिए:

for element in "${array[@]}"
do
    echo "$element"
done

सूचकांक और मूल्य दोनों प्राप्त करने के लिए:

for index in "${!array[@]}"
do
    echo "$index ${array[index]}"
done

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

unset "array[1]"
array[42]=Earth

किसी सरणी में तत्वों की संख्या प्राप्त करने के लिए:

echo "${#array[@]}"

जैसा कि ऊपर उल्लेख किया गया है, सरणियों को विरल किया जा सकता है, इसलिए आपको अंतिम तत्व प्राप्त करने के लिए लंबाई का उपयोग नहीं करना चाहिए। यहाँ आप बैश 4.2 और बाद में कैसे कर सकते हैं:

echo "${array[-1]}"

बैश के किसी भी संस्करण में (2.05 बी के बाद कहीं से):

echo "${array[@]: -1:1}"

सरणी के अंत से बड़े नकारात्मक ऑफसेट आगे का चयन करते हैं। पुराने रूप में माइनस साइन से पहले स्पेस पर ध्यान दें। यह आवश्यक है।


15
बस उपयोग करें IFS=', ', तो आपको रिक्त स्थान अलग से निकालने की आवश्यकता नहीं है। टेस्ट:IFS=', ' read -a array <<< "Paris, France, Europe"; echo "${array[@]}"
l0b0

4
@ l0b0: धन्यवाद। मुझे नहीं पता कि मैं क्या सोच रहा था। मुझे declare -p arrayपरीक्षण आउटपुट के लिए उपयोग करना पसंद है, वैसे।
अगली सूचना तक रोक दिया गया।

1
यह उद्धरण का सम्मान नहीं करता है। उदाहरण के लिए France, Europe, "Congo, The Democratic Republic of the"यह शंकु के बाद विभाजित हो जाएगा।
यिसरेल डोव

2
@YisraelDov: बैश के पास सीएसवी से निपटने का कोई तरीका नहीं है। यह उद्धरण के अंदर अल्पविराम और उनके बाहर के लोगों के बीच अंतर नहीं बता सकता है। आप एक उपकरण है जो सीएसवी समझता है इस तरह के एक उच्च स्तर की भाषा में एक lib के रूप में, उदाहरण के लिए उपयोग करने के लिए की आवश्यकता होगी सीएसवी अजगर में मॉड्यूल।
अगली सूचना तक रोक दिया गया।

5
str="Paris, France, Europe, Los Angeles"; IFS=', ' read -r -a array <<< "$str"array=([0]="Paris" [1]="France" [2]="Europe" [3]="Los" [4]="Angeles")एक नोट के रूप में विभाजित हो जाएगा । इसलिए यह केवल उन स्थानों के साथ काम करता है, जहां कोई रिक्त स्थान नहीं है, क्योंकि IFS=', 'यह व्यक्तिगत पात्रों का एक समूह है - न कि एक स्ट्रिंग सीमांकक।
dawg

332

इस प्रश्न के सभी उत्तर एक या दूसरे तरीके से गलत हैं।


गलत उत्तर # 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 का सामना करने के बाद प्रोसेसिंग बंद करें। readBuiltin केवल मंगलाचरण प्रति एक लाइन संसाधित करता है। यह तब भी सही है जब आप केवल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//:/ }"पैटर्न प्रतिस्थापन" रूप का उपयोग करना बेहतर है , और बाहरी निष्पादन योग्य ( या ) चल रहा है , क्योंकि पैरामीटर विस्तार विशुद्ध रूप से एक शेल-आंतरिक ऑपरेशन है। (इसके अलावा, और समाधान के लिए, इनपुट चर को कमांड प्रतिस्थापन के अंदर डबल-उद्धृत किया जाना चाहिए; अन्यथा शब्द विभाजन कमांड में प्रभावी होगा और संभावित रूप से फ़ील्ड मान के साथ गड़बड़ करेगा। इसके अलावा, कमांड प्रतिस्थापन का रूप पुराने के लिए बेहतर है।trsedtrsedecho$(...)`...` फॉर्म चूंकि यह कमांड प्रतिस्थापन के घोंसले के शिकार को सरल करता है और पाठ संपादकों द्वारा बेहतर वाक्य रचना हाइलाइटिंग के लिए अनुमति देता है।)


गलत उत्तर # 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 संस्करण थोड़ा बेहतर है, क्योंकि यह विस्तार खंड के पहले वाक्य में "शब्द" के बजाय "टोकन" शब्द का विरोध करता है:

विस्तार को कमांड लाइन पर प्रदर्शन के बाद इसे टोकन में विभाजित किया गया है।

महत्वपूर्ण बिंदु यह है कि, $IFSbars पार्स स्रोत कोड को बदलने का तरीका नहीं बदलता है। बैश स्रोत कोड का पार्सिंग वास्तव में एक बहुत ही जटिल प्रक्रिया है जिसमें शेल व्याकरण के विभिन्न तत्वों की मान्यता शामिल है, जैसे कि कमांड अनुक्रम, कमांड लिस्ट, पाइपलाइन, पैरामीटर विस्तार, अंकगणितीय प्रतिस्थापन और कमांड प्रतिस्थापन। अधिकांश भाग के लिए, बैश पार्सिंग प्रक्रिया को उपयोगकर्ता के स्तर की कार्रवाइयों जैसे परिवर्तनशील कार्य (वास्तव में, इस नियम में कुछ मामूली अपवाद नहीं हैं) द्वारा बदला जा सकता है; उदाहरण के लिए, विभिन्न 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")

8
यह नोट करने के लिए भी सहायक हो सकता है (हालांकि समझदारी से आपके पास ऐसा करने के लिए कोई जगह नहीं थी) कि बैश 4.4 में पहली बार -dविकल्प readarrayदिखाई देता है।
15

2
शानदार जवाब (+1)। यदि आप awk '{ gsub(/,[ ]+|$/,"\0"); print }'फाइनल के अपने अवतरण को बदल देते हैं और ", " फाइनल के रिकॉर्ड को खत्म करने पर आपको जिमनास्टिक से गुजरने की जरूरत नहीं है। तो: readarray -td '' a < <(awk '{ gsub(/,[ ]+/,"\0"); print; }' <<<"$string")बैश पर जो समर्थन करता है readarray। ध्यान दें कि आपके विधि बैश है 4.4+ मैं की वजह से लगता है -dमेंreadarray
dawg

3
@datUser यह दुर्भाग्यपूर्ण है। आपका बैश का संस्करण बहुत पुराना होना चाहिए readarray। इस मामले में, आप पर निर्मित दूसरे-सर्वश्रेष्ठ समाधान का उपयोग कर सकते हैं read। मैं इसका उल्लेख कर रहा हूं: a=(); while read -rd,; do a+=("$REPLY"); done <<<"$string,";( awkप्रतिस्थापन के साथ यदि आपको मल्टीचैकर डिमाइटर समर्थन की आवश्यकता है)। यदि आप किसी समस्या में भाग लेते हैं तो मुझे बताएं; मुझे पूरा यकीन है कि इस समाधान को बैश के पुराने संस्करणों पर काम करना चाहिए, दो दशक पहले के संस्करण की तरह, संस्करण 2-कुछ पर वापस करना चाहिए।
bgoldst

1
वाह, क्या शानदार जवाब है! हे हे, मेरी प्रतिक्रिया: बैश स्क्रिप्ट को खोदकर अजगर को निकाल दिया!
आर्टफ्लोरोबॉट

1
OSX पर @datUser बैश अभी भी 3.2 पर जारी है (2007 सीए जारी); मैं bash Homebrew में पाया का उपयोग किया है ओएस एक्स पर 4.x बैश संस्करण प्राप्त करने के लिए
जेडीएस

222

यहाँ IFS की स्थापना के बिना एक तरीका है:

string="1:2:3:4:5"
set -f                      # avoid globbing (expansion of *).
array=(${string//:/ })
for i in "${!array[@]}"
do
    echo "$i=>${array[i]}"
done

यह विचार स्ट्रिंग प्रतिस्थापन का उपयोग कर रहा है:

${string//substring/replacement}

किसी स्थान को आरंभीकृत करने के लिए प्रतिस्थापित स्थान के साथ सफ़ेद स्थान के साथ $ प्रतिस्थापन के सभी मैचों को बदलने के लिए:

(element1 element2 ... elementN)

नोट: यह उत्तर उपयोग करता है विभाजन + ग्लोब ऑपरेटर । इस प्रकार, कुछ पात्रों के विस्तार को रोकने के लिए (जैसे कि *) इस स्क्रिप्ट के लिए ग्लोबिंग को रोकना एक अच्छा विचार है।


1
इस दृष्टिकोण का उपयोग किया ... जब तक मैं विभाजित करने के लिए एक लंबी स्ट्रिंग में नहीं आया। एक मिनट से अधिक के लिए 100% सीपीयू (फिर मैंने इसे मार दिया)। यह अफ़सोस की बात है क्योंकि यह विधि आईएफएस में कुछ चरित्र नहीं, एक स्ट्रिंग द्वारा विभाजित करने की अनुमति देती है।
वर्नर लेहमन

एक मिनट के लिए 100% CPU समय मुझे लगता है जैसे कि कहीं न कहीं कुछ गलत होना चाहिए। बस वह तार कितना लंबा था, यह एमबी या जीबी आकार का है? मुझे लगता है, आम तौर पर, अगर आपको बस एक छोटे स्ट्रिंग विभाजन की आवश्यकता है, तो आप बैश के भीतर रहना चाहते हैं, लेकिन अगर यह एक बड़ी फ़ाइल है, तो मैं इसे करने के लिए पर्ल जैसी किसी चीज़ को निष्पादित करूंगा।

12
चेतावनी: बस इस दृष्टिकोण के साथ एक समस्या में भाग गया। यदि आपके पास * नाम का कोई तत्व है, तो आपको अपने cwd के सभी तत्व मिल जाएंगे। इस प्रकार स्ट्रिंग = "1: 2: 3: 4: *" आपके कार्यान्वयन के आधार पर कुछ अप्रत्याशित और संभवतः खतरनाक परिणाम देगा। (IFS = ',' read -a array <<< "$ string") के साथ एक ही त्रुटि नहीं मिली और यह उपयोग करने के लिए सुरक्षित लगता है।
डाइटर ग्रिबनिट्ज

4
उद्धृत करना ${string//:/ }खोल विस्तार को रोकता है
एंड्रयू व्हाइट

1
मुझे OSX पर निम्नलिखित का उपयोग करना था: array=(${string//:/ })
मार्क थॉमसन

95
t="one,two,three"
a=($(echo "$t" | tr ',' '\n'))
echo "${a[2]}"

प्रिंट तीन


8
मैं वास्तव में इस दृष्टिकोण को पसंद करता हूं। सरल।
श्रिम्पवगन

4
मैंने इसे कॉपी और पेस्ट किया और यह गूंज के साथ काम नहीं किया, लेकिन जब मैंने इसे लूप के लिए इस्तेमाल किया तो काम किया।
बेन

2
यह बताए अनुसार काम नहीं करता है। @ Jmoney38 या श्रिम्पवगन यदि आप इसे टर्मिनल में पेस्ट कर सकते हैं और वांछित आउटपुट प्राप्त कर सकते हैं, तो कृपया यहाँ परिणाम पेस्ट करें।
abalter

2
@abalter मेरे साथ काम करता है a=($(echo $t | tr ',' "\n"))। साथ ही परिणाम a=($(echo $t | tr ',' ' '))
पत्ती

@procrastinator मैंने इसे केवल VERSION="16.04.2 LTS (Xenial Xerus)"एक bashशेल में आज़माया था , और अंतिम echoसिर्फ एक रिक्त रेखा प्रिंट करता है। लिनक्स का कौन सा संस्करण और आप किस शेल का उपयोग कर रहे हैं? दुर्भाग्य से, एक टिप्पणी में टर्मिनल सत्र प्रदर्शित नहीं कर सकता।
अबला जूल 25'17

29

कभी-कभी मेरे साथ ऐसा हुआ कि स्वीकृत उत्तर में वर्णित विधि काम नहीं करती थी, खासकर अगर विभाजक एक गाड़ी वापसी है।
उन मामलों में मैंने इस तरह से हल किया:

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"

for line in "${lines[@]}"
    do
        echo "--> $line"
done

2
+1 यह पूरी तरह से मेरे लिए काम करता है। मुझे कई तार लगाने की ज़रूरत थी, एक नई रेखा से विभाजित, एक सरणी में, और read -a arr <<< "$strings"साथ काम नहीं किया IFS=$'\n'
स्टेफन वैन डेन अककर


यह मूल प्रश्न का उत्तर नहीं देता है।
माइक

29

स्वीकृत उत्तर एक पंक्ति में मानों के लिए काम करता है।
यदि चर में कई लाइनें हैं:

string='first line
        second line
        third line'

हमें सभी पंक्तियों को प्राप्त करने के लिए एक बहुत अलग कमांड की आवश्यकता है:

while read -r line; do lines+=("$line"); done <<<"$string"

या बहुत सरल बैश पुनर्लेखन :

readarray -t lines <<<"$string"

सभी पंक्तियों को प्रिंट करना बहुत आसान है जो प्रिंटफ फीचर का लाभ उठाता है:

printf ">[%s]\n" "${lines[@]}"

>[first line]
>[        second line]
>[        third line]

2
जबकि हर समाधान हर स्थिति के लिए काम नहीं करता है, आपके पुनर्वसन का उल्लेख ... मेरे अंतिम दो घंटे को 5 मिनट के साथ बदल दिया ... आपको मेरा वोट मिला
गुस्सा 84


6

आपके स्ट्रिंग को एक सरणी में विभाजित करने की कुंजी मल्टी कैरेक्टर सीमांकक है ", "IFSमल्टी कैरेक्टर सीमांकक का उपयोग करने वाला कोई भी समाधान स्वाभाविक रूप से गलत है क्योंकि IFS उन पात्रों का एक सेट है, न कि एक स्ट्रिंग।

यदि आप असाइन करते हैं IFS=", "तो स्ट्रिंग EITHER ","OR " "या उनमें से किसी भी संयोजन पर टूट जाएगी जो कि दो वर्णों के परिसीमन का सटीक प्रतिनिधित्व नहीं है ", "

आप प्रक्रिया प्रतिस्थापन के साथ, स्ट्रिंग को विभाजित awkया sedविभाजित करने के लिए उपयोग कर सकते हैं :

#!/bin/bash

str="Paris, France, Europe"
array=()
while read -r -d $'\0' each; do   # use a NUL terminated field separator 
    array+=("$each")
done < <(printf "%s" "$str" | awk '{ gsub(/,[ ]+|$/,"\0"); print }')
declare -p array
# declare -a array=([0]="Paris" [1]="France" [2]="Europe") output

यह सीधे बैश में आपके द्वारा रीगेक्स का उपयोग करने के लिए अधिक कुशल है:

#!/bin/bash

str="Paris, France, Europe"

array=()
while [[ $str =~ ([^,]+)(,[ ]+|$) ]]; do
    array+=("${BASH_REMATCH[1]}")   # capture the field
    i=${#BASH_REMATCH}              # length of field + delimiter
    str=${str:i}                    # advance the string by that length
done                                # the loop deletes $str, so make a copy if needed

declare -p array
# declare -a array=([0]="Paris" [1]="France" [2]="Europe") output...

दूसरे रूप के साथ, कोई उप शेल नहीं है और यह स्वाभाविक रूप से तेज होगा।


Bgoldst द्वारा संपादित करें: यहां कुछ readarrayसमाधान दिए गए हैं, जो मेरे समाधान की तुलना dawg के रेगेक्स समाधान से करते हैं, और मैंने इसका readहल भी शामिल किया है (ध्यान दें: मैंने अपने समाधान के साथ अधिक सामंजस्य के लिए regex समाधान को संशोधित किया है) (नीचे मेरी टिप्पणियाँ भी देखें) पद):

## competitors
function c_readarray { readarray -td '' a < <(awk '{ gsub(/, /,"\0"); print; };' <<<"$1, "); unset 'a[-1]'; };
function c_read { a=(); local REPLY=''; while read -r -d ''; do a+=("$REPLY"); done < <(awk '{ gsub(/, /,"\0"); print; };' <<<"$1, "); };
function c_regex { a=(); local s="$1, "; while [[ $s =~ ([^,]+),\  ]]; do a+=("${BASH_REMATCH[1]}"); s=${s:${#BASH_REMATCH}}; done; };

## helper functions
function rep {
    local -i i=-1;
    for ((i = 0; i<$1; ++i)); do
        printf %s "$2";
    done;
}; ## end rep()

function testAll {
    local funcs=();
    local args=();
    local func='';
    local -i rc=-1;
    while [[ "$1" != ':' ]]; do
        func="$1";
        if [[ ! "$func" =~ ^[_a-zA-Z][_a-zA-Z0-9]*$ ]]; then
            echo "bad function name: $func" >&2;
            return 2;
        fi;
        funcs+=("$func");
        shift;
    done;
    shift;
    args=("$@");
    for func in "${funcs[@]}"; do
        echo -n "$func ";
        { time $func "${args[@]}" >/dev/null 2>&1; } 2>&1| tr '\n' '/';
        rc=${PIPESTATUS[0]}; if [[ $rc -ne 0 ]]; then echo "[$rc]"; else echo; fi;
    done| column -ts/;
}; ## end testAll()

function makeStringToSplit {
    local -i n=$1; ## number of fields
    if [[ $n -lt 0 ]]; then echo "bad field count: $n" >&2; return 2; fi;
    if [[ $n -eq 0 ]]; then
        echo;
    elif [[ $n -eq 1 ]]; then
        echo 'first field';
    elif [[ "$n" -eq 2 ]]; then
        echo 'first field, last field';
    else
        echo "first field, $(rep $[$1-2] 'mid field, ')last field";
    fi;
}; ## end makeStringToSplit()

function testAll_splitIntoArray {
    local -i n=$1; ## number of fields in input string
    local s='';
    echo "===== $n field$(if [[ $n -ne 1 ]]; then echo 's'; fi;) =====";
    s="$(makeStringToSplit "$n")";
    testAll c_readarray c_read c_regex : "$s";
}; ## end testAll_splitIntoArray()

## results
testAll_splitIntoArray 1;
## ===== 1 field =====
## c_readarray   real  0m0.067s   user 0m0.000s   sys  0m0.000s
## c_read        real  0m0.064s   user 0m0.000s   sys  0m0.000s
## c_regex       real  0m0.000s   user 0m0.000s   sys  0m0.000s
##
testAll_splitIntoArray 10;
## ===== 10 fields =====
## c_readarray   real  0m0.067s   user 0m0.000s   sys  0m0.000s
## c_read        real  0m0.064s   user 0m0.000s   sys  0m0.000s
## c_regex       real  0m0.001s   user 0m0.000s   sys  0m0.000s
##
testAll_splitIntoArray 100;
## ===== 100 fields =====
## c_readarray   real  0m0.069s   user 0m0.000s   sys  0m0.062s
## c_read        real  0m0.065s   user 0m0.000s   sys  0m0.046s
## c_regex       real  0m0.005s   user 0m0.000s   sys  0m0.000s
##
testAll_splitIntoArray 1000;
## ===== 1000 fields =====
## c_readarray   real  0m0.084s   user 0m0.031s   sys  0m0.077s
## c_read        real  0m0.092s   user 0m0.031s   sys  0m0.046s
## c_regex       real  0m0.125s   user 0m0.125s   sys  0m0.000s
##
testAll_splitIntoArray 10000;
## ===== 10000 fields =====
## c_readarray   real  0m0.209s   user 0m0.093s   sys  0m0.108s
## c_read        real  0m0.333s   user 0m0.234s   sys  0m0.109s
## c_regex       real  0m9.095s   user 0m9.078s   sys  0m0.000s
##
testAll_splitIntoArray 100000;
## ===== 100000 fields =====
## c_readarray   real  0m1.460s   user 0m0.326s   sys  0m1.124s
## c_read        real  0m2.780s   user 0m1.686s   sys  0m1.092s
## c_regex       real  17m38.208s   user 15m16.359s   sys  2m19.375s
##

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

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

@boldstst: क्या एक शांत बेंचमार्क! रेगेक्स की रक्षा में, हज़ारों फ़ील्ड्स (जो रेगेक्स को विभाजित कर रहा है) के 10 या 100 में से एक है, संभवतः \nउन फ़ील्ड्स में रिकॉर्ड के कुछ रूप (जैसे सीमांकित पाठ लाइनें) होंगे, ताकि भयावह धीमा-डाउन होने की संभावना न हो। यदि आपके पास 100,000 फ़ील्ड के साथ एक स्ट्रिंग है - तो शायद बैश आदर्श नहीं है ;-) बेंचमार्क के लिए धन्यवाद। मैंने एक-दो चीजें सीखीं।
dgg

4

शुद्ध बैश मल्टी-कैरेक्टर सीमांकक घोल।

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

चूँकि Google इस उत्तर को खोज परिणामों के शीर्ष पर या उसके निकट रैंक करना चाहता है, इसलिए मैं पाठकों को एकाधिक वर्ण परिसीमनकर्ताओं के प्रश्न का सशक्त उत्तर देना चाहता था, क्योंकि इसका उल्लेख कम से कम एक प्रतिक्रिया में भी किया गया है।

यदि आप एक बहु-चरित्र सीमांकक समस्या के समाधान की तलाश में हैं, तो मैं मल्लिकार्जुन के पोस्ट की समीक्षा करने का सुझाव देता हूं , विशेष रूप से gniourf_gniourf से प्रतिक्रिया जो पैरामीटर विस्तार का उपयोग करके यह सुरुचिपूर्ण शुद्ध BASH समाधान प्रदान करता है:

#!/bin/bash
str="LearnABCtoABCSplitABCaABCString"
delimiter=ABC
s=$str$delimiter
array=();
while [[ $s ]]; do
    array+=( "${s%%"$delimiter"*}" );
    s=${s#*"$delimiter"};
done;
declare -p array

उद्धृत टिप्पणी / संदर्भित पोस्ट का लिंक

उद्धृत प्रश्न का लिंक: कैसे एक मल्टी-कैरेक्टर सीमांकक में एक स्ट्रिंग को बैश में विभाजित किया है?


1
इसी तरह के लेकिन बेहतर दृष्टिकोण के लिए मेरी टिप्पणी देखें ।
xebeche

3

यह मेरे लिए OSX पर काम करता है:

string="1 2 3 4 5"
declare -a array=($string)

यदि आपके स्ट्रिंग में अलग-अलग सीमांकक है, तो बस 1 उन लोगों को जगह देता है:

string="1,2,3,4,5"
delimiter=","
declare -a array=($(echo $string | tr "$delimiter" " "))

सरल :-)


दोनों बैश और Zsh के लिए काम करता है जो एक प्लस है!
एलिजा डब्ल्यू। गगेंन

2

IFS को संशोधित किए बिना इसे करने का दूसरा तरीका:

read -r -a myarray <<< "${string//, /$IFS}"

बल्कि हमारे वांछित सीमांकक मैच के लिए भारतीय विदेश सेवा को बदलने से, हम अपने वांछित सीमांकक की सभी घटनाओं की जगह ले सकता है ", "की सामग्री के साथ $IFSके माध्यम से "${string//, /$IFS}"

शायद यह बहुत बड़े तार के लिए धीमा होगा, हालांकि?

यह डेनिस विलियमसन के जवाब पर आधारित है।


2

मैं इस पोस्ट के पार आया जब एक इनपुट पार्स करना चाह रहा था जैसे: word1, word2, ...

उपरोक्त में से किसी ने भी मेरी मदद नहीं की। awk का उपयोग करके इसे हल किया। अगर यह किसी की मदद करता है:

STRING="value1,value2,value3"
array=`echo $STRING | awk -F ',' '{ s = $1; for (i = 2; i <= NF; i++) s = s "\n"$i; print s; }'`
for word in ${array}
do
        echo "This is the word $word"
done

1

इसे इस्तेमाल करे

IFS=', '; array=(Paris, France, Europe)
for item in ${array[@]}; do echo $item; done

यह आसान है। यदि आप चाहें, तो आप एक घोषणा भी जोड़ सकते हैं (और अल्पविराम भी हटा सकते हैं):

IFS=' ';declare -a array=(Paris France Europe)

उपरोक्त को पूर्ववत् करने के लिए IFS जोड़ा जाता है, लेकिन यह इसके बिना ताजा बैश उदाहरण में काम करता है


1

हम सरणी ऑब्जेक्ट में स्ट्रिंग को विभाजित करने के लिए tr कमांड का उपयोग कर सकते हैं। यह मैकओएस और लिनक्स दोनों पर काम करता है

  #!/usr/bin/env bash
  currentVersion="1.0.0.140"
  arrayData=($(echo $currentVersion | tr "." "\n"))
  len=${#arrayData[@]}
  for (( i=0; i<=$((len-1)); i++ )); do 
       echo "index $i - value ${arrayData[$i]}"
  done

एक अन्य विकल्प IFS कमांड का उपयोग करता है

IFS='.' read -ra arrayData <<< "$currentVersion"
#It is the same as tr
arrayData=($(echo $currentVersion | tr "." "\n"))

#Print the split string
for i in "${arrayData[@]}"
do
    echo $i
done

0

इसे इस्तेमाल करो:

countries='Paris, France, Europe'
OIFS="$IFS"
IFS=', ' array=($countries)
IFS="$OIFS"

#${array[1]} == Paris
#${array[2]} == France
#${array[3]} == Europe

3
खराब: शब्द बंटवारे और pathname विस्तार के अधीन। कृपया खराब उत्तर देने के लिए पुराने प्रश्नों को अच्छे उत्तरों के साथ पुनर्जीवित न करें।
गनीउरफ_गनीउरफ

2
यह एक बुरा जवाब हो सकता है, लेकिन यह अभी भी एक वैध जवाब है। फ़्लैगर्स / समीक्षक: गलत उत्तर जैसे कि यह, डाउनवोट, डिलीट न करें!
स्कॉट वेल्डन

2
@gniourf_gniourf क्या आप बता सकते हैं कि यह एक बुरा जवाब क्यों है? मैं वास्तव में समझ में नहीं आता जब यह विफल रहता है।
जॉर्ज सोवतोव

3
@GeorgeSovetov: जैसा कि मैंने कहा, यह शब्द विभाजन और pathname विस्तार के अधीन है। अधिक आम तौर पर, एक स्ट्रिंग को एक सरणी में विभाजित करना एक array=( $string )(दुख की बात है कि बहुत ही सामान्य) एंटीपैर्टन: शब्द विभाजन होता है string='Prague, Czech Republic, Europe':; पाथनेम का विस्तार होता है: string='foo[abcd],bar[efgh]'यदि आपके पास कोई फ़ाइल होगी, जैसे कि नाम, जैसे, foodया barfआपकी निर्देशिका में। इस तरह के निर्माण का एकमात्र वैध उपयोग तब stringहोता है जब एक ग्लोब होता है।
गनीउरफ_गनीउरफ

0

अद्यतन: यह मत करो, eval के साथ समस्याओं के कारण।

थोड़ा कम समारोह के साथ:

IFS=', ' eval 'array=($string)'

जैसे

string="foo, bar,baz"
IFS=', ' eval 'array=($string)'
echo ${array[1]} # -> bar

4
बुराई बुराई है! यह मत करो।
कैसरसोल

1
Pfft। यदि आप इस बात के लिए बड़ी स्क्रिप्ट लिख रहे हैं, तो आप इसे गलत कर रहे हैं। एप्लिकेशन कोड में, बुराई बुराई है। शेल स्क्रिप्टिंग में, यह सामान्य, आवश्यक और असंगत है।
user1009908 4

2
$अपने वैरिएबल में डालें और आप देखेंगे ... मैं कई स्क्रिप्ट लिखता हूं और मुझे कभी भी एक का उपयोग नहीं करना पड़ाeval
caesarsol

2
आप सही कह रहे हैं, यह केवल तभी प्रयोग करने योग्य होता है जब इनपुट साफ-सुथरा हो। एक मजबूत समाधान नहीं।
user1009908

केवल समय मैं कभी भी eval का उपयोग करना था, एक ऐसे अनुप्रयोग के लिए था जो स्वयं अपना कोड / मॉड्यूल उत्पन्न करेगा ... और इसके पास उपयोगकर्ता इनपुट का कोई भी रूप नहीं था ...
गुस्सा 84

0

यहाँ मेरा हैक है!

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

नीचे दिए गए दृष्टिकोण में कई युद्धाभ्यासों की आवश्यकता है, लेकिन मुझे विश्वास है कि यह हमारी अधिकांश आवश्यकताओं के लिए काम करेगा!

#!/bin/bash

# --------------------------------------
# SPLIT FUNCTION
# ----------------

F_SPLIT_R=()
f_split() {
    : 'It does a "split" into a given string and returns an array.

    Args:
        TARGET_P (str): Target string to "split".
        DELIMITER_P (Optional[str]): Delimiter used to "split". If not 
    informed the split will be done by spaces.

    Returns:
        F_SPLIT_R (array): Array with the provided string separated by the 
    informed delimiter.
    '

    F_SPLIT_R=()
    TARGET_P=$1
    DELIMITER_P=$2
    if [ -z "$DELIMITER_P" ] ; then
        DELIMITER_P=" "
    fi

    REMOVE_N=1
    if [ "$DELIMITER_P" == "\n" ] ; then
        REMOVE_N=0
    fi

    # NOTE: This was the only parameter that has been a problem so far! 
    # By Questor
    # [Ref.: https://unix.stackexchange.com/a/390732/61742]
    if [ "$DELIMITER_P" == "./" ] ; then
        DELIMITER_P="[.]/"
    fi

    if [ ${REMOVE_N} -eq 1 ] ; then

        # NOTE: Due to bash limitations we have some problems getting the 
        # output of a split by awk inside an array and so we need to use 
        # "line break" (\n) to succeed. Seen this, we remove the line breaks 
        # momentarily afterwards we reintegrate them. The problem is that if 
        # there is a line break in the "string" informed, this line break will 
        # be lost, that is, it is erroneously removed in the output! 
        # By Questor
        TARGET_P=$(awk 'BEGIN {RS="dn"} {gsub("\n", "3F2C417D448C46918289218B7337FCAF"); printf $0}' <<< "${TARGET_P}")

    fi

    # NOTE: The replace of "\n" by "3F2C417D448C46918289218B7337FCAF" results 
    # in more occurrences of "3F2C417D448C46918289218B7337FCAF" than the 
    # amount of "\n" that there was originally in the string (one more 
    # occurrence at the end of the string)! We can not explain the reason for 
    # this side effect. The line below corrects this problem! By Questor
    TARGET_P=${TARGET_P%????????????????????????????????}

    SPLIT_NOW=$(awk -F"$DELIMITER_P" '{for(i=1; i<=NF; i++){printf "%s\n", $i}}' <<< "${TARGET_P}")

    while IFS= read -r LINE_NOW ; do
        if [ ${REMOVE_N} -eq 1 ] ; then

            # NOTE: We use "'" to prevent blank lines with no other characters 
            # in the sequence being erroneously removed! We do not know the 
            # reason for this side effect! By Questor
            LN_NOW_WITH_N=$(awk 'BEGIN {RS="dn"} {gsub("3F2C417D448C46918289218B7337FCAF", "\n"); printf $0}' <<< "'${LINE_NOW}'")

            # NOTE: We use the commands below to revert the intervention made 
            # immediately above! By Questor
            LN_NOW_WITH_N=${LN_NOW_WITH_N%?}
            LN_NOW_WITH_N=${LN_NOW_WITH_N#?}

            F_SPLIT_R+=("$LN_NOW_WITH_N")
        else
            F_SPLIT_R+=("$LINE_NOW")
        fi
    done <<< "$SPLIT_NOW"
}

# --------------------------------------
# HOW TO USE
# ----------------

STRING_TO_SPLIT="
 * How do I list all databases and tables using psql?

\"
sudo -u postgres /usr/pgsql-9.4/bin/psql -c \"\l\"
sudo -u postgres /usr/pgsql-9.4/bin/psql <DB_NAME> -c \"\dt\"
\"

\"
\list or \l: list all databases
\dt: list all tables in the current database
\"

[Ref.: /dba/1285/how-do-i-list-all-databases-and-tables-using-psql]


"

f_split "$STRING_TO_SPLIT" "bin/psql -c"

# --------------------------------------
# OUTPUT AND TEST
# ----------------

ARR_LENGTH=${#F_SPLIT_R[*]}
for (( i=0; i<=$(( $ARR_LENGTH -1 )); i++ )) ; do
    echo " > -----------------------------------------"
    echo "${F_SPLIT_R[$i]}"
    echo " < -----------------------------------------"
done

if [ "$STRING_TO_SPLIT" == "${F_SPLIT_R[0]}bin/psql -c${F_SPLIT_R[1]}" ] ; then
    echo " > -----------------------------------------"
    echo "The strings are the same!"
    echo " < -----------------------------------------"
fi

0

बहुस्तरीय तत्वों के लिए, क्यों कुछ पसंद नहीं है

$ array=($(echo -e $'a a\nb b' | tr ' ' '§')) && array=("${array[@]//§/ }") && echo "${array[@]/%/ INTERELEMENT}"

a a INTERELEMENT b b INTERELEMENT

-1

एक और तरीका होगा:

string="Paris, France, Europe"
IFS=', ' arr=(${string})

अब आपके तत्व "गिरफ्तार" सरणी में संग्रहीत हैं। तत्वों के माध्यम से पुनरावृति करने के लिए:

for i in ${arr[@]}; do echo $i; done

1
मैं इस विचार को अपने उत्तर में शामिल करता हूं ; देखना गलत जवाब # 5 (आप विशेष रूप से की मेरी चर्चा में रुचि हो सकती evalचाल)। आपका समाधान $IFSवास्तव में कॉमा-स्पेस वैल्यू के बाद सेट हो जाता है।
bgoldst

-1

चूंकि इसे हल करने के बहुत सारे तरीके हैं, इसलिए हम अपने समाधान में जो देखना चाहते हैं उसे परिभाषित करके शुरू करें।

  1. बैश readarrayइस उद्देश्य के लिए एक बिलिन प्रदान करता है । इसका उपयोग करते हैं।
  2. बदसूरत और अनावश्यक तरकीबों से बचें जैसे कि बदलना IFS, उपयोग करना eval, या एक अतिरिक्त तत्व जोड़ना फिर इसे निकालना।
  3. एक सरल, पठनीय दृष्टिकोण खोजें जो आसानी से समान समस्याओं के लिए अनुकूलित किया जा सके।

readarrayआदेश के लिए सबसे आसान परिसीमक के रूप में नई-पंक्तियों के साथ उपयोग करने के लिए है। अन्य सीमांकक के साथ यह सरणी में एक अतिरिक्त तत्व जोड़ सकता है। सबसे स्वच्छ दृष्टिकोण पहले हमारे इनपुट को एक ऐसे रूप में अनुकूलित करना है जो अच्छी तरह से काम करता हैreadarray इसे पास करने से पहले है।

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

string='  Paris,France  ,   All of Europe  '
readarray -t foo < <(tr ',' '\n' <<< "$string" |sed 's/^ *//' |sed 's/ *$//')
declare -p foo

# declare -a foo='([0]="Paris" [1]="France" [2]="All of Europe")'

-2

एक और तरीका यह हो सकता है:

str="a, b, c, d"  # assuming there is a space after ',' as in Q
arr=(${str//,/})  # delete all occurrences of ','

इसके बाद 'गिरफ्तारी' चार तारों वाला एक सरणी है। इसके लिए IFS से निपटने या किसी अन्य विशेष सामान को पढ़ने की आवश्यकता नहीं है, इसलिए यह बहुत सरल और प्रत्यक्ष है।


अन्य उत्तरों के रूप में समान (दुख की बात है) एंटीपैटर्न: शब्द विभाजन और फ़ाइल नाम विस्तार के अधीन।
gnourf_gniourf
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.