Bash:遍历变量中的行


225

如何在变量中或从命令输出中正确地遍历bash中的行?只需将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命令中回显变量或迭代将正确打印每一行。但是,第一个for循环将所有项目打印在一行上。有任何想法吗?


只是对所有答案的评论:我必须做$(echo“ $ line” | sed -e's / ^ [[:space:]] * //'),以修剪换行符。
servermanfail

Answers:


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"至关重要的原因是因为它将“ $ list”作为输入传递给read
wisbucky

22
我需要强调一下:双重排队$list非常关键。
安德烈Chalella

8
echo "$list" | while ...看起来比... done <<< "$line"
-kyb

5
该方法有一些缺点,因为while循环将在子shell中运行:循环中分配的任何变量都不会持久。
格伦·杰克曼

66

您可以使用while+ read

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

顺便说一句。的-e选项echo是非标准的。printf如果您要携带,请改用。


12
请注意,如果使用此语法,则循环内分配的变量不会在循环后停留。奇怪的是,<<<glenn jackman建议的版本确实适用于变量赋值。
Sparhawk

7
@Sparhawk是的,这是因为管道启动了执行该while零件的子Shell 。该<<<版本没有(至少在新的bash版本中)。
maxelost 2013年

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
这将输出所有项目,而不是行。
TomaszPosłuszny15年

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

替换是用空格替换空格,足以在for循环中工作:

$ 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:更新了我的答案-也使用了更简单的版本:-)
Mat

我尝试了一下,如果输入中有空格,它似乎无法正常工作。在上面的示例中,我们有“ one \ ntwo \ nthree”,但是如果我们有“ entry 1 \ entry 2 \ entry 3”,它会失败,因为它也为该空间添加了新行。
约翰·罗沙

2
请注意,cr您可以使用代替定义$'\n'
devios1 2013年

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

这仅在您不希望弄乱the IFS并且还read遇到命令问题(如可能发生)时有用,如果您在循环内调用另一个脚本,该脚本可以在返回之前清空您的读取缓冲区,就像发生在我身上。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.