बैश: एक चर में लाइनों पर फेरबदल


225

कैसे ठीक से एक चर में या एक कमांड के आउटपुट से बैश में लाइनों पर iterate करता है? बस IFS वेरिएबल को एक नई लाइन पर सेट करना एक कमांड के आउटपुट के लिए काम करता है, लेकिन किसी ऐसे वेरिएबल को प्रोसेस करते समय जिसमें नई लाइनें नहीं होती हैं।

उदाहरण के लिए

#!/bin/bash

list="One\ntwo\nthree\nfour"

#Print the list with echo
echo -e "echo: \n$list"

#Set the field separator to new line
IFS=$'\n'

#Try to iterate over each line
echo "For loop:"
for item in $list
do
        echo "Item: $item"
done

#Output the variable to a file
echo -e $list > list.txt

#Try to iterate over each line from the cat command
echo "For loop over command output:"
for item in `cat list.txt`
do
        echo "Item: $item"
done

यह आउटपुट देता है:

echo: 
One
two
three
four
For loop:
Item: One\ntwo\nthree\nfour
For loop over command output:
Item: One
Item: two
Item: three
Item: four

जैसा कि आप देख सकते हैं, चर प्रतिध्वनित कर रहा है या catकमांड पर एक-एक करके सभी लाइनों को सही ढंग से प्रिंट करता है। हालांकि, लूप के लिए पहला एक लाइन पर सभी आइटम प्रिंट करता है। कोई विचार?


सभी उत्तरों के लिए बस एक टिप्पणी: मुझे न्यूलाइन वर्ण को ट्रिम करने के लिए $ (इको "$ लाइन" | sed -e 's / ^ [[: space:]] * //') करना था।
सर्वरमैनफेल

जवाबों:


290

बैश के साथ, यदि आप एक स्ट्रिंग में नए सिरे से एम्बेड करना चाहते हैं, तो स्ट्रिंग को संलग्न करें $'':

$ list="One\ntwo\nthree\nfour"
$ echo "$list"
One\ntwo\nthree\nfour
$ list=$'One\ntwo\nthree\nfour'
$ echo "$list"
One
two
three
four

और अगर आपके पास एक चर में पहले से ही ऐसी स्ट्रिंग है, तो आप इसे लाइन-बाय-लाइन के साथ पढ़ सकते हैं:

while read -r line; do
    echo "... $line ..."
done <<< "$list"

30
बस एक नोट जो कि done <<< "$list"महत्वपूर्ण है
जेसन एक्सलसन

21
इसका कारण done <<< "$list"यह है कि यह महत्वपूर्ण है क्योंकि यह "$ सूची" को इनपुट के रूप में पारित कर देगाread
समझदार

22
मुझे इस पर जोर देने की जरूरत है: डबल-क्वोटिंग $listबहुत महत्वपूर्ण है।
एंड्रे चलेला

8
echo "$list" | while ...से अधिक स्पष्ट दिखाई दे सकता है... done <<< "$line"
kyb

5
उस दृष्टिकोण के लिए डाउनसाइड हैं क्योंकि लूप एक उप-भाग में चलेगा: लूप में निर्दिष्ट कोई भी चर स्थिर नहीं रहेगा।
ग्लेन जैकमैन

66

आप while+ का उपयोग कर सकते हैं read:

some_command | while read line ; do
   echo === $line ===
done

Btw। -eकरने के लिए विकल्प echoअमानक है। printfयदि आप पोर्टेबिलिटी चाहते हैं, तो इसके बजाय उपयोग करें ।


12
ध्यान दें कि यदि आप इस सिंटैक्स का उपयोग करते हैं, तो लूप के अंदर दिए गए चर लूप के बाद चिपक नहीं पाएंगे। अजीब तरह से पर्याप्त, <<<संस्करण ग्लेन जैकमैन ने सुझाव दिया है चर काम के साथ काम करते हैं।
स्पार्कहॉक

7
@ श्रावक हां, ऐसा इसलिए है क्योंकि पाइप whileभाग को निष्पादित करने वाला एक उपखंड शुरू करता है । <<<संस्करण नहीं है (नई पार्टी के संस्करणों में, कम से कम)।
मैक्सलॉस्ट

