Shell 脚本输入重定向奇怪之处
-
08-06-2019 - |
题
谁能解释这种行为?跑步:
#!/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 中尝试了相同的代码,并从它们中得到了相同的行为。
解决方案
最近添加了 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
其他提示
#!/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 脚本块时,都可以使用这种管道插入块结构。
这已经被正确回答了,但解决方案尚未说明。使用 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 是一个优秀的编程 shell,因为有这样的小细节。(在我看来,bash 是更好的交互式 shell。)
read var1 var2 < <(echo "hello world")
该帖子已得到正确答复,但我想提供另一种可能有用的衬里。
要将来自 echo(或 stdout)的空格分隔值分配给 shell 变量,您可以考虑使用 shell 数组:
$ var=( $( echo 'hello world' ) )
$ echo ${var[0]}
hello
$ echo ${var[1]}
world
在此示例中,var 是一个数组,可以使用构造 ${var[index]} 访问内容,其中 index 是数组索引(从 0 开始)。
这样,您就可以将任意数量的参数分配给相关的数组索引。
我对这个问题的看法(使用 Bash):
read var1 var2 <<< "hello world"
echo $var1 $var2
好吧,我想通了!
这是一个很难捕获的错误,但这是由 shell 处理管道的方式造成的。管道的每个元素都在单独的进程中运行。当 read 命令设置 var1 和 var2 时,会将它们设置为其自己的子 shell,而不是父 shell。所以当子shell退出时,var1和var2的值就会丢失。但是,您可以尝试这样做
var1=$(echo "Hello")
echo var1
返回预期的答案。不幸的是,这仅适用于单个变量,您不能一次设置多个变量。为了一次设置多个变量,您必须读入一个变量并将其分成多个变量,或者使用如下所示的内容:
set -- $(echo "Hello World")
var1="$1" var2="$2"
echo $var1
echo $var2
虽然我承认它不如使用管道那么优雅,但它确实有效。当然,您应该记住,读取的目的是从文件读取到变量中,因此从标准输入读取它应该有点困难。
这是因为管道版本正在创建一个子 shell,它将变量读入其本地空间,然后在子 shell 退出时销毁该变量。
执行这个命令
$ echo $$;cat | read a
10637
并使用 pstree -p 查看正在运行的进程,您将看到一个额外的 shell 挂在主 shell 上。
| |-bash(10637)-+-bash(10786)
| | `-cat(10785)
尝试:
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