Bash: วนซ้ำบรรทัดในตัวแปร


225

หนึ่งจะทำซ้ำอย่างถูกต้องผ่านสายในทุบตีทั้งในตัวแปรหรือจากการส่งออกของคำสั่งได้อย่างไร เพียงแค่ตั้งค่าตัวแปร 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คำสั่งจะพิมพ์แต่ละบรรทัดทีละบรรทัดอย่างถูกต้อง อย่างไรก็ตามสิ่งแรกสำหรับการวนซ้ำพิมพ์รายการทั้งหมดในบรรทัดเดียว ความคิดใด ๆ


เพียงความคิดเห็นสำหรับคำตอบทั้งหมด: ฉันต้องทำ $ (echo "$ line" | sed -e 's / ^ [[: space:]] * //') เพื่อตัดอักขระบรรทัดใหม่
servermanfail

คำตอบ:


290

ด้วย bash หากคุณต้องการฝังบรรทัดใหม่ในสตริงให้ล้อมรอบด้วย$'':

$ 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"สิ่งสำคัญคือ
Jason Axelson

21
เหตุผลdone <<< "$list"มีความสำคัญเนื่องจากจะผ่าน "รายการ $" เป็นข้อมูลป้อนเข้าread
wisbucky

22
ฉันต้องเน้นเรื่องนี้: การพูดซ้ำ$listเป็นสิ่งสำคัญมาก
André Chalella

8
echo "$list" | while ...อาจปรากฏชัดเจนยิ่งขึ้นกว่า... done <<< "$line"
kyb

5
มีข้อเสียของวิธีการดังกล่าวเนื่องจาก while loop จะทำงานใน subshell: ตัวแปรใด ๆ ที่กำหนดใน loop จะไม่คงอยู่
เกล็นแจ็

66

คุณสามารถใช้while+ read:

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

Btw -eตัวเลือกที่จะechoเป็นที่ไม่ได้มาตรฐาน ใช้printfแทนถ้าคุณต้องการพกพา


12
โปรดทราบว่าถ้าคุณใช้ไวยากรณ์นี้ตัวแปรที่กำหนดภายในลูปจะไม่ติดหลังจากลูป ผิดปกติพอที่<<<รุ่นที่แนะนำโดยเกล็น Jackman ไม่ทำงานร่วมกับการกำหนดตัวแปร
Sparhawk

7
@Sparhawk ใช่นั่นเป็นเพราะไปป์เริ่ม subshell ดำเนินการwhileส่วน <<<รุ่นไม่ได้ (ในรุ่นใหม่ทุบตีอย่างน้อย)
maxelost

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
สิ่งนี้จะแสดงรายการทั้งหมดไม่ใช่บรรทัด
Tomasz Posłuszny

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ตัวแปรของคุณไม่มีการขึ้นบรรทัดใหม่ มันมีกรณีตาม\ 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 ด้วยบรรทัดใหม่ ... อะไรคือความแตกต่างระหว่างสตริงเดิมและใหม่?
Alex Spurling

@ Alex: ปรับปรุงคำตอบของฉัน - กับรุ่นง่ายเกินไป :-)
จ้า

ฉันลองทำสิ่งนี้แล้วดูเหมือนจะไม่ทำงานหากคุณมีช่องว่างในข้อมูลที่คุณป้อน ในตัวอย่างข้างต้นเรามี "หนึ่ง \ n สองสองสาม" และการทำงานนี้ แต่มันล้มเหลวถ้าเรามี "รายการหนึ่ง \ 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คำสั่งเท่าที่มันสามารถเกิดขึ้นได้ถ้าคุณเรียกสคริปต์อื่นภายในวงที่สคริปต์นั้นสามารถล้างบัฟเฟอร์การอ่านของคุณก่อนที่จะกลับมาเหมือนเดิม .

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.