Bash: Lặp lại các dòng trong một biến


225

Làm thế nào để một lần lặp đúng trên các dòng trong bash hoặc trong một biến hoặc từ đầu ra của một lệnh? Đơn giản chỉ cần đặt biến IFS thành một dòng mới hoạt động cho đầu ra của lệnh chứ không phải khi xử lý một biến chứa các dòng mới.

Ví dụ

#!/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

Điều này cho đầu ra:

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

Như bạn có thể thấy, lặp lại biến hoặc lặp qua catlệnh sẽ in từng dòng một cách chính xác. Tuy nhiên, vòng lặp đầu tiên in tất cả các mục trên một dòng. Có ý kiến ​​gì không?


Chỉ cần một nhận xét cho tất cả các câu trả lời: Tôi đã phải thực hiện $ (echo "$ line" | sed -e 's / ^ [[: space:]] * //') để cắt bớt ký tự dòng mới.
servermanfail

Câu trả lời:


290

Với bash, nếu bạn muốn nhúng dòng mới vào một chuỗi, hãy đính kèm chuỗi với $'':

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

Và nếu bạn đã có một chuỗi như vậy trong một biến, bạn có thể đọc từng chuỗi với:

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

30
Chỉ cần lưu ý rằng điều done <<< "$list"rất quan trọng
Jason Axelson

21
Lý do done <<< "$list"rất quan trọng là bởi vì điều đó sẽ vượt qua "$ list" làm đầu vào choread
wvducky 11/03/2015

22
Tôi cần nhấn mạnh điều này: NHÂN ĐÔI-QUOTING $listlà rất quan trọng.
André Chalella

8
echo "$list" | while ...có thể xuất hiện rõ ràng hơn... done <<< "$line"
kyb

5
Có những nhược điểm của cách tiếp cận đó vì vòng lặp while sẽ chạy trong một khung con: mọi biến được gán trong vòng lặp sẽ không tồn tại.
glenn jackman

66

Bạn có thể sử dụng while+ read:

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

Btw. các -etùy chọn để echolà phi tiêu chuẩn. Sử dụng printfthay thế, nếu bạn muốn tính di động.


12
Lưu ý rằng nếu bạn sử dụng cú pháp này, các biến được gán bên trong vòng lặp sẽ không bị dính sau vòng lặp. Thật kỳ lạ, <<<phiên bản được đề xuất bởi glenn jackman không hoạt động với phép gán biến.
Sparhawk

7
@Sparhawk Có, đó là vì đường ống bắt đầu một lớp con thực hiện whilephần. Các <<<phiên bản không (trong các phiên bản bash mới, ít nhất).
tối đa

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
Điều này xuất ra tất cả các mục, không phải dòng.
Tomasz Posłuszny

4
@ TomaszPosłuszny Không, điều này xuất ra 3 (số lượng là 3) dòng trên máy của tôi. Lưu ý cài đặt của IFS. Bạn cũng có thể sử dụng IFS=$'\n'cho hiệu ứng tương tự.
jmiserez

11

Đây là một cách thú vị để thực hiện vòng lặp của bạn:

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

Một chút hợp lý / dễ đọc hơn sẽ là:

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

Nhưng đó là tất cả quá phức tạp, bạn chỉ cần một không gian trong đó:

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

Bạn $linebiến không chứa ký tự dòng mới. Nó chứa các trường hợp \theo sau n. Bạn có thể thấy rõ điều đó với:

$ 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

Sự thay thế đang thay thế những cái có khoảng trắng, đủ để nó hoạt động trong các vòng lặp:

$ 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

Bản giới thiệu:

$ 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

Thật thú vị, bạn có thể giải thích những gì đang xảy ra ở đây? Có vẻ như bạn đang thay thế \ n bằng một dòng mới ... Sự khác biệt giữa chuỗi gốc và chuỗi mới là gì?
Alex Spurling

@Alex: đã cập nhật câu trả lời của tôi - với phiên bản đơn giản hơn :-)
Mat

Tôi đã thử điều này và nó dường như không hoạt động nếu bạn có khoảng trống trong đầu vào của bạn. Trong ví dụ trên, chúng ta có "one \ ntwo \ nthree" và điều này hoạt động nhưng không thành công nếu chúng ta có "entry one \ nentry Two \ nentry ba" vì nó cũng thêm một dòng mới cho khoảng trắng.
John Rocha

2
Chỉ cần một lưu ý rằng thay vì xác định crbạn có thể sử dụng $'\n'.
devios1

1

Trước tiên, bạn cũng có thể chuyển đổi biến thành một mảng, sau đó lặp lại điều này.

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

Điều này chỉ hữu ích nếu bạn không muốn gây rối với IFSvà cũng có vấn đề với readlệnh, vì nó có thể xảy ra, nếu bạn gọi một tập lệnh khác trong vòng lặp mà tập lệnh đó có thể làm trống bộ đệm đọc của bạn trước khi quay lại, như nó đã xảy ra với tôi .

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.