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