26
#!/bin/sh

items="
one two three four
hello world
this should work just fine
"

IFS='
'
count=0
for item in $items
do
  count=$((count+1))
  echo $count $item
done

2
यह सभी वस्तुओं का उत्पादन करता है, लाइनों का नहीं।
टॉमाज़ पॉज़ुस्ज़नी

4
@ TomaszPosłuszny नहीं, यह मेरी मशीन पर 3 (गिनती 3) लाइनों का आउटपुट है। IFS की सेटिंग पर ध्यान दें। आप IFS=$'\n'एक ही प्रभाव के लिए भी उपयोग कर सकते हैं ।
jmiserez

11

यहाँ अपने पाश के लिए एक मजेदार तरीका है:

for item in ${list//\\n/
}
do
   echo "Item: $item"
done

थोड़ा अधिक समझदार / पठनीय होगा:

cr='
'
for item in ${list//\\n/$cr}
do
   echo "Item: $item"
done

लेकिन यह सब बहुत जटिल है, आपको केवल वहां एक स्थान की आवश्यकता है:

for item in ${list//\\n/ }
do
   echo "Item: $item"
done

आप $lineचर में newlines नहीं है। इसके \बाद इसके उदाहरण हैं n। आप इसे स्पष्ट रूप से देख सकते हैं:

$ cat t.sh
#! /bin/bash
list="One\ntwo\nthree\nfour"
echo $list | hexdump -C

$ ./t.sh
00000000  4f 6e 65 5c 6e 74 77 6f  5c 6e 74 68 72 65 65 5c  |One\ntwo\nthree\|
00000010  6e 66 6f 75 72 0a                                 |nfour.|
00000016

प्रतिस्थापन उन स्थानों के साथ बदल रहा है, जो छोरों के लिए काम करने के लिए पर्याप्त है:

$ cat t.sh
#! /bin/bash
list="One\ntwo\nthree\nfour"
echo ${list//\\n/ } | hexdump -C

$ ./t.sh 
00000000  4f 6e 65 20 74 77 6f 20  74 68 72 65 65 20 66 6f  |One two three fo|
00000010  75 72 0a                                          |ur.|
00000013

डेमो:

$ cat t.sh
#! /bin/bash
list="One\ntwo\nthree\nfour"
echo ${list//\\n/ } | hexdump -C
for item in ${list//\\n/ } ; do
    echo $item
done

$ ./t.sh 
00000000  4f 6e 65 20 74 77 6f 20  74 68 72 65 65 20 66 6f  |One two three fo|
00000010  75 72 0a                                          |ur.|
00000013
One
two
three
four

दिलचस्प है, क्या आप बता सकते हैं कि यहां क्या चल रहा है? ऐसा लगता है कि आप नई पंक्ति के साथ \ n का स्थान ले रहे हैं ... मूल स्ट्रिंग और नए के बीच क्या अंतर है?
एलेक्स स्पर्लिंग

@ एलेक्स: मेरे जवाब को अपडेट किया - एक सरल संस्करण के साथ भी :-)
मैट

मैंने इसे आज़माया और अगर आपके इनपुट में जगह है तो यह काम नहीं करता। ऊपर के उदाहरण में हमारे पास "एक \ ntwo \ nthree" है और यह काम करता है लेकिन यह विफल रहता है अगर हमारे पास "एक प्रविष्टि \ nentry दो \ nentry तीन" है क्योंकि यह अंतरिक्ष के लिए एक नई पंक्ति भी जोड़ता है।
जॉन रोचा

2
बस ध्यान दें कि परिभाषित करने के बजाय crआप उपयोग कर सकते हैं $'\n'
devios1

1

आप पहले चर को एक सरणी में बदल सकते हैं, फिर इस पर पुनरावृति कर सकते हैं।

lines="abc
def
ghi"

declare -a theArray

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

for line in "${theArray[@]}"
do
    echo "$line"
    #Do something complex here that would break your read loop
done

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

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