从管道将值读取到外壳变量中


205

我正在尝试让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 test = $ test对我来说效果很好。结果:test = hello world; 在什么环境下运行?我正在使用bash 4.2 ..
alex.pilon

您是否希望一次读取多行?您的示例仅显示一行,但是问题描述不清楚。
Charles Duffy 2012年

2
@ alex.pilon,我正在运行Bash 4.2.25版,他的示例也不适用于我。可能是Bash运行时选项或环境变量的问题吗?我的示例也不适用于Sh,所以Bash可以尝试与Sh兼容吗?
Hibou57 2014年

2
@ Hibou57-我在bash 4.3.25中再次尝试了此操作,它不再起作用。我对此记忆不清,我不确定如何使它正常工作。
alex.pilon 2014年

2
@ Hibou57 @ alex.pilon管道中的最后一个CMD应该影响瓦尔在bash4> = 4.2 shopt -s lastpipe- tldp.org/LDP/abs/html/bashver4.html#LASTPIPEOPT
IMZ -伊万Zakharyaschev

Answers:


162

IFS= read var << EOF
$(foo)
EOF

可以read像这样欺骗从管道中接受:

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

甚至编写这样的函数:

read_from_pipe() { read "$@" <&0; }

但是没有意义-您的变量分配可能不会持续!管道可能会产生一个子外壳,其中环境是通过值而不是通过引用继承的。这就是为什么read不打扰管道输入的原因-它是未定义的。

仅供参考,http://www.etalabs.net/sh_tricks.html是打击bourne贝壳sh的怪异和不兼容所必需的精巧的草皮收藏。


您可以改为执行以下操作:`test =“ echo” hello world“ | {阅读测试;回声$测试; }```
Compholio

2
让我们再试一次(显然,在该标记中转义反引号很有趣):test=`echo "hello world" | { read test; echo $test; }`
Compholio 2012年

请问您为什么使用{}而不是()将这两个命令分组?
尔根·保罗

4
诀窍不是read从管道中获取输入,而是在执行的同一shell中使用变量read
chepner

1
bash permission denied尝试使用此解决方案时得到了。我的情况是完全不同的,但我找不到任何地方它的答案,什么工作对我来说是(不同的例子,但类似的使用)pip install -U echo $(ls -t *.py | head -1)。万一有人遇到类似的问题,并且像我一样偶然发现了这个答案。
ivan_bilan

111

如果您想读取大量数据并分别在每一行上工作,则可以使用以下方法:

cat myFile | while read x ; do echo $x ; done

如果要将线分成多个单词,可以使用多个变量代替x,如下所示:

cat myFile | while read x y ; do echo $y $x ; done

或者:

while read x y ; do echo $y $x ; done < myFile

但是,一旦您开始想对这种事情做任何真正聪明的事情,您最好选择一些脚本语言,例如perl,您可以尝试这样的事情:

perl -ane 'print "$F[0]\n"' < myFile

perl(或者我猜其中的任何一种语言)的学习曲线都相当陡峭,但是从长远来看,如果您想做除最简单的脚本之外的任何事情,就会发现它容易得多。我会推荐《 Perl Cookbook》,当然也推荐Larry Wall等人的《 Perl编程语言》。


8
“替代”是正确的方法。没有UUoC,也没有子shell。参见BashFAQ / 024
暂停,直到另行通知。

43

read不会从管道读取(或者可能会丢失结果,因为管道会创建一个子外壳)。但是,您可以在Bash中使用here字符串:

$ read a b c <<< $(echo 1 2 3)
$ echo $a $b $c
1 2 3

但是,请参阅@chepner的答案以获取有关的信息lastpipe


简洁漂亮的单线,易于理解。这个答案需要更多的投票。
David Parks

42

这是另一种选择

$ read test < <(echo hello world)

$ echo $test
hello world

24
该显著优势,<(..)拥有$(..)的是<(..)回报的每一行调用者只要它执行命令使其可用。 $(..)但是,在命令将任何输出提供给调用者之前,它会等待命令完成并生成其所有输出。
德里克·马哈尔

37

我不是Bash的专家,但是我想知道为什么没有提出这个建议:

stdin=$(cat)

echo "$stdin"

一线证明它对我有用:

$ fortune | eval 'stdin=$(cat); echo "$stdin"'

4
这可能是因为“读取”是bash命令,而cat是将在子进程中启动的单独的二进制文件,因此效率较低。
dj_segfault

10
有时简单和清晰会胜过效率:)
Rondo

5
绝对是最直接的答案
drwatsoncode 2015年

我遇到的问题是,如果不使用管道,脚本将挂起。
Dale Anderson

@DaleA好吧,当然。如果没有任何输入,任何试图从标准输入读取的程序都将“挂起”。
djanowski

27

