Bash 子shell 消耗父进程的stdin
Bash subshell consumes stdin of the parent process
假设我有一个 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 '{ = "111"; print [=11=] }'
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
了解管道内容,因为我认为 one.sh
在 one_result)
:
中它自己的子 shell 中
$ echo "HELLO WORLD MAIN" | ./main.sh
one_result) 111 WORLD MAIN
one_piped) 111 quick brown fox
难道我在one.sh
的if
条件不是我想要的?我希望 one.sh
不产生任何消耗我的 main.sh
的标准输入的副作用 - 因为现在它已经消耗了它,我的 main.sh
现在有效地 stdin
-less ,因为标准输入只能读取一次,除非我将其存储起来。
想法? TIA.
默认情况下,每个进程都从其父进程继承其标准输入(以及输出和错误)。输入重定向和管道是在启动子进程时将标准输入更改为不同文件描述符的两种方式。
main.sh
有责任,如果需要从它的标准输入读取,知道one.sh
也从标准输入读取,并且它需要防止 one.sh
消耗它。
一般来说,子shells(以及其他shell产生的进程)从父shell继承stdin。如果那是终端,那么您的测试将按预期进行;如果它是一个管道,那么它会检测到它是一个管道并继续使用它。 subshell 无法通过显式分配(如 echo "the quick brown fox" | ./one.sh
中那样)或通过继承来判断它是否获得了该管道。
据我所知,避免此问题的唯一方法是将 one.sh
的输入显式重定向到管道以外的其他内容,以避免它继承父 shell 的输入标准输入(可能是管道)。类似于:
echo "one_nonpipe) $(./one.sh </dev/null)"
echo "one_piped) $(echo "the quick brown fox" | ./one.sh)"
...但是更好的是添加一个标志来告诉 one.sh
是否从标准输入读取,而不是试图从附加的文件类型中找出它标准输入:
#! /bin/bash
# Usage: one.sh [-i]
# -i Read from stdin
set -euo pipefail
if [ "" = "-i" ]; then
cat -
else
echo "one little two little three little buses"
fi | awk '{ = "111"; print [=11=] }'
...
echo "one_result) $(./one.sh)"
echo "one_piped) $(echo "the quick brown fox" | ./one.sh -i)"
(请注意,我还删除了 if
块周围不必要的括号——他们无缘无故地创建了另一层 subshell。)
假设我有一个 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 '{ = "111"; print [=11=] }'
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
了解管道内容,因为我认为 one.sh
在 one_result)
:
$ echo "HELLO WORLD MAIN" | ./main.sh
one_result) 111 WORLD MAIN
one_piped) 111 quick brown fox
难道我在one.sh
的if
条件不是我想要的?我希望 one.sh
不产生任何消耗我的 main.sh
的标准输入的副作用 - 因为现在它已经消耗了它,我的 main.sh
现在有效地 stdin
-less ,因为标准输入只能读取一次,除非我将其存储起来。
想法? TIA.
默认情况下,每个进程都从其父进程继承其标准输入(以及输出和错误)。输入重定向和管道是在启动子进程时将标准输入更改为不同文件描述符的两种方式。
main.sh
有责任,如果需要从它的标准输入读取,知道one.sh
也从标准输入读取,并且它需要防止 one.sh
消耗它。
一般来说,子shells(以及其他shell产生的进程)从父shell继承stdin。如果那是终端,那么您的测试将按预期进行;如果它是一个管道,那么它会检测到它是一个管道并继续使用它。 subshell 无法通过显式分配(如 echo "the quick brown fox" | ./one.sh
中那样)或通过继承来判断它是否获得了该管道。
据我所知,避免此问题的唯一方法是将 one.sh
的输入显式重定向到管道以外的其他内容,以避免它继承父 shell 的输入标准输入(可能是管道)。类似于:
echo "one_nonpipe) $(./one.sh </dev/null)"
echo "one_piped) $(echo "the quick brown fox" | ./one.sh)"
...但是更好的是添加一个标志来告诉 one.sh
是否从标准输入读取,而不是试图从附加的文件类型中找出它标准输入:
#! /bin/bash
# Usage: one.sh [-i]
# -i Read from stdin
set -euo pipefail
if [ "" = "-i" ]; then
cat -
else
echo "one little two little three little buses"
fi | awk '{ = "111"; print [=11=] }'
...
echo "one_result) $(./one.sh)"
echo "one_piped) $(echo "the quick brown fox" | ./one.sh -i)"
(请注意,我还删除了 if
块周围不必要的括号——他们无缘无故地创建了另一层 subshell。)