Shell脚本输入重定向奇怪

Rya*_*arn 18 linux bash shell ksh dash-shell

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

#!/bin/sh
echo "hello world" | read var1 var2
echo $var1
echo $var2
Run Code Online (Sandbox Code Playgroud)

导致没有任何输出,而:

#!/bin/sh
echo "hello world" > test.file
read var1 var2 < test.file
echo $var1
echo $var2
Run Code Online (Sandbox Code Playgroud)

产生预期的输出:

hello
world
Run Code Online (Sandbox Code Playgroud)

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

fla*_*let 11

#!/bin/sh
echo "hello world" | read var1 var2
echo $var1
echo $var2
Run Code Online (Sandbox Code Playgroud)

不产生输出,因为管道在子shell中运行它们的每个组件.子shell 继承拷贝父进程的变量,而不是分享.试试这个:

#!/bin/sh
foo="contents of shell variable foo"
echo $foo
(
    echo $foo
    foo="foo contents modified"
    echo $foo
)
echo $foo
Run Code Online (Sandbox Code Playgroud)

括号定义了一个在子shell中运行的代码区域,$ foo在它们内部修改后保留其原始值.

现在试试这个:

#!/bin/sh
foo="contents of shell variable foo"
echo $foo
{
    echo $foo
    foo="foo contents modified"
    echo $foo
}
echo $foo
Run Code Online (Sandbox Code Playgroud)

大括号纯粹用于分组,没有创建子shell,并且在大括号内修改的$ foo与在它们之外修改的$ foo相同.

现在试试这个:

#!/bin/sh
echo "hello world" | {
    read var1 var2
    echo $var1
    echo $var2
}
echo $var1
echo $var2
Run Code Online (Sandbox Code Playgroud)

在大括号内,read builtin正确地创建$ var1和$ var2,你可以看到它们被回显.在牙箍之外,它们不再存在.大括号内的所有代码都在子shell中运行,因为它是管道的一个组件.

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

  • +1代表干净的例子 - 漂亮,可读的代码 (3认同)

che*_*ner 11

最近添加bashlastpipe选项是,当取消激活作业控制时,允许管道中的最后一个命令在当前shell中运行,而不是子shell.

#!/bin/bash
set +m      # Deactiveate job control
shopt -s lastpipe
echo "hello world" | read var1 var2
echo $var1
echo $var2
Run Code Online (Sandbox Code Playgroud)

确实会输出

hello
world
Run Code Online (Sandbox Code Playgroud)


Jon*_*son 10

这已经得到了正确回答,但尚未说明解决方案.使用ksh,而不是bash.相比:

$ echo 'echo "hello world" | read var1 var2
echo $var1
echo $var2' | bash -s
Run Code Online (Sandbox Code Playgroud)

至:

$ echo 'echo "hello world" | read var1 var2
echo $var1
echo $var2' | ksh -s
hello
world
Run Code Online (Sandbox Code Playgroud)

ksh是一个优秀的编程shell,因为这样的细节很少.(在我看来,bash是更好的交互式shell.)


Ste*_*ker 8

read var1 var2 < <(echo "hello world")
Run Code Online (Sandbox Code Playgroud)


Mar*_*son 5

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

执行这个命令

$ echo $$;cat | read a
10637
Run Code Online (Sandbox Code Playgroud)

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

    |                       |-bash(10637)-+-bash(10786)
    |                       |             `-cat(10785)
Run Code Online (Sandbox Code Playgroud)


num*_*um1 5

好吧,我明白了!

这是一个难以捕获的bug,但是由shell处理管道的方式产生.管道的每个元素都在一个单独的进程中运行.当read命令设置var1和var2时,将它们设置为自己的子shell,而不是父shell.因此,当子shell退出时,var1和var2的值将丢失.但是,您可以尝试这样做

var1=$(echo "Hello")
echo var1
Run Code Online (Sandbox Code Playgroud)

它返回预期的答案.不幸的是,这仅适用于单个变量,您不能一次设置多个变量.为了一次设置多个变量,您必须读入一个变量并将其切换为多个变量或使用以下内容:

set -- $(echo "Hello World")
var1="$1" var2="$2"
echo $var1
echo $var2
Run Code Online (Sandbox Code Playgroud)

虽然我承认它并不像使用烟斗那么优雅,但它确实有效.当然,你应该记住,读取是为了从文件读入变量,所以从标准输入读取它应该有点困难.

  • 这取决于shell的选择.Ksh93旨在限制流程开销.它还在调用shell进程内部运行管道*的最后一个元素,从而保留状态. (3认同)
  • Bash 4.2 引入了一个选项来做同样的事情。关闭作业控制(`set +m`)并设置`lastpipe`选项(`shopt -s lastpipe`)。 (2认同)

小智 5

该帖子已得到妥善回答,但我想提供另一种可能有用的替代衬里.

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

$ var=( $( echo 'hello world' ) )
$ echo ${var[0]}
hello
$ echo ${var[1]}
world
Run Code Online (Sandbox Code Playgroud)

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

这样,您可以将所需的参数分配给相关的数组索引.

  • 好的解决方案 它在bash中运行良好,但它在dash shell中不起作用. (2认同)

小智 5

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

read var1 var2 <<< "hello world"
echo $var1 $var2
Run Code Online (Sandbox Code Playgroud)