从管道将值读入 shell 变量

发布于 2024-08-30 13:20:28 字数 350 浏览 8 评论 0原文

我试图让 bash 处理从管道输入的标准输入的数据,但没有运气。我的意思不是以下任何工作:

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

I am trying to get bash to process data from stdin that gets piped into, but no luck. What I mean is none of the following work:

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=

where I want the output to be test=hello world. I've tried putting "" quotes around "$test" that doesn't work either.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(17

少女的英雄梦 2024-09-06 13:20:28

使用

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,sh。

Use

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

You can trick read into accepting from a pipe like this:

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

or even write a function like this:

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

But there's no point - your variable assignments may not last! A pipeline may spawn a subshell, where the environment is inherited by value, not by reference. This is why read doesn't bother with input from a pipe - it's undefined.

FYI, http://www.etalabs.net/sh_tricks.html is a nifty collection of the cruft necessary to fight the oddities and incompatibilities of bourne shells, sh.

愛上了 2024-09-06 13:20:28

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

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 等人的 The Perl 编程语言。

if you want to read in lots of data and work on each line separately you could use something like this:

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

if you want to split the lines up into multiple words you can use multiple variables in place of x like this:

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

alternatively:

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

But as soon as you start to want to do anything really clever with this sort of thing you're better going for some scripting language like perl where you could try something like this:

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

There's a fairly steep learning curve with perl (or I guess any of these languages) but you'll find it a lot easier in the long run if you want to do anything but the simplest of scripts. I'd recommend the Perl Cookbook and, of course, The Perl Programming Language by Larry Wall et al.

仄言 2024-09-06 13:20:28

这是另一种选择

$ read test < <(echo hello world)

$ echo $test
hello world

This is another option

$ read test < <(echo hello world)

$ echo $test
hello world
套路撩心 2024-09-06 13:20:28

read 不会从管道中读取(或者可能会因为管道创建子 shell 而导致结果丢失)。但是,您可以在 Bash 中使用此处的字符串:

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

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

read won't read from a pipe (or possibly the result is lost because the pipe creates a subshell). You can, however, use a here string in Bash:

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

But see @chepner's answer for information about lastpipe.

画中仙 2024-09-06 13:20:28

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

stdin=$(cat)

echo "$stdin"

单行证明它对我有用:

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

I'm no expert in Bash, but I wonder why this hasn't been proposed:

stdin=$(cat)

echo "$stdin"

One-liner proof that it works for me:

$ fortune | eval 'stdin=$(cat); echo "$stdin"'
月亮是我掰弯的 2024-09-06 13:20:28

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

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

bash 4.2 introduces the lastpipe option, which allows your code to work as written, by executing the last command in a pipeline in the current shell, rather than a subshell.

shopt -s lastpipe
echo "hello world" | read test; echo test=$test
变身佩奇 2024-09-06 13:20:28

一个智能脚本,可以从 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 条件中。

A smart script that can both read data from PIPE and command line arguments:

#!/bin/bash
if [[ -p /dev/stdin ]]
    then
    PIPE=$(cat -)
    echo "PIPE=$PIPE"
fi
echo "ARGS=$@"

Output:

$ bash test arg1 arg2
ARGS=arg1 arg2

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

Explanation: When a script receives any data via pipe, then the /dev/stdin (or /proc/self/fd/0) will be a symlink to a pipe.

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

If not, it will point to the current terminal:

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

The bash [[ -p option can check it it is a pipe or not.

cat - reads the from stdin.

If we use cat - when there is no stdin, it will wait forever, that is why we put it inside the if condition.

時窥 2024-09-06 13:20:28

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

var=$(command)

or

var=`command`

在您的示例中,您将数据通过管道传输到赋值语句,该语句不需要任何输入。

The syntax for an implicit pipe from a shell command into a bash variable is

var=$(command)

or

var=`command`

In your examples, you are piping data to an assignment statement, which does not expect any input.

猫烠⑼条掵仅有一顆心 2024-09-06 13:20:28

在我看来,在 bash 中读取 stdin 的最佳方法是以下一种,它还可以让您在输入结束之前处理行:

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

In my eyes the best way to read from stdin in bash is the following one, which also lets you work on the lines before the input ends:

while read LINE; do
    echo $LINE
done < /dev/stdin
冷了相思 2024-09-06 13:20:28

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

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

并且输出是:

测试=你好世界

您需要在管道后面使用大括号来括起要测试的分配和回显。

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

The first attempt was pretty close. This variation should work:

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

and the output is:

test=hello world

You need braces after the pipe to enclose the assignment to test and the echo.

Without the braces, the assignment to test (after the pipe) is in one shell, and the echo "test=$test" is in a separate shell which doesn't know about that assignment. That's why you were getting "test=" in the output instead of "test=hello world".

断爱 2024-09-06 13:20:28

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

some_command | read a b c

into:

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

和如下代码:

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

into:

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

Because I fall for it, I would like to drop a note.
I found this thread, because I have to rewrite an old sh script
to be POSIX compatible.
This basically means to circumvent the pipe/subshell problem introduced by POSIX by rewriting code like this:

some_command | read a b c

into:

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

And code like this:

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

into:

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

But the latter does not behave the same on empty input.
With the old notation the while loop is not entered on empty input,
but in POSIX notation it is!
I think it's due to the newline before EOF,
which cannot be ommitted.
The POSIX code which behaves more like the old notation
looks like this:

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

In most cases this should be good enough.
But unfortunately this still behaves not exactly like the old notation
if some_command prints an empty line.
In the old notation the while body is executed
and in POSIX notation we break in front of the body.

An approach to fix this might look like this:

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
生死何惧 2024-09-06 13:20:28

将某些内容通过管道输送到涉及赋值的表达式中的行为并非如此。

相反,请尝试:

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

Piping something into an expression involving an assignment doesn't behave like that.

Instead, try:

test=$(echo "hello world"); echo test=$test
寄离 2024-09-06 13:20:28

下面的代码:

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 将运行最后一个命令
当前 shell 中未在后台执行的管道的
环境。

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

The following code:

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

will work too, but it will open another new sub-shell after the pipe, where

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

won't.


I had to disable job control to make use of chepnars' method (I was running this command from terminal):

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

Bash Manual says:

lastpipe

If set, and job control is not active, the shell runs the last command
of a pipeline not executed in the background in the current shell
environment.

Note: job control is turned off by default in a non-interactive shell and thus you don't need the set +m inside a script.

榆西 2024-09-06 13:20:28

我认为您正在尝试编写一个可以从标准输入获取输入的 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 ~ $ 

就这样吧。

I think you were trying to write a shell script which could take input from stdin.
but while you are trying it to do it inline, you got lost trying to create that test= variable.
I think it does not make much sense to do it inline, and that's why it does not work the way you expect.

I was trying to reduce

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

to get a specific line from various input.
so I could type...

cat program_file.c | line 34

so I need a small shell program able to read from stdin. like you do.

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

there you go.

笑,眼淚并存 2024-09-06 13:20:28

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

直观的构造

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

<<< $(...) 是一个此处文档变体,它提供标准命令行的所有元处理。 < <(...) 是文件替换运算符的输入重定向。

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

The questions is how to catch output from a command to save in variable(s) for use later in a script. I might repeat some earlier answers but I try to line up all the answers I can think up to compare and comment, so bear with me.

The intuitive construct

echo test | read x
echo x=$x

is valid in Korn shell because ksh have implemented that the last command in a piped series is part of the current shell ie. the previous pipe commands are subshells. In contrast other shells define all piped commands as subshells including the last.
This is the exact reason I prefer ksh.
But having to copy with other shells, bash f.ex., another construct must be used.

To catch 1 value this construct is viable:

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

But that only caters for 1 value to be collected for later use.

To catch more values this construct is useful and works in bash and ksh:

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

There is a variant which I have noticed work in bash but not in ksh:

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

The <<< $(...) is a here-document variant which gives all the meta handling of a standard command line. < <(...) is an input redirection of a file-substitution operator.

I use "<<< $(" in all my scripts now because it seems the most portable construct between shell variants. I have a tools set I carry around on jobs in any Unix flavor.

Of course there is the universally viable but crude solution:

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
白色秋天 2024-09-06 13:20:28

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

我想出了如下的解决方案(作为 #!/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 结束输入。

I wanted something similar - a function that parses a string that can be passed as a parameter or piped.

I came up with a solution as below (works as #!/bin/sh and as #!/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"

Outputs:

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

For the last case (3.) you need to type, hit enter and CTRL+D to end the input.

荭秂 2024-09-06 13:20:28

这个怎么样:

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

How about this:

echo "hello world" | echo test=$(cat)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文