从管道将值读入 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 也没有 subshel​​l。见BashFAQ/024。【参考方案3】:

这是另一种选择

$ read test < <(echo hello world)

$ echo $test
hello world

【讨论】:

&lt;(..) 相对于$(..) 的显着优势是&lt;(..) 在其执行的命令使其可用时立即将每一行返回给调用者。但是,$(..) 会等待命令完成并生成其所有输出,然后才能将任何输出提供给调用者。【参考方案4】:

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

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

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

【讨论】:

漂亮而简单的单行,易于理解。这个答案需要更多的支持。 &lt;&lt;&lt; 添加了可能不需要的换行符【参考方案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 变量的主要内容,如果未能解决你的问题,请参考以下文章

将值从闭包赋值给变量 Swift

如何将值从Visual Basic 6程序传递给VBscript代码?

从file.Python3将值变量插入sql查询

如何在python中定义空变量或将值从函数传递给全局变量? [复制]

如何使用范围变量将值从控制器获取到指令中

将值从数据库传递到 vue 组件并将其分配给变量