从管道中读取数值到shell变量中

260

我正在尝试让bash处理从stdin管道传输的数据,但是没有成功。我的意思是以下任何一种方式都不起作用:

echo "hello world" | test=($(< /dev/stdin)); echo test=$test
test=

echo "hello world" | read test; echo test=$test
test=

echo "hello world" | test=`cat`; echo test=$test
test=

我希望输出结果为test=hello world。我尝试过在"$test"周围加上双引号,但也不起作用。


1
你的例子.. echo "hello world" | read test; echo test=$test 对我来说运行良好.. 结果: test=hello world ; 你在什么环境下运行这个程序?我使用的是bash 4.2.. - alex.pilon
3
@alex.pilon,我正在运行Bash 4.2.25版本,他的例子对我也无效。也许这是Bash运行时选项或环境变量的问题?我发现这个例子也不能用Sh执行,所以也许Bash可以尝试与Sh兼容? - Hibou57
2
@Hibou57 - 我在bash 4.3.25中再次尝试了一下,但它不再起作用了。我的记忆有点模糊,我不确定我可能做了什么才能让它工作。 - alex.pilon
2
@Hibou57 @alex.pilon 在bash4>=4.2中,通过shopt -s lastpipe命令,管道中的最后一个命令应该会影响到变量 -- http://tldp.org/LDP/abs/html/bashver4.html#LASTPIPEOPT - imz -- Ivan Zakharyaschev
@alex.pilon,$test变量可能已经绑定了先前的尝试。使用新会话重复此操作将会失败,这是预期的结果。 - Tasos Papastylianou
显示剩余2条评论
17个回答

10
第一次尝试已经很接近了。这个变体应该可行:
echo "hello world" | { test=$(< /dev/stdin); echo "test=$test"; };

输出结果为:

test=hello world

在管道符后面需要加花括号来包含对test的赋值和echo语句。

如果没有花括号,test的赋值(在管道符后面)在一个shell中,而echo "test=$test"在一个不知道这个赋值的独立shell中。这就是为什么输出中出现"test="而不是"test=hello world"。


@KevinBuchs,OP的问题是如何让“bash处理从stdin管道传输的数据”。而OP走得很对,只是忘了在两个shell命令周围加上花括号。当您使用管道时,shell会分叉自身以执行每个部分,但由于变量赋值仅适用于当前shell(或子进程,如果导出),因此当子进程退出时,变量赋值将丢失。花括号将管道后面的shell命令放入单个shell中,因此可以使用该赋值。也许您有其他问题要问吗? - jbuhacoff
你是对的,我的错误。抱歉。 - Kevin Buchs
最佳答案。在这个简短的描述中总结:你有一个由某个脚本返回的数据的[bash]数组,并且希望对该数组的每个成员重复相同的操作。浪费了1个小时解决这个问题。 - undefined

5

将某些内容注入涉及赋值的表达式并不像那样起作用。

相反,尝试使用以下方式:

test=$(echo "hello world"); echo test=$test

4
以下代码:
echo "hello world" | ( test=($(< /dev/stdin)); echo test=$test )

使用这种方式也可以,但它会在管道后打开另一个新的子shell,

echo "hello world" | { test=($(< /dev/stdin)); echo test=$test; }

won't.


我不得不禁用作业控制才能使用chepnars的方法(我是从终端运行此命令的):

set +m;shopt -s lastpipe
echo "hello world" | read test; echo test=$test
echo "hello world" | test="$(</dev/stdin)"; echo test=$test

Bash手册:

lastpipe

如果设置了,并且作业控制不活动,则shell在当前shell环境中运行未在后台执行的管道的最后一个命令。

注意:在非交互式shell中,默认情况下关闭作业控制,因此您不需要在脚本中使用set +m


1
问题是如何捕获命令的输出,以便稍后在脚本中使用变量保存。我可能会重复一些早期的答案,但我会尽力列出我能想到的所有答案进行比较和评论,请耐心等待。
直观的构造方式:
echo test | read x
echo x=$x

由于ksh已经实现了在管道序列中最后一个命令是当前shell的一部分,因此在Korn shell中有效。换句话说,前面的管道命令是子shell。相比之下,其他shell将所有管道命令都定义为子shell,包括最后一个。

这正是我喜欢ksh的确切原因。

但是,必须使用其他shell(例如bash)时,必须使用另一种构造。

要捕获1个值,可以使用以下构造:

x=$(echo test)
echo x=$x

但这只适用于收集1个值以供以后使用。
为了捕获更多的值,此结构在bash和ksh中非常有用:
read x y <<< $(echo test again)
echo x=$x y=$y

我注意到有一种变体在bash中可以工作,但在ksh中不行:

read x y < <(echo test again)
echo x=$x y=$y

<<< $(...)是一种here-document变体,具有标准命令行的所有元处理功能。< <(...)是文件替换运算符的输入重定向。

现在我在所有脚本中都使用"<<< $(",因为它似乎是各种shell变体之间最可移植的构造。我有一套工具集,在任何Unix版本上都可以使用。

当然,还有普遍适用但粗糙的解决方案:

command-1 | {command-2; echo "x=test; y=again" > file.tmp; chmod 700 file.tmp}
. ./file.tmp
rm file.tmp
echo x=$x y=$y

这是一个非常好的解释性答案,但我似乎无法让多值结构正常工作。第一个变量由命令输出的第一行填充,但第二个变量保持为空。您有任何想法我可能错了吗? - Hashim Aziz
@HashimAziz - 你可能需要包含你正在使用的shell代码,这样我们才能帮助你。 - Kevin Buchs

1

我认为您正在尝试编写一个可以从stdin接收输入的shell脚本。 但是,当您尝试在行内执行时,您迷失了自己,试图创建那个test=变量。 我认为在行内执行它没有太多意义,这就是为什么它不能按照您的期望工作的原因。

我正在尝试减少。

$( ... | head -n $X | tail -n 1 )

从各种输入中获取特定行。这样我就可以输入...
cat program_file.c | line 34

所以我需要一个能够从标准输入读取的小型shell程序,就像你一样。

22:14 ~ $ cat ~/bin/line 
#!/bin/sh

if [ $# -ne 1 ]; then echo enter a line number to display; exit; fi
cat | head -n $1 | tail -n 1
22:16 ~ $ 

这就是你要的。


0
我需要类似的东西——一个可以传递参数或者通过管道传递字符串进行解析的函数。
我想到了以下的解决方案(可以作为#!/bin/sh#!/bin/bash使用)。
#!/bin/sh

set -eu

my_func() {
    local content=""
    
    # if the first param is an empty string or is not set
    if [ -z ${1+x} ]; then
 
        # read content from a pipe if passed or from a user input if not passed
        while read line; do content="${content}$line"; done < /dev/stdin

    # first param was set (it may be an empty string)
    else
        content="$1"
    fi

    echo "Content: '$content'"; 
}


printf "0. $(my_func "")\n"
printf "1. $(my_func "one")\n"
printf "2. $(echo "two" | my_func)\n"
printf "3. $(my_func)\n"
printf "End\n"

输出:

0. Content: ''
1. Content: 'one'
2. Content: 'two'
typed text
3. Content: 'typed text'
End

对于最后一种情况(3.),您需要输入并按回车键,然后按CTRL+D来结束输入。

-3
这样怎么样:
echo "hello world" | echo test=$(cat)

基本上是我下面答案的重复。 - djanowski
也许,我会认为我的代码略微更清晰,更接近于原始问题中发布的代码。 - bingles
据我所知,这不会分配任何在完成后持续存在的变量。你试过了吗? - bingles
请忽略我(已删除的)评论。我错了。 - Kevin Buchs
这个 echo 执行了一个赋值操作(但实际上没有转义内容以使其在 eval 中安全执行),但它并不会以使变量在脚本中后续可用的方式执行赋值操作。 - Charles Duffy

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