在bash中将多行输出作为数组捕获

103

如果inner.sh存在

#...
echo first
echo second
echo third

outer.sh

var=`./inner.sh`
# only wants to use "first"...  

如何将var按空格拆分?


1
为什么不使用 for word in $var; do - Dmytro Sirenko
$EarlGray 我猜bash有更直接的方法。 - djechlin
这让我想起了bash对string(3)的“模拟”这里。我确定你会在那里找到一个strtok()函数 :) - davak
3个回答

150

试试这个:

var=($(./inner.sh))

# And then test the array with:

echo ${var[0]}
echo ${var[1]}
echo ${var[2]}

输出:

first
second
third

说明:

  • 你可以通过这样做来创建一个bash数组:var=(first second third),例如。
  • $(./inner.sh) 运行 inner.sh 脚本,它在单独的行上打印出firstsecondthird。由于我们没有在$(...)周围加双引号,它们被合并到同一行上,但是由空格分隔开,因此最终呈现出与上一个小点标记中看起来相同的结果。

4
看一下最后一个答案,这种方法对于这个问题很不错,但是如果你需要更多的东西(比如保持分隔符不变,在每行读取后自动调用函数等),那么使用mapfile会更强大且效率非常高。我已经用许多程序进行了测试,即使是拥有数十万行的文件,我也从未需要等待很久。通常情况下,它几乎是瞬间完成的,只要你使用本地存储 :) - osirisgothra
26
这个答案实际上是将“单词”而不是“行”拆分成数组元素。从技术上讲,它回答了问题,但并不适用于更一般的情况。 测试用例:var=( $( echo "1 2 3" ; echo "4 5 6" ) ) ; echo ${var[0]} 我期望得到“1 2 3”,但它输出了“1”。这要取决于你的用例,mapfile 执行我所需的操作。 - Stéphane Gourichon
@sampson-chen,我如何引用元素的范围子集?var[0:2]不起作用。 - Tom
4
正如@StéphaneGourichon所指出的,这会分割单词而不是行,因此它无法处理空行,这些空行被简单地忽略了,从而有效地破坏了数组中行的顺序。 - ssc
对我没有用。我必须在var=( ...命令前加上IFS=$'\n',然后在下一行使用unset IFS取消bash的内部字段分隔符。 - miu

61

不要忘记内置的mapfile。在您的情况下,它绝对是最有效的:如果您想将整个文件读入一个数组中,该数组的字段将是./inner.sh输出的行。

mapfile -t array < <(./inner.sh)

然后你将拥有数组中的第一行,如${array [0]},以此类推...

有关此内置程序及所有可能选项的更多信息:

help mapfile

如果你只需要将第一行存储在名为 firstline 的变量中,则可以执行以下操作:
read -r firstline < <(./inner.sh)

以下是最有效的方法!

这个小基准测试将证明它:

$ time mapfile -t array < <(for i in {0..100000}; do echo "fdjsakfjds$i"; done)

real    0m1.514s
user    0m1.492s
sys 0m0.560s
$ time array=( $(for i in {0..100000}; do echo "fdjsakfjds$i"; done) )

real    0m2.045s
user    0m1.844s
sys 0m0.308s

如果你只需要使用空格作为分隔符获取 ./inner.sh 输出的第一行的第一个单词,最高效的方法是:
read firstword _ < <(./inner.sh)

4
imo:这里最好的答案是,mapfile比其他方法快得多,我正在处理超过14,000个条目的输出行,mapfile几乎瞬间工作,而不是使用外部程序。 我遵循一个经验法则,从不涉及其他程序来完成语言本身应该完成的工作,bash也不例外,当涉及变量时,这个规则会加倍。 - osirisgothra
4
“WTF”是什么意思?_ brew info mapfile || echo "found $(brew desc -s mapfile | wc -l) references to mapfile" -> 错误:没有mapfile可用的配方。发现0个关于mapfile的引用 让我继续… sh-3.2 $ mapfile -> sh:mapfile:未找到命令 - Alex Gray
5
@alexgray mapfile是Bash≥4内置命令,不支持POSIX shell或Bash<4。看起来你正在使用3.2版本的Bash,你需要安装更新的Bash版本,否则将无法使用该命令。 - gniourf_gniourf
4
这个答案确实将“行”分割成了一个数组。测试案例为:mapfile -t var < <( echo "1 2 3" ; echo "4 5 6" ) ; echo ${var[1]},它正确地输出了“4 5 6”。 - Stéphane Gourichon
1
@ssc 这是为了确保在调用 mapfile 时使用正确的 Bash 版本,因为这在 OS X 上是常见的错误来源。我不知道为什么你的错误信息前缀是 -bash。很抱歉,我没有可用的 OS X。此外,在这里有很多人在 OS X 上使用 mapfile,所以我怀疑是你做错了什么。我只是想帮助你,从基础开始建议你再次检查版本号。现在很抱歉我无法提供更多帮助。 - gniourf_gniourf
显示剩余9条评论

11

你并不是在空格上进行分割,而是在换行符上进行分割。试试这个:

IFS=$'
'

var=$(./echo.sh)
echo $var | awk '{ print $1 }'
unset IFS

我该如何访问var的元素? - djechlin
newvar = $(echo $var | awk '{ print $1 }'),并访问$newvar。 - Arnestig
1
谢谢,我找了很久终于找到了你的好代码片段。为了更好的可读性,你也可以写成IFS=$'\n',并将两个命令放在同一行中,它们之间不需要使用分号,例如:"IFS=$'\n' var=$(./echo.sh)",这样你就不需要在之后取消IFS的设置了。 - splaisan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接