如何从bash中的变量逐行读取?

Eug*_*ash 68 shell bash io cat

我有一个包含命令的多行输出的变量。从变量中逐行读取输出的最有效方法是什么?

例如:

jobs="$(jobs)"
if [ "$jobs" ]; then
    # read lines from $jobs
fi
Run Code Online (Sandbox Code Playgroud)

dog*_*ane 92

您可以使用带有进程替换的 while 循环:

while read -r line
do
    echo "$line"
done < <(jobs)
Run Code Online (Sandbox Code Playgroud)

读取多行变量的最佳方法是设置一个空白IFS变量,并printf使用尾随换行符输入变量:

# Printf '%s\n' "$var" is necessary because printf '%s' "$var" on a
# variable that doesn't end with a newline then the while loop will
# completely miss the last line of the variable.
while IFS= read -r line
do
   echo "$line"
done < <(printf '%s\n' "$var")
Run Code Online (Sandbox Code Playgroud)

注意:根据shellcheck sc2031,使用进程替换比管道更可取,以避免 [巧妙地] 创建子外壳。

另外,请注意,通过命名变量jobs可能会引起混淆,因为这也是常用 shell 命令的名称。

  • 如果你想保留所有的空格,那么使用 `while IFS= read` .... 如果你想防止 \ 解释,那么使用 `read -r` (4认同)

Gil*_*il' 36

逐行处理命令行的输出(解释):

jobs |
while IFS= read -r line; do
  process "$line"
done
Run Code Online (Sandbox Code Playgroud)

如果您已经在变量中有数据:

printf %s "$foo" | …
Run Code Online (Sandbox Code Playgroud)

printf %s "$foo"几乎与 相同echo "$foo",但按$foo字面打印,而如果它以 a 开头,则echo "$foo"可能解释$foo为 echo 命令的一个选项-,并且可能会$foo在某些 shell 中扩展反斜杠序列。

请注意,在某些 shell(ash、bash、pdksh,但不是 ksh 或 zsh)中,管道的右侧在单独的进程中运行,因此您在循环中设置的任何变量都将丢失。例如,以下行计数脚本在这些 shell 中打印 0:

n=0
printf %s "$foo" |
while IFS= read -r line; do
  n=$(($n + 1))
done
echo $n
Run Code Online (Sandbox Code Playgroud)

一种解决方法是将脚本的其余部分(或至少需要$n循环中的值的部分)放在命令列表中:

n=0
printf %s "$foo" | {
  while IFS= read -r line; do
    n=$(($n + 1))
  done
  echo $n
}
Run Code Online (Sandbox Code Playgroud)

如果作用于非空行足够好并且输入不是很大,则可以使用分词:

IFS='
'
set -f
for line in $(jobs); do
  # process line
done
set +f
unset IFS
Run Code Online (Sandbox Code Playgroud)

说明:设置IFS为单个换行符会使分词仅发生在换行符处(与默认设置下的任何空白字符相反)。set -f关闭通配符(即通配符扩展),否则这将发生在命令替换$(jobs)或变量替换的结果中$foo。的for循环作用于所有的片$(jobs),这些都是在命令输出非空行。最后,将通配符和IFS设置恢复为与默认值等效的值。

  • @BjornRoche:在函数内部,使用`local IFS=something`。它不会影响全局范围的值。IIRC,`unset IFS` 不会让你回到默认值(如果事先不是默认值,肯定不起作用)。 (2认同)

小智 18

问题:如果您使用 while 循环,它将在子 shell 中运行并且所有变量都将丢失。解决方案:使用for循环

# change delimiter (IFS) to new line.
IFS_BAK=$IFS
IFS=$'\n'

for line in $variableWithSeveralLines; do
 echo "$line"

 # return IFS back if you need to split new line by spaces:
 IFS=$IFS_BAK
 IFS_BAK=
 lineConvertedToArraySplittedBySpaces=( $line )
 echo "{lineConvertedToArraySplittedBySpaces[0]}"
 # return IFS back to newline for "for" loop
 IFS_BAK=$IFS
 IFS=$'\n'

done 

# return delimiter to previous value
IFS=$IFS_BAK
IFS_BAK=
Run Code Online (Sandbox Code Playgroud)

  • 太感谢了!!上述所有解决方案对我来说都失败了。 (3认同)

l0b*_*0b0 17

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

参考:

  • `-r` 也是一个好主意;它可以防止 `\\` 解释......(它在你的链接中,但它可能值得一提,只是为了完善你的 `IFS=`(这对于防止丢失空格至关重要) (3认同)

seh*_*ehe 10

在最近的 bash 版本中,使用mapfilereadarray有效地将命令输出读入数组

$ readarray test < <(ls -ltrR)
$ echo ${#test[@]}
6305
Run Code Online (Sandbox Code Playgroud)

免责声明:可怕的例子,但你可以自己想出一个比 ls 更好的命令来使用