我们说我有一个main.sh
脚本,它将通过子shell调用one.sh
。
one.sh
:
#! /bin/bash
set -euo pipefail
(if [ -t 0 ]; then
echo "one little two little three little buses"
else
cat -
fi) | awk '{ $1 = "111"; print $0 }'
main.sh
:
#! /bin/bash
set -euo pipefail
main() {
echo "one_result) $(./one.sh)"
echo "one_piped) $(echo "the quick brown fox" | ./one.sh)"
}
main
现在,他们每个人都按预期工作:
$ ./one.sh
111 little two little three little buses
$ ./main.sh
one_result) 111 little two little three little buses
one_piped) 111 quick brown fox
但是,当我将某些内容传递给main.sh
时,我没想到(或者说,我不想要)one.sh
来了解管道内容,因为我认为{{ 1}}位于one.sh
:
one_result)
$ echo "HELLO WORLD MAIN" | ./main.sh
one_result) 111 WORLD MAIN
one_piped) 111 quick brown fox
中的if
条件不是我想要的吗?我希望one.sh
不会产生使用我的one.sh
标准素的任何副作用 - 因为现在已经消耗了它,我的main.sh
现在已经有效了{{1} } -less,因为stdin只能读取一次,除非我将它存储起来。
思考? TIA。
答案 0 :(得分:3)
通常,子shell(以及shell生成的其他进程)从父shell继承stdin。如果那是终端,您的测试将按预期工作;如果它是管道,那么它将检测到它是管道并继续使用它。子shell无法通过明确分配(如在echo "the quick brown fox" | ./one.sh
中)或通过继承来判断它是否获得了该管道。
据我所知,避免此问题的唯一方法是明确地将one.sh
的输入重定向到管道以外的其他内容,以避免它继承父shell的stdin (可能是管道)。类似的东西:
echo "one_nonpipe) $(./one.sh </dev/null)"
echo "one_piped) $(echo "the quick brown fox" | ./one.sh)"
...但更好的是添加一个标志来告诉one.sh
是否从stdin读取,而不是试图从附加到的文件类型中找出它标准输入:
#! /bin/bash
# Usage: one.sh [-i]
# -i Read from stdin
set -euo pipefail
if [ "$1" = "-i" ]; then
cat -
else
echo "one little two little three little buses"
fi | awk '{ $1 = "111"; print $0 }'
...
echo "one_result) $(./one.sh)"
echo "one_piped) $(echo "the quick brown fox" | ./one.sh -i)"
(请注意,我还删除了if
块周围不必要的括号 - 他们没有充分的理由创建了另一个子shell级别。)
答案 1 :(得分:2)
默认情况下,每个进程都从其父进程继承其标准输入(以及输出和错误)。输入重定向和管道是在启动子进程时将标准输入更改为不同文件描述符的两种方法。
main.sh
的责任是,如果需要从标准输入中读取,要知道one.sh
也从标准输入中读取,并且需要预防one.sh
消费它。