I am trying to do something that involves scripts calling other scripts in subshells to capture their output.
One of the scripts needs to have a side effect of starting a background process. This all works when executed directly, but it blocks when called within a subshell.
As a self-contained example consider the following 2 scripts:
test1.sh
#!/bin/bash
echo $(./test2.sh)test2.sh
#!/bin/bash
(yes > /dev/null ; echo 'yes killed') &
echo successWhen I run test2.sh by itself, I get the expected result of "success" on the terminal, and yes running in the background. Killing yes prints "yes killed" to the terminal as expected.
When I run test1.sh I expect to get the same behavior, but what actually happens is that the terminal hangs until I kill yes after which "success yes killed" is printed to the terminal.
What do I change to these scripts so that I can get the same behavior from calling either one?
The premise here is that the subshell evaluation in test1.sh will actually be stored in a variable for later use. The background process started in test2.sh should live past the execution of either script.
4 Answers
Command substitution $(...) turns output into arguments. It first needs the whole output, because it's not possibly to supply arguments dynamically one by one.
As @choroba and @GordonDavisson suggested, command substitution $( ... ) will not return until all of its stdout has disconnected [1].
The trick here is that even if you redirect all stdout of the commands, the subshell itself eg. ( ... ) still has its stdout attached. This means that the following will not work:
#!/bin/bash
(yes > /dev/null ; echo 'yes killed' > /dev/null) &
echo successBut this will:
#!/bin/bash
(yes ; echo 'yes killed' ) > /dev/null &
echo successNote: You will no longer get any output to the terminal or stdout like this, but for my purpose this is not an issue. If necessary you could always redirect to a file or something if the output is needed
[1] See also
1I think bash's disown is the answer. See help disown.
Use it like this:
yes &>/dev/null </dev/zero &
disown -h $!Redirect the output or closing terminal would result in SIGHUP signal or a broken pipe for output (SIGPIPE) eventually blocking or killing the program.
If you care about output redirect it to a file. Or use nohup like this:
nohup yes &
disown $!A good explanation of nohup and disown.
0You don't need echo to run another shell script
#!/bin/bash
#echo $(./test2.sh)
./test2.sh