22

谁能解释这种行为?跑步:

#!/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 在第二个示例中所做的事情吗?我对 dash 和 bash shell 都尝试了相同的代码,并从它们中得到了相同的行为。

4

9 回答 9

12

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

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

确实会输出

hello
world
于 2012-07-26T12:10:13.397 回答
11
#!/bin/sh
echo "hello world" | read var1 var2
echo $var1
echo $var2

不会产生任何输出,因为管道在子外壳中运行它们的每个组件。子shell继承父shell 变量的副本,而不是共享它们。试试这个:

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

括号定义了在子 shell 中运行的代码区域,并且 $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

在大括号内,内置的 read 正确地创建了 $var1 和 $var2 ,您可以看到它们得到了回显。在大括号之外,它们不再存在。大括号内的所有代码都在子shell 中运行,因为它是 pipeline 的一个组件

您可以在大括号之间放置任意数量的代码,因此当您需要运行一个解析其他输出的 shell 脚本块时,您可以使用这种管道到块的构造。

于 2008-09-19T02:20:35.417 回答
10

这已经得到了正确的回答,但尚未说明解决方案。使用 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。)

于 2008-08-15T15:52:01.110 回答
8
read var1 var2 < <(echo "hello world")
于 2008-09-17T00:17:05.340 回答
6

好吧,我想通了!

这是一个很难捕捉的错误,但它是由 shell 处理管道的方式造成的。管道的每个元素都在单独的进程中运行。当读取命令设置 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

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

于 2008-08-05T20:09:15.377 回答
6

该帖子已得到适当的回答,但我想提供一种替代的衬里,它可能会有一些用处。

要将空间分隔值从 echo (或 stdout )分配给 shell 变量,您可以考虑使用 shell 数组:

$ var=( $( echo 'hello world' ) )
$ echo ${var[0]}
hello
$ echo ${var[1]}
world

在此示例中,var 是一个数组,可以使用构造 ${var[index]} 访问其内容,其中 index 是数组索引(从 0 开始)。

这样,您可以将任意数量的参数分配给相关的数组索引。

于 2008-09-14T17:00:04.750 回答
5

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

执行这个命令

$ echo $$;cat | read a
10637

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

    |                       |-bash(10637)-+-bash(10786)
    |                       |             `-cat(10785)
于 2008-08-05T20:00:43.023 回答
5

我对这个问题的看法(使用 Bash):

read var1 var2 <<< "hello world"
echo $var1 $var2
于 2009-03-04T09:52:39.953 回答
3

尝试:

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

正如许多人所说,问题在于 var1 和 var2 是在子 shell 环境中创建的,当该子 shell 退出时会被破坏。以上避免破坏子shell,直到结果被回显。另一种解决方案是:

result=`echo "hello world"`
read var1 var2 <<EOF
$result
EOF
echo $var1
echo $var2
于 2008-09-17T02:15:03.743 回答