从管道将值读入 shell 变量
Posted
技术标签:
【中文标题】从管道将值读入 shell 变量【英文标题】:Read values into a shell variable from a pipe 【发布时间】:2011-02-14 08:23:48 【问题描述】:我试图让 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"
周围加上 "" 引号,但这也不起作用。
【问题讨论】:
你的例子.. echo "hello world" |阅读测试; echo test=$test 对我来说效果很好.. 结果: test=hello world ;这是在什么环境下运行的?我正在使用 bash 4.2.. 您想一次读取多行吗?您的示例只显示了一行,但问题描述不清楚。 @alex.pilon,我正在运行 Bash 版本 4.2.25,他的示例也不适用于我。可能是 Bash 运行时选项或环境变量的问题?我的示例也不适用于 Sh,所以 Bash 可以尝试与 Sh 兼容吗? @Hibou57 - 我在 bash 4.3.25 中再次尝试过,但它不再有效。我对此的记忆很模糊,我不确定我做了什么才能让它发挥作用。 @Hibou57 @alex.pilon 管道中的最后一个 cmd 应该影响 bash4>=4.2 中的变量shopt -s lastpipe
-- tldp.org/LDP/abs/html/bashver4.html#LASTPIPEOPT
【参考方案1】:
使用
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 是与 bourne shell 的怪异和不兼容所必需的内容的精美合集,嘘。
【讨论】:
你可以通过这样做来最后完成分配:`test=``echo "hello world" | 阅读测试;回声$测试; ``` 让我们再试一次(显然在这个标记中转义反引号很有趣):test=`echo "hello world" | read test; echo $test; `
我能问一下您为什么在对这两个命令进行分组时使用
而不是()
吗?
诀窍不是让read
从管道中获取输入,而是在执行read
的同一个shell 中使用变量。
在尝试使用此解决方案时收到bash permission denied
。我的情况完全不同,但我在任何地方都找不到答案,对我有用的是(一个不同的例子但类似的用法):pip install -U echo $(ls -t *.py | head -1)
。以防万一,有人遇到过类似的问题并像我一样偶然发现了这个答案。【参考方案2】:
如果您想读取大量数据并分别处理每一行,您可以使用以下内容:
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 Programming Language。
【讨论】:
"alternatively" 是正确的方法。没有 UUoC 也没有 subshell。见BashFAQ/024。【参考方案3】:这是另一种选择
$ read test < <(echo hello world)
$ echo $test
hello world
【讨论】:
<(..)
相对于$(..)
的显着优势是<(..)
在其执行的命令使其可用时立即将每一行返回给调用者。但是,$(..)
会等待命令完成并生成其所有输出,然后才能将任何输出提供给调用者。【参考方案4】:
read
不会从管道中读取(或者结果可能会丢失,因为管道创建了一个子外壳)。但是,您可以在 Bash 中使用此处的字符串:
$ read a b c <<< $(echo 1 2 3)
$ echo $a $b $c
1 2 3
但有关lastpipe
的信息,请参阅@chepner 的回答。
【讨论】:
漂亮而简单的单行,易于理解。这个答案需要更多的支持。<<<
添加了可能不需要的换行符【参考方案5】:
我不是 Bash 方面的专家,但我想知道为什么没有提出这个建议:
stdin=$(cat)
echo "$stdin"
证明它对我有用的单行证明:
$ fortune | eval 'stdin=$(cat); echo "$stdin"'
【讨论】:
那可能是因为“read”是一个bash命令,而cat是一个单独的二进制文件,会在子进程中启动,所以效率较低。 有时简单明了胜过效率 :) 绝对是最直接的答案 @djanowski 但这不一定是给定脚本的预期行为。如果只有一种方法可以优雅地处理标准输入的缺失,如果它不存在,则退回到“常规”行为。 This post 几乎有它 - 它接受参数或标准输入。唯一缺少的是能够在两者都不存在时提供使用帮助器。 在寻找已接受答案的替代方案时,我决定为我的用例选择与此答案类似的东西:)$@:-$(cat)
【参考方案6】:
bash
4.2 引入了lastpipe
选项,通过在当前 shell 而非子 shell 中执行管道中的最后一条命令,您的代码可以按照编写的方式工作。
shopt -s lastpipe
echo "hello world" | read test; echo test=$test
【讨论】:
啊!太好了,这个如果在交互式 shell 中进行测试,还需要:“set +m”(在 .sh 脚本中不需要)【参考方案7】:从 shell 命令到 bash 变量的隐式管道的语法是
var=$(command)
或
var=`command`
在您的示例中,您将数据传递给赋值语句,它不需要任何输入。
【讨论】:
因为 $() 可以很容易地嵌套。想想 JAVA_DIR=$(dirname $(readlink -f $(which java))),然后用 `.你需要逃跑 3 次!【参考方案8】:一个可以从 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
条件中的原因。
【讨论】:
您也可以使用/dev/stdin
,它是指向/proc/self/fd/0
的链接【参考方案9】:
第一次尝试非常接近。这种变化应该有效:
echo "hello world" | test=$(< /dev/stdin); echo "test=$test"; ;
输出是:
test=你好世界
您需要在管道之后使用大括号来包含要测试的分配和回声。
没有大括号,test 的赋值(在管道之后)在一个 shell 中,而 echo "test=$test" 在一个不知道该赋值的单独 shell 中。这就是为什么您在输出中得到“test=”而不是“test=hello world”。
【讨论】:
【参考方案10】:在我看来,在 bash 中从标准输入读取的最佳方法是以下一种,它还可以让您在输入结束之前处理行:
while read LINE; do
echo $LINE
done < /dev/stdin
【讨论】:
在找到这个之前我几乎发疯了。非常感谢分享!【参考方案11】:因为我爱上了它,所以我想写一个便条。 我找到了这个线程,因为我必须重写一个旧的 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
【讨论】:
[ -n "$a" ] || break
也应该可以工作——但是关于缺少实际空行的问题仍然存在【参考方案12】:
将某些内容导入涉及赋值的表达式中的行为并非如此。
请尝试:
test=$(echo "hello world"); echo test=$test
【讨论】:
【参考方案13】:以下代码:
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 Manual says:
最后一个管道
如果设置,并且作业控制未激活,shell 将运行最后一个命令 在当前 shell 中未在后台执行的管道 环境。
注意:在非交互式 shell 中,作业控制默认是关闭的,因此您不需要在脚本中使用 set +m
。
【讨论】:
【参考方案14】:我认为您正在尝试编写一个可以从标准输入获取输入的 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 ~ $
你去。
【讨论】:
【参考方案15】:我想要类似的东西 - 一个解析可以作为参数传递或管道传递的字符串的函数。
我想出了如下解决方案(使用 #!/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 结束输入。
【讨论】:
【参考方案16】:问题是如何捕获命令的输出以保存在变量中以供稍后在脚本中使用。我可能会重复之前的一些答案,但我会尝试将所有我能想到的答案排列起来进行比较和评论,请耐心等待。
直观的构造
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
我现在在所有脚本中都使用“
当然有普遍可行但粗略的解决方案:
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
【讨论】:
这是一个很好的解释性答案,但我似乎无法让多值构造工作。第一个变量由我的命令输出的第一行填充,但第二个变量保持为空。知道我哪里出错了吗?【参考方案17】:这个怎么样:
echo "hello world" | echo test=$(cat)
【讨论】:
基本上是我下面的回答中的一个骗子。 也许,我认为我的代码更干净,更接近原始问题中发布的代码。以上是关于从管道将值读入 shell 变量的主要内容,如果未能解决你的问题,请参考以下文章
如何将值从Visual Basic 6程序传递给VBscript代码?