从管道将值读入 shell 变量
我试图让 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(17)
使用
你可以欺骗
read
从这样的管道接受:或者甚至编写一个这样的函数:
但是没有意义 - 你的变量赋值可能不会持续!管道可能会生成子 shell,其中环境是按值继承的,而不是按引用继承的。这就是为什么 read 不关心来自管道的输入 - 它是未定义的。
仅供参考,http://www.etalabs.net/sh_tricks.html 是一个漂亮的集合对抗伯恩炮弹的怪异和不兼容性所必需的 cruft,sh。
Use
You can trick
read
into accepting from a pipe like this:or even write a function like this:
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.
如果你想读取大量数据并分别处理每一行,你可以使用这样的东西:
如果你想将行分成多个单词,你可以使用多个变量代替x,如下所示:
或者:
但是尽快当你开始想做任何真正聪明的事情时,你最好使用像 perl 这样的脚本语言,你可以尝试这样的事情:
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:
if you want to split the lines up into multiple words you can use multiple variables in place of x like this:
alternatively:
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:
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.
这是另一种选择
This is another option
read
不会从管道中读取(或者可能会因为管道创建子 shell 而导致结果丢失)。但是,您可以在 Bash 中使用此处的字符串:但请参阅 @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:But see @chepner's answer for information about
lastpipe
.我不是 Bash 专家,但我想知道为什么没有提出这一点:
单行证明它对我有用:
I'm no expert in Bash, but I wonder why this hasn't been proposed:
One-liner proof that it works for me:
bash
4.2 引入了lastpipe
选项,该选项允许您的代码通过在当前 shell(而不是子 shell)中执行管道中的最后一个命令来按编写的方式工作。bash
4.2 introduces thelastpipe
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.一个智能脚本,可以从 PIPE 和命令行参数读取数据:
输出:
解释: 当脚本通过管道接收任何数据时,/dev/stdin(或/proc/self/fd/0) 将是管道的符号链接。
如果不是,它将指向当前终端:
bash
[[ -p
选项可以检查它是否是管道。cat -
从stdin
读取。如果我们在没有
stdin
时使用cat -
,它将永远等待,这就是为什么我们将其放在if
条件中。A smart script that can both read data from PIPE and command line arguments:
Output:
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.
If not, it will point to the current terminal:
The bash
[[ -p
option can check it it is a pipe or not.cat -
reads the fromstdin
.If we use
cat -
when there is nostdin
, it will wait forever, that is why we put it inside theif
condition.从 shell 命令到 bash 变量的隐式管道的语法是
or
在您的示例中,您将数据通过管道传输到赋值语句,该语句不需要任何输入。
The syntax for an implicit pipe from a shell command into a bash variable is
or
In your examples, you are piping data to an assignment statement, which does not expect any input.
在我看来,在 bash 中读取 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:
第一次尝试非常接近。这种变化应该有效:
并且输出是:
您需要在管道后面使用大括号来括起要测试的分配和回显。
如果没有大括号,对 test 的分配(在管道之后)位于一个 shell 中,而 echo "test=$test" 位于一个单独的 shell 中,该 shell 不知道该分配。这就是为什么你在输出中得到“test=”而不是“test=hello world”。
The first attempt was pretty close. This variation should work:
and the output is:
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".
因为我喜欢它,所以我想写一张纸条。
我找到了这个线程,因为我必须重写一个旧的 sh 脚本
与 POSIX 兼容。
这基本上意味着通过重写如下代码来规避 POSIX 引入的管道/子外壳问题:
into:
和如下代码:
into:
但后者在空输入时的行为不同。
使用旧的符号时,不会在空输入上进入 while 循环,
但在 POSIX 表示法中它是!
我认为这是由于 EOF 之前的换行符造成的,
不能省略。
POSIX 代码的行为更像旧的表示法
看起来像这样:
在大多数情况下,这应该足够好了。
但不幸的是,这仍然与旧的表示法不完全一样
如果 some_command 打印空行。
在旧的表示法中, while 主体被执行
在 POSIX 表示法中,我们在主体前面中断。
解决此问题的方法可能如下所示:
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:
into:
And code like this:
into:
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:
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:
将某些内容通过管道输送到涉及赋值的表达式中的行为并非如此。
相反,请尝试:
Piping something into an expression involving an assignment doesn't behave like that.
Instead, try:
下面的代码:
也可以工作,但它会在管道之后打开另一个新的子外壳,而
不会。
我必须禁用作业控制才能使用 chepnars' 方法(我从终端运行此命令):
Bash 手册说:
注意:默认情况下,在非交互式 shell 中作业控制处于关闭状态,因此您不需要在脚本内使用
set +m
。The following code:
will work too, but it will open another new sub-shell after the pipe, where
won't.
I had to disable job control to make use of chepnars' method (I was running this command from terminal):
Bash Manual says:
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.我认为您正在尝试编写一个可以从标准输入获取输入的 shell 脚本。
但是当您尝试内联执行此操作时,您在尝试创建 test= 变量时迷失了方向。
我认为内联执行没有多大意义,这就是为什么它不能按您期望的方式工作。
我试图减少
从各种输入中获取特定的行。
所以我可以输入...
所以我需要一个能够从标准输入读取的小型 shell 程序。就像你一样。
就这样吧。
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
to get a specific line from various input.
so I could type...
so I need a small shell program able to read from stdin. like you do.
there you go.
问题是如何捕获命令的输出以保存在变量中以便稍后在脚本中使用。我可能会重复一些之前的答案,但我会尝试将我能想到的所有答案排列起来以进行比较和评论,所以请耐心等待。
直观的构造
在 Korn shell 中是有效的,因为 ksh 已经实现了管道系列中的最后一个命令是当前 shell 的一部分,即。前面的管道命令是子shell。相比之下,其他 shell 将所有管道命令定义为子 shell,包括最后一个。
这就是我更喜欢 ksh 的确切原因。
但必须使用其他 shell(如 bash f.ex.)进行复制,必须使用另一种构造。
要捕获 1 个值,此构造是可行的:
但这只能满足收集 1 个值以供以后使用。
为了捕获更多值,这个构造很有用,并且可以在 bash 和 ksh 中工作:
我注意到有一个变体可以在 bash 中工作,但不能在 ksh 中工作:
<<< $(...) 是一个此处文档变体,它提供标准命令行的所有元处理。 < <(...) 是文件替换运算符的输入重定向。
我现在在所有脚本中使用“<<< $(”,因为它似乎是 shell 变体之间最可移植的结构。我有一个工具集,可以在任何 Unix 风格的工作中随身携带。
当然,还有普遍可行的工具但粗略的解决方案:
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
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:
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:
There is a variant which I have noticed work in bash but not in ksh:
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:
我想要类似的东西 - 一个解析可以作为参数或管道传递的字符串的函数。
我想出了如下的解决方案(作为
#!/bin/sh
和#!/bin/bash
工作)输出:
对于最后一种情况(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
)Outputs:
For the last case (3.) you need to type, hit enter and CTRL+D to end the input.
这个怎么样:
How about this: