Bash:迭代变量中的行

Ale*_*ing 304 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
Run Code Online (Sandbox Code Playgroud)

这给出了输出:

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
Run Code Online (Sandbox Code Playgroud)

如您所见,回显变量或迭代cat命令会正确地一一打印每一行。但是,第一个 for 循环将所有项目打印在一行上。有任何想法吗?

gle*_*man 370

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

$ list="One\ntwo\nthree\nfour"
$ echo "$list"
One\ntwo\nthree\nfour
$ list=$'One\ntwo\nthree\nfour'
$ echo "$list"
One
two
three
four
Run Code Online (Sandbox Code Playgroud)

如果变量中已经有这样的字符串,则可以使用以下命令逐行读取:

while IFS= read -r line; do
    echo "... $line ..."
done <<< "$list"
Run Code Online (Sandbox Code Playgroud)

  • 请注意,`done &lt;&lt;&lt; "$list"` 是至关重要的 (35认同)
  • `done &lt;&lt;&lt; "$list"` 之所以重要,是因为它会将 "$list" 作为输入传递给 `read` (28认同)
  • 我需要强调这一点:双引号 `$list` 非常重要。 (28认同)
  • 这种方法有缺点,因为 while 循环将在子 shell 中运行:循环中分配的任何变量都不会持续存在。 (15认同)
  • `echo "$list" | 而 ...` 可能比 `... done &lt;&lt;&lt; "$line"` 看起来更清晰 (10认同)
  • 我将如何在灰烬中做到这一点?因为我收到“语法错误:意外重定向”。 (3认同)
  • 让我留个便条并强调这一点:阅读投票最多的答案的评论至关重要。 (2认同)

max*_*ost 105

您可以使用while+ read

some_command | while read line ; do
   echo === $line ===
done
Run Code Online (Sandbox Code Playgroud)

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

  • 请注意,如果您使用此语法,则在循环内分配的变量将不会在循环后保持不变。奇怪的是,glenn jackman 建议的`&lt;&lt;&lt;` 版本确实适用于变量赋值。 (19认同)
  • @Sparhawk 是的,那是因为管道启动了一个执行`while` 部分的子shell。`&lt;&lt;&lt;` 版本没有(至少在新的 bash 版本中)。 (9认同)

小智 49

#!/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
Run Code Online (Sandbox Code Playgroud)

  • @TomaszPosłuszny 不,这会在我的机器上输出 3(计数为 3)行。请注意 IFS 的设置。你也可以使用 `IFS=$'\n'` 来达到同样的效果。 (11认同)
  • 这将输出所有项目,而不是行。 (2认同)

Mat*_*Mat 22

这是执行 for 循环的一种有趣方式:

for item in ${list//\\n/
}
do
   echo "Item: $item"
done
Run Code Online (Sandbox Code Playgroud)

更明智/可读的将是:

cr='
'
for item in ${list//\\n/$cr}
do
   echo "Item: $item"
done
Run Code Online (Sandbox Code Playgroud)

但这太复杂了,你只需要一个空间:

for item in ${list//\\n/ }
do
   echo "Item: $item"
done
Run Code Online (Sandbox Code Playgroud)

您的$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
Run Code Online (Sandbox Code Playgroud)

替换是用空格替换那些,这足以让它在 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
Run Code Online (Sandbox Code Playgroud)

演示:

$ 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
Run Code Online (Sandbox Code Playgroud)

  • 请注意,您可以使用 `$'\n'` 代替定义 `cr`。 (4认同)

Tor*_*rge 7

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

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
Run Code Online (Sandbox Code Playgroud)

这仅在您不想弄乱命令IFS并且也有问题时有用read,因为它可能发生,如果您在循环中调用另一个脚本,该脚本可以在返回之前清空读取缓冲区,就像我发生的那样.