Bash:遍历变量中的行

Bash:遍历变量中的行

如何正确地在 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 循环将所有项目打印在一行上。有什么想法吗?

答案1

使用 bash 时,如果要在字符串中嵌入换行符,请将字符串括在以下内容中$''

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

如果变量中已经有这样的字符串,则可以逐行读取它:

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

<<<@wheeler 对添加尾随换行符提出了很好的观点。

假设变量以换行符结尾

list=$'One\ntwo\nthree\nfour\n'

然后 while 循环输出

while IFS= read -r line; do
    echo "... $line ..."
done <<< "$list"
... One ...
... two ...
... three ...
... four ...
...  ...

为了解决这个问题,请使用进程替换重定向,而不是此处的字符串

while IFS= read -r line; do
    echo "... $line ..."
done < <(printf '%s' "$list")
... One ...
... two ...
... three ...
... four ...

但现在这对于字符串来说“失败”了没有尾随换行符

list2=$'foo\nbar\nbaz'
while IFS= read -r line; do
    echo "... $line ..."
done < <(printf '%s' "$list2")
... foo ...
... bar ...

read文档

退出状态为零,除非遇到文件结尾

并且因为输入没有以换行符结束,所以在read得到整行之前会遇到 EOF。read退出时为非零,并且 while 循环完成。

但是字符会被消耗到变量中。

因此,循环遍历字符串行的绝对正确的方法是:

while IFS= read -r line || [[ -n $line ]]; do
    echo "... $line ..."
done < <(printf '%s' "$list2")

$list这将输出和的预期结果$list2

答案2

您可以使用while+ read

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

顺便说一句,-e选项echo是非标准的。printf如果您想要可移植性,请使用。

答案3

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

答案4

您也可以先将变量转换为数组,然后对其进行迭代。

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,因为如果您在循环内调用另一个脚本,该脚本可以在返回之前清空您的读取缓冲区,就像我遇到的情况一样。

相关内容