Shell脚本输入重定向奇怪

谁能解释这种行为? 运行:

#!/bin/sh
echo "hello world" | read var1 var2
echo $var1
echo $var2

结果没有任何东西被输出,而:

#!/bin/sh
echo "hello world" > test.file
read var1 var2 < test.file
echo $var1
echo $var2

产生预期的输出:

hello
world

管道不应该在第二个例子中一步完成对test.file的重定向吗?我用破折号和bash炮弹尝试了相同的代码,并从它们中获得相同的行为。

0
额外 编辑
意见: 2

7 答案

这是因为管道版本正在创建一个子shell,它将变量读入本地空间,然后在子shell退出时将其销毁。

执行此命令

$ echo $$;cat | read a
10637

并使用pstree -p查看正在运行的进程,您将看到一个额外的shell挂在主shell上。

    |                       |-bash(10637)-+-bash(10786)
    |                       |             `-cat(10785)
0
额外

好吧,我明白了!

这是一个难以捉摸的问题,但是结果来自管道由外壳处理的方式。管道的每个元素都在一个单独的进程中运行。当读取命令设置var1和var2时,将它设置为它自己的子shell,而不是父shell。所以当subshel​​l退出时,var1和var2的值就会丢失。但是,你可以尝试做

var1=$(echo "Hello")
echo var1

这将返回预期的答案。不幸的是,这只适用于单个变量,您不能一次设置多个变量。为了一次设置多个变量,您必须读入一个变量并将其分成多个变量或使用如下所示的内容:

set -- $(echo "Hello World")
var1="$1" var2="$2"
echo $var1
echo $var2

虽然我承认它不像使用管道那么优雅,但它起作用。当然,你应该记住,读取是为了从文件读入变量,所以从标准输入中读取应该有点困难。

0
额外
Bash 4.2引入了一个选项来做同样的事情。关闭作业控制( set + m )并设置 lastpipe 选项( shopt -s lastpipe )。
额外 作者 chepner,
这取决于壳的选择。 Ksh93旨在限制处理开销。它还在调用shell进程中运行管道的最后一个元素,从而保持状态。
额外 作者 Henk Langeveld,
我会研究一下。谢谢!
额外 作者 Henk Langeveld,

最近添加到 bash 的是 lastpipe 选项,该选项允许在作业控制停用时,管道中的最后一个命令在当前shell中运行,而不是子shell。

#!/bin/bash
set +m      # Deactiveate job control
shopt -s lastpipe
echo "hello world" | read var1 var2
echo $var1
echo $var2

将确实输出

hello
world
0
额外

尝试:

echo "hello world" | (read var1 var2 ; echo $var1 ; echo $var2 )

正如多人所说的,问题在于,var1和var2是在子外壳环境中创建的,当子外壳退出时,该子环境会被销毁。上面的内容避免了在子结构被破坏之前,直到结果被echo'd。另一个解决方案是

result=`echo "hello world"`
read var1 var2 <
0
额外

这已经被正确回答了,但是解决方案还没有被陈述。使用ksh,而不是bash。比较:

$ echo 'echo "hello world" | read var1 var2
echo $var1
echo $var2' | bash -s

至:

$ echo 'echo "hello world" | read var1 var2
echo $var1
echo $var2' | ksh -s
hello
world

ksh是一款优秀的编程外壳,因为这种小巧的外观。 (在我看来,bash是更好的交互式shell。)

0
额外
read var1 var2 < <(echo "hello world")
0
额外
#!/bin/sh
echo "hello world" | read var1 var2
echo $var1
echo $var2

由于管道在子shell中运行其每个组件,因此不会生成输出。子壳继承父壳体变量的副本,而不是共享它们。尝试这个:

#!/bin/sh
foo="contents of shell variable foo"
echo $foo
(
    echo $foo
    foo="foo contents modified"
    echo $foo
)
echo $foo

圆括号定义了一个在子外壳中运行的代码区域,并且$ foo在被修改后保留其原始值。

现在试试这个:

#!/bin/sh
foo="contents of shell variable foo"
echo $foo
{
    echo $foo
    foo="foo contents modified"
    echo $foo
}
echo $foo

大括号纯粹是为了分组,没有创建子shell,并且大括号内的$ foo修改是在它们外面修改的相同的$ foo。

现在试试这个:

#!/bin/sh
echo "hello world" | {
    read var1 var2
    echo $var1
    echo $var2
}
echo $var1
echo $var2

在大括号内部,内置读取程序正确创建$ var1和$ var2,您可以看到它们得到回显。在大括号之外,它们不再存在。大括号内的所有代码都已在子shell中运行,因为它是管道的一个组成部分 。

您可以在大括号之间放置任意数量的代码,因此,无论何时需要运行一组用于解析其他输出的shell脚本,您都可以使用此管道模块构造。

0
额外
清洁示例+1 - 漂亮易读的代码
额外 作者 Henk Langeveld,