将值从管道读入shell变量

我试图让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"周围加上""引号,但也不起作用。

382955 次浏览

将一些内容输出到包含赋值的表达式中就不是这样了。

相反,尝试:

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

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

var=$(command)

var=`command`

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

read不会从管道中读取(或者可能因为管道创建了子shell而导致结果丢失)。然而,你可以在Bash中使用here字符串:

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

但有关lastpipe的信息,请参阅@chepner的回答。

如果你想读入大量数据并分别处理每一行,你可以使用这样的方法:

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烹饪书》,当然还有Larry Wall等人写的《Perl编程语言》。

使用

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

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

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

或者写一个这样的函数:

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

但这没有意义——你的可变任务可能不会持久!管道可以生成子shell,其中环境是按值继承的,而不是按引用继承的。这就是为什么read不关心来自管道的输入——它是未定义的。

仅供参考,http://www.etalabs.net/sh_tricks.html是一个漂亮的cruft的集合,必须对抗奇怪和不兼容的bourne炮弹,sh。

这是另一种选择

$ read test < <(echo hello world)


$ echo $test
hello world

我认为你试图写一个shell脚本,可以从stdin输入。 但是当您尝试内联执行时,您在尝试创建test=变量时迷失了方向。 我认为内联做它没有多大意义,这就是为什么它不能以你期望的方式工作

我试着减少

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

从不同的输入中获得特定的行。 所以我可以输入…< / p >

cat program_file.c | line 34

所以我需要一个小shell程序能够从stdin读取。就像你一样。

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 ~ $

好了。

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

stdin=$(cat)


echo "$stdin"

一行程序证明它对我有效:

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

第一次尝试非常接近。这种变化应该是有效的:

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

输出为:

测试= hello world

在管道后面需要用大括号括起要测试的赋值和回显。

如果没有大括号,test的赋值(在管道之后)在一个shell中,而echo "test=$test"在一个单独的shell中,该shell不知道该赋值。这就是为什么在输出中得到"test="而不是"test=hello world"。

bash 4.2引入了lastpipe选项,它允许你的代码像写的那样工作,通过在当前shell中执行管道中的最后一个命令,而不是在子shell中。

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

以下代码:

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手册说:

lastpipe

如果设置,并且作业控制未激活, shell将运行最后一个命令 没有在当前shell的后台执行的管道 环境。< / p >

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

这个怎么样:

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

在我看来,在bash中读取stdin的最好方法是下面的方法,它还可以让你在输入结束之前处理这些行:

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

因为我上当了,我想留个便条。 我发现了这个线程,因为我必须重写一个旧的sh脚本 POSIX兼容。 这基本上意味着绕过POSIX引入的管道/子shell问题,重写如下代码:

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符号中,我们在body前面中断。

解决这个问题的方法是这样的:

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

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

#!/bin/bash
if [[ -p /dev/stdin ]]
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

解释:当脚本通过管道接收任何数据时,/dev/stdin(或/proc/self/fd/0)将是到管道的符号链接。

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

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

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

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

cat -stdin读取。

如果我们在没有stdin的情况下使用cat -,它将永远等待,这就是为什么我们将它放在if条件中。

我想要类似的东西——一个可以解析字符串的函数,可以作为参数或管道传递。

我提出了一个解决方案如下(工作为#!/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.),您需要键入,按enter和CTRL+D结束输入。

问题是如何捕获命令的输出,以保存在变量中,以便稍后在脚本中使用。我可能会重复一些之前的答案,但我试着把我能想到的所有答案排列起来进行比较和评论,所以请容忍我。

直观的构造

echo test | read x
echo x=$x

在Korn shell中是有效的,因为ksh已经实现了管道系列中的最后一个命令是当前shell的一部分。前面的管道命令是子shell。相反,其他shell将所有管道命令定义为子shell,包括最后一个。 这正是我更喜欢ksh的原因。 但必须复制与其他shell, bash f.ex。

.

.

.

要捕获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

& lt; & lt; & lt;$(…)是一个here-document变体,它提供标准命令行的所有元处理。& lt;<(…)是文件替换操作符的输入重定向。

我用"<<<$(“在我所有的脚本中,因为它似乎是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