简体   繁体   中英

Bash subshell consumes stdin of the parent process

Let's say I have a main.sh script that will be calling one.sh via a subshell.

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                                                        

Now, each of them works as expected:

$ ./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

But, when I pipe something to main.sh , I was not expecting (or, rather, I don't want) one.sh to know about the piped content, because I thought one.sh was in its own subshell in one_result) :

$ echo "HELLO WORLD MAIN" | ./main.sh                                                                                
one_result) 111 WORLD MAIN
one_piped) 111 quick brown fox

Is it the case my if condition in one.sh is not what I want? I would like one.sh to not create any side-effects of consuming my main.sh 's stdin - since now it has consumed it, my main.sh is now effectively stdin -less, as stdin can only be read once unless I store it away.

Thoughts? TIA.

In general, subshells (and other processes that a shell spawns) inherit stdin from the parent shell. If that's the terminal, your test will work as you expect; if it's a pipe then it will detect that it's a pipe and go ahead and consume it. There's no way for the subshell to tell whether it got that pipe by having it explicitly assigned (as in echo "the quick brown fox" | ./one.sh ) or via inheritance.

As far as I can see, the only way to avoid this problem is to explicitly redirect one.sh 's input to something other than a pipe to avoid it inheriting the parent shell's stdin (which might be a pipe). Something like:

echo "one_nonpipe) $(./one.sh </dev/null)"                            

echo "one_piped) $(echo "the quick brown fox" | ./one.sh)"

... but what'd be even better would be to add a flag to tell one.sh whether to read from stdin or not, rather than trying to figure it out from the type of file attached to 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)"

(Note that I also removed the unnecessary parentheses around the if block -- they created another level of subshell for no good reason.)

By default, every process inherits its standard input (and output and error) from its parent. Input redirection and pipes are two ways to change standard input to a different file descriptor when starting the child process.

It is the responsibility of main.sh , if needs to read from its standard input, to know that one.sh also reads from standard input, and it will need to prevent one.sh from consuming it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM