【问题标题】:Shell scripting input redirection odditiesShell 脚本输入重定向异常
【发布时间】:2010-09-05 09:19:01
【问题描述】:

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

#!/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 都尝试了相同的代码,并从它们中得到了相同的行为。

【问题讨论】:

    标签: dash-shell


    【解决方案1】:

    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
    

    【讨论】:

      【解决方案2】:
      #!/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中运行因为它是管道的一个组件

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

      【讨论】:

        【解决方案3】:

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

        【讨论】:

          【解决方案4】:
          read var1 var2 < <(echo "hello world")
          

          【讨论】:

            【解决方案5】:

            该帖子已得到适当的回答,但我想提供一种替代品,也许会有一些用处。

            为了将 echo(或标准输出)中的空格分隔值分配给 shell 变量,您可以考虑使用 shell 数组:

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

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

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

            【讨论】:

            • 很好的解决方案。它在 bash 中运行良好,但在 dash shell 中不起作用。
            【解决方案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 意味着从文件中读取到变量中,所以从标准输入中读取应该有点困难。

            【讨论】:

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

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

            执行这个命令

            $ echo $$;cat | read a
            10637
            

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

                |                       |-bash(10637)-+-bash(10786)
                |                       |             `-cat(10785)
            

            【讨论】:

              【解决方案8】:

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

              read var1 var2 <<< "hello world"
              echo $var1 $var2
              

              【讨论】:

                【解决方案9】:

                试试:

                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
                

                【讨论】:

                • 您可以通过内联 $result 来缩短它(但在 StackOverflow 评论中很难显示)。
                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2010-10-07
                • 2023-03-30
                • 1970-01-01
                • 2013-12-21
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多