【问题标题】:Capturing multiple line output into a Bash variable将多行输出捕获到 Bash 变量中
【发布时间】:2010-10-11 10:40:25
【问题描述】:

我有一个脚本“myscript”,它输出以下内容:

abc
def
ghi

在另一个脚本中,我调用:

declare RESULT=$(./myscript)

$RESULT 获取值

abc def ghi

有没有办法用换行符或“\n”字符存储结果,以便我可以用“echo -e”输出它?

【问题讨论】:

  • 这让我很惊讶。你没有 $(cat ./myscipt) 吗?否则我会期望它尝试执行命令 abc、def 和 ghi
  • @litb:是的,我想是的;您还可以使用 $(<.>
  • (注意:上面的两个 cmets 指的是开始的问题的修订版我有一个包含以下内容的脚本“myscript”,这导致了问题.问题的当前修订版(我有一个脚本'myscript'输出以下内容)使cmets变得多余。但是,修订版是从2011-11-11开始的,在两者之后很久制作了厘米。

标签: bash variables


【解决方案1】:

实际上,RESULT 包含了你想要的——用来展示:

echo "$RESULT"

你所展示的就是你从中得到的:

echo $RESULT

如 cmets 中所述,不同之处在于 (1) 变量 (echo "$RESULT") 的双引号版本保留了值的内部间距,与变量中表示的值完全相同 - 换行符、制表符、多个空格和所有 - 而 (2) 未引用版本 (echo $RESULT) 用一个空格替换一个或多个空格、制表符和换行符的每个序列。因此 (1) 保留输入变量的形状,而 (2) 创建一个可能非常长的单行输出,其中“单词”由单个空格分隔(其中“单词”是非空白字符的序列;不需要'不是任何单词中的任何字母数字)。

【讨论】:

  • @troelskn:不同之处在于(1)变量的双引号版本保留了值的内部间距,与变量、换行符、制表符、多个空格等所表示的完全一样,而 (2) 未加引号的版本将一个或多个空格、制表符和换行符的每个序列替换为一个空格。因此 (1) 保留输入变量的形状,而 (2) 创建一个可能非常长的单行输出,其中“单词”由单个空格分隔(其中“单词”是非空白字符的序列;不需要'不是任何单词中的任何字母数字)。
  • 为了让答案更容易理解:答案告诉 echo "$RESULT" 保留换行符,而 echo $RESULT 没有。
  • 这在某些情况下无法保留换行符和前导空格。
  • @CommaToast:你会详细说明吗?尾随换行符丢失;没有一个简单的方法。前导空格——我不知道在什么情况下它们会丢失。
【解决方案2】:

另一个陷阱是command substitution$() — 去除尾随的换行符。可能并不总是很重要,但如果你真的想准确地保留输出的内容,你将不得不使用另一行和一些引用:

RESULTX="$(./myscript; echo x)"
RESULT="${RESULTX%x}"

如果您想handle all possible filenames,这一点尤其重要(以避免未定义的行为,例如操作错误的文件)。

【讨论】:

  • 我不得不用一个破损的外壳工作了一段时间,它没有从command substitution 中删除最后一个换行符(它是不是 process substitution),它坏了几乎所有的东西。例如,如果您输入了pwd=`pwd`; ls $pwd/$file,则在/ 之前有一个换行符,并且用双引号将名称括起来没有帮助。很快就修好了。这可以追溯到 1983-5 年 ICL Perq PNX 的时间框架; shell 没有 $PWD 作为内置变量。
【解决方案3】:

如果您对特定行感兴趣,请使用结果数组:

declare RESULT=($(./myscript))  # (..) = array
echo "First line: ${RESULT[0]}"
echo "Second line: ${RESULT[1]}"
echo "N-th line: ${RESULT[N]}"

【讨论】:

  • 如果行中有空格,这将计算字段(空格之间的内容)而不是行。
  • 您将使用readarray 和进程替换而不是命令替换:readarray -t RESULT &lt; &lt;(./myscript&gt;
【解决方案4】:

除了@l0b0 给出的答案之外,我还遇到了一种情况,即我需要保留脚本输出的任何尾随换行符检查脚本的返回码。 l0b0 的答案的问题是“echo x”正在重置 $?归零...所以我设法想出了这个非常狡猾的解决方案:

RESULTX="$(./myscript; echo x$?)"
RETURNCODE=${RESULTX##*x}
RESULT="${RESULTX%x*}"

【讨论】:

  • 这很可爱,我实际上有一个用例,我想要一个die () 函数,它接受任意退出代码和可选的消息,我想使用另一个 usage () 函数来提供消息,但换行符不断被挤压,希望这能让我解决这个问题。
【解决方案5】:

解析多个输出

简介

所以你的myscript 输出 3 行,可能看起来像:

myscript() { echo $'abc\ndef\nghi'; }

myscript() { local i; for i in abc def ghi ;do echo $i; done ;}

好的,这是一个函数,不是脚本(不需要路径./),但是输出是一样的

myscript
abc
def
ghi

考虑结果代码

为了检查结果代码,测试函数将变为:

myscript() { local i;for i in abc def ghi ;do echo $i;done;return $((RANDOM%128));}

1。将多个输出存储在一个变量中,显示 换行符

你的操作是正确的:

RESULT=$(myscript)

关于结果代码,可以补充:

RCODE=$?

即使在同一行:

RESULT=$(myscript) RCODE=$?

然后

echo $RESULT $RCODE
abc def ghi 66

echo "$RESULT"
abc
def
ghi

echo ${RESULT@Q}
$'abc\ndef\nghi'

printf "%q\n" "$RESULT"
$'abc\ndef\nghi'

但要显示变量定义,请使用declare -p:

declare -p RESULT RCODE
declare -- RESULT="abc
def
ghi"
declare -- RCODE="66"

2。解析数组中的多个输出,使用mapfile

将答案存储到myvar 变量中:

mapfile -t myvar < <(myscript)
echo ${myvar[2]}
ghi

显示$myvar:

declare -p myvar
declare -a myvar=([0]="abc" [1]="def" [2]="ghi")

考虑结果代码

如果您必须检查结果代码,您可以:

RESULT=$(myscript) RCODE=$?
mapfile -t myvar <<<"$RESULT"

declare -p myvar RCODE
declare -a myvar=([0]="abc" [1]="def" [2]="ghi")
declare -- RCODE="40"

3。在命令组中通过连续read 解析多个输出

{ read firstline; read secondline; read thirdline;} < <(myscript)
echo $secondline
def

显示变量:

declare -p firstline secondline thirdline
declare -- firstline="abc"
declare -- secondline="def"
declare -- thirdline="ghi"

我经常使用:

{ read foo;read foo total use free foo ;} < <(df -k /)

然后

declare -p use free total
declare -- use="843476"
declare -- free="582128"
declare -- total="1515376"

考虑结果代码

相同的前置步骤:

RESULT=$(myscript) RCODE=$?
{ read firstline; read secondline; read thirdline;} <<<"$RESULT"

declare -p firstline secondline thirdline RCODE
declare -- firstline="abc"
declare -- secondline="def"
declare -- thirdline="ghi"
declare -- RCODE="50"

【讨论】:

  • 查看help readhelp mapfile 以了解有关换行符反斜杠和其他专业的选项!!
【解决方案6】:

在这里尝试了大多数解决方案后,我发现最简单的事情是显而易见的 - 使用临时文件。我不确定您想对多行输出做什么,但是您可以使用 read 逐行处理它。您唯一不能真正做的事情就是轻松地将它们全部放在同一个变量中,但对于大多数实际目的而言,这更容易处理。

./myscript.sh > /tmp/foo
while read line ; do 
    echo 'whatever you want to do with $line'
done < /tmp/foo

快速破解以使其执行请求的操作:

result=""
./myscript.sh > /tmp/foo
while read line ; do
  result="$result$line\n"
done < /tmp/foo
echo -e $result

请注意,这会增加一行。如果你在它上面工作,你可以围绕它编写代码,我太懒了。


编辑:虽然这个案例运行良好,但阅读本文的人应该知道,您可以轻松地将标准输入压缩到 while 循环中,从而为您提供一个运行一行、清除标准输入并退出的脚本。我认为像 ssh 会那样做吗?最近才看到,其他代码示例在这里:https://unix.stackexchange.com/questions/24260/reading-lines-from-a-file-with-bash-for-vs-while

再来一次!这次使用不同的文件句柄(stdin、stdout、stderr 是 0-2,所以我们可以在 bash 中使用 &3 或更高)。

result=""
./test>/tmp/foo
while read line  <&3; do
    result="$result$line\n"
done 3</tmp/foo
echo -e $result

您也可以使用 mktemp,但这只是一个简单的代码示例。 mktemp 的用法如下:

filenamevar=`mktemp /tmp/tempXXXXXX`
./test > $filenamevar

然后像使用文件的实际名称一样使用 $filenamevar。可能这里不需要解释,但是在cmets中有人抱怨。

【讨论】:

  • 我也尝试了其他解决方案,根据您的第一个建议,我终于让我的脚本工作了
  • 投反对票:这太复杂了,无法避免多个常见的bash pitfalls
  • 你前几天有人告诉我奇怪的标准输入文件句柄问题,我就像“哇”。让我快速添加一些东西。
  • 在 Bash 中,您可以使用 read 命令扩展 -u 3 从文件描述符 3 中读取。
  • 除了@JonathanLeffler 评论,您还可以写:while read -ru $testout line;do echo "do something with $line";done {testout}&lt; &lt;(./test) 而不是创建临时文件!
【解决方案7】:

这样怎么样,它会将每一行读取到一个变量中,然后可以使用它! 假设 myscript 输出被重定向到一个名为 myscript_output 的文件

awk '{while ( (getline var < "myscript_output") >0){print var;} close ("myscript_output");}'

【讨论】:

  • 嗯,那不是 bash,那是 awk。