bash4.2引入了该lastpipe选项,该选项通过在当前外壳程序(而不是子外壳程序)的管道中执行最后一个命令,使您的代码按编写的方式工作。

shopt -s lastpipe
echo "hello world" | read test; echo test=$test

2
啊!很好,这个。如果在交互式外壳中进行测试,还:“ set + m”(。sh脚本中不需要)
XXL

14

从shell命令到bash变量的隐式管道的语法是

var=$(command)

要么

var=`command`

在示例中,您正在将数据传递到赋值语句,该赋值语句不需要任何输入。


4
因为$()可以很容易地嵌套。考虑一下JAVA_DIR = $(dirname $(readlink -f $(which java))),然后尝试使用`。您将需要逃脱三遍!
albfan 2012年

8

第一次尝试非常接近。这种变化应该起作用:

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

输出为:

测试=你好世界

您需要在管道后用大括号将测试分配和回声括起来。

没有花括号,测试的分配(在管道之后)在一个外壳中,而回声“ test = $ test”在一个单独的外壳中,该外壳不知道该分配。这就是为什么在输出中得到“ test =”而不是“ test = hello world”的原因。


8

在我看来,从bash中读取stdin的最佳方法是以下方法,它也使您可以在输入结束之前进行操作:

while read LINE; do
    echo $LINE
done < /dev/stdin

1
在找到这个之前,我几乎疯了。非常感谢分享!
Samy Dindane

6

因为我爱上了它,所以我想写个便条。我找到了这个线程,因为我必须重写一个旧的sh脚本才能与POSIX兼容。这基本上意味着通过重写如下代码来避免POSIX引入的管道/子外壳问题:

some_command | read a b c

变成:

read a b c << EOF
$(some_command)
EOF

像这样的代码:

some_command |
while read a b c; do
    # something
done

变成:

while read a b c; do
    # something
done << EOF
$(some_command)
EOF

但是后者在空输入时的行为并不相同。使用旧的表示法时,while循环不会在空输入中输入,但是在POSIX表示法中是!我认为这是由于EOF之前的换行符,不能省略。行为更像旧符号的POSIX代码如下所示:

while read a b c; do
    case $a in ("") break; esac
    # something
done << EOF
$(some_command)
EOF

在大多数情况下,这应该足够好。但是不幸的是,如果some_command打印一个空行,它的行为仍然与旧记法并不完全一样。在旧的表示法中,while主体被执行,而在POSIX表示法中,我们在主体的前面中断。

解决此问题的方法可能如下所示:

while read a b c; do
    case $a in ("something_guaranteed_not_to_be_printed_by_some_command") break; esac
    # something
done << EOF
$(some_command)
echo "something_guaranteed_not_to_be_printed_by_some_command"
EOF

5

可以从PIPE和命令行参数读取数据的智能脚本:

#!/bin/bash
if [[ -p /proc/self/fd/0 ]]
    then
    PIPE=$(cat -)
    echo "PIPE=$PIPE"
fi
echo "ARGS=$@"

输出:

$ bash test arg1 arg2
ARGS=arg1 arg2

$ echo pipe_data1 | bash test arg1 arg2
PIPE=pipe_data1
ARGS=arg1 arg2

说明:当脚本通过管道接收任何数据时,stdin / proc / self / fd / 0将是到管道的符号链接。

/proc/self/fd/0 -> pipe:[155938]

如果不是,它将指向当前终端:

/proc/self/fd/0 -> /dev/pts/5

bash [[ -p选项可以检查它是否是管道。

cat -从中读取stdin

如果使用cat -no时stdin,它将永远等待,这就是为什么我们将它放在if条件中。


您还可以使用/dev/stdin链接至/proc/self/fd/0
Elie G.

3

将某些东西放入涉及分配的表达式中并非如此。

相反,请尝试:

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

2

如下代码:

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

也可以使用,但是它将在管道之后打开另一个新的子外壳,

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

惯于。


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

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

Bash手册说

最后的管道

如果设置了该选项,并且作业控制未激活,则外壳程序在当前外壳程序环境中运行不在后台执行的管道的最后一个命令。

注意:在非交互式shell中,作业控制默认情况下处于关闭状态,因此您不需要set +m脚本内部。


1

我认为您正在尝试编写一个shell脚本,该脚本可以从stdin中获取输入。但是当您尝试以内联方式进行操作时,尝试创建该test =变量会迷路。我认为以内联方式进行操作没有太大意义,这就是为什么它无法按您期望的方式工作。

我试图减少

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

从各种输入中获取特定行。所以我可以输入...

cat program_file.c | line 34

所以我需要一个能够从stdin读取的小型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 ~ $ 

你去。


-1

这个怎么样:

echo "hello world" | echo test=$(cat)

基本上是我下面的回答中的欺骗。
djanowski

也许,我会认为我的方法稍微更清洁一些,更接近原始问题中发布的代码。
bingles
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.