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

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

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

例如:

jobs="$(jobs)"
if [ "$jobs" ]; then
    # read lines from $jobs
fi

答案1

您可以使用 while 循环进行进程替换:

while read -r line
do
    echo "$line"
done < <(jobs)

读取多行变量的最佳方法是设置一个空白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")

注:按照shellcheck sc2031,使用进程替换优于管道,以避免[巧妙地]创建子shell。

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

答案2

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

jobs |
while IFS= read -r line; do
  process "$line"
done

如果变量中已有数据:

printf %s "$foo" | …

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

请注意,在某些 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

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

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

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

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

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

答案3

jobs="$(jobs)"
while IFS= read -r line
do
    echo "$line"
done <<< "$jobs"

参考:

答案4

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

$ readarray test < <(ls -ltrR)
$ echo ${#test[@]}
6305

免责声明:可怕的例子,但你可能会想出一个比 ls 更好的命令

相关内容