从管道中将值读入shell变量

时间:2010-04-30 17:51:55

标签: linux bash pipe

我正在尝试让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"附近加上“引号”也不起作用。

17 个答案:

答案 0 :(得分:150)

使用

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是对抗伯恩贝壳怪异和不相容所必需的琐事.h /

答案 1 :(得分:105)

如果您想要阅读大量数据并分别处理每一行,您可以使用以下内容:

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编程语言。

答案 2 :(得分:37)

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

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

但请参阅@ chepner的答案,了解有关lastpipe的信息。

答案 3 :(得分:36)

这是另一种选择

$ read test < <(echo hello world)

$ echo $test
hello world

答案 4 :(得分:29)

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

stdin=$(cat)

echo "$stdin"

单行证明它对我有用:

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

答案 5 :(得分:20)

bash 4.2引入了lastpipe选项,它允许您的代码按照写入的方式工作,方法是在当前shell的管道中执行最后一个命令,而不是子shell。

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

答案 6 :(得分:12)

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

var=$(command)

var=`command`

在您的示例中,您将数据传递给赋值语句,该语句不期望任何输入。

答案 7 :(得分:6)

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

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

,输出为:

  

test = hello world

管道后需要括号来包含要测试的分配和回声。

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

答案 8 :(得分:5)

在我看来,在bash中读取stdin的最佳方法是以下方法,它还允许您在输入结束之前处理这些行:

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

答案 9 :(得分:4)

因为我堕落了,所以我想留言。 我找到了这个帖子,因为我必须重写一个旧的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表示法中,我们在身体前面打破。

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

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

答案 10 :(得分:3)

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

相反,请尝试:

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

答案 11 :(得分:2)

以下代码:

sponge

也会工作,但它会在管道之后打开另一个新的子shell,其中

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

赢得&#39;吨

我必须禁用作业控制才能使用chepnars&#39;方法(我从终端运行此命令):

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

Bash Manual says

  

lastpipe

     

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

注意:默认情况下,在非交互式shell中关闭作业控制,因此您不需要脚本中的set +m;shopt -s lastpipe echo "hello world" | read test; echo test=$test echo "hello world" | test="$(</dev/stdin)"; echo test=$test

答案 12 :(得分:1)

我认为您正在尝试编写一个可以从stdin获取输入的shell脚本。 但是当你尝试它内联时,你迷失了尝试创建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 ~ $ 
你去吧。

答案 13 :(得分:0)

可以同时从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

说明::当脚本通过管道接收任何数据时,标准输入/ proc / self / fd / 0将成为管道的符号链接。

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

如果没有,它将指向当前终端:

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

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

cat -stdin中读取。

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

答案 14 :(得分: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.),您需要输入,按Enter键并按CTRL + D结束输入。

答案 15 :(得分:0)

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

直观的构造

echo test | read x
echo x=$x

在 Korn shell 中是有效的,因为 ksh 已经实现了管道系列中的最后一个命令是当前 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

答案 16 :(得分:-1)

这个怎么样:

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