1

I'm looking at https://stackoverflow.com/a/10225050/1737158 And in same Q there is an answer with timeout command but it's not in all OSes, so I want to avoid it.

What I try to do is:

demo="$(top)" &
TASK_PID=$!
sleep 3
echo "TASK_PID: $TASK_PID"
echo "demo: $demo"

And I expect to have nothing in $demo variable while top command never ends.

Now I get an empty result. Which is "acceptable" but when i re-use the same thing with the command which should return value, I still get an empty result, which is not ok. E.g.:

demo="$(uptime)" &
TASK_PID=$!
sleep 3
echo "TASK_PID: $TASK_PID"
echo "demo: $demo"

This should return uptime result but it doesn't. I also tried to kill the process by TASK_PID but I always get. If a command fails, I expect to have stderr captures somehow. It can be in different variable but it has to be captured and not leaked out.

randomir
  • 17,989
  • 1
  • 40
  • 55
Lukas Liesis
  • 24,652
  • 10
  • 111
  • 109
  • If `bash` can be compiled on system, then `timeout` can be compiled as well. – Ipor Sircer Nov 05 '17 at 11:57
  • 1
    the problem here is that the whole line is run as a backgroup process. You run a shell that assigns the variable demo. Then the shell is over and the demo variable is lost (it was already lost before since only available to this forked shell). Easily hinted by the output: `[1]+ Done demo="$(uptime)"` – A.B Nov 05 '17 at 12:04

1 Answers1

2

What happens when you execute var=$(cmd) &

Let's start by noting that the simple command in bash has the form:

[variable assignments] [command] [redirections]

for example

$ demo=$(echo 313) declare -p demo
declare -x demo="313"

According to the manual:

[..] the text after the = in each variable assignment undergoes tilde expansion, parameter expansion, command substitution, arithmetic expansion, and quote removal before being assigned to the variable.

Also, after the [command] above is expanded, the first word is taken to be the name of the command, but:

If no command name results, the variable assignments affect the current shell environment. Otherwise, the variables are added to the environment of the executed command and do not affect the current shell environment.

So, as expected, when demo=$(cmd) is run, the result of $(..) command substitution is assigned to the demo variable in the current shell.

Another point to note is related to the background operator &. It operates on the so called lists, which are sequences of one or more pipelines. Also:

If a command is terminated by the control operator &, the shell executes the command asynchronously in a subshell. This is known as executing the command in the background.

Finally, when you say:

$ demo=$(top) &
# ^^^^^^^^^^^ simple command, consisting ONLY of variable assignment

that simple command is executed in a subshell (call it s1), inside which $(top) is executed in another subshell (call it s2), the result of this command substitution is assigned to variable demo inside the shell s1. Since no commands are given, after variable assignment, s1 terminates, but the parent shell never receives the variables set in child (s1).

Communicating with a background process

If you're looking for a reliable way to communicate with the process run asynchronously, you might consider coprocesses in bash, or named pipes (FIFO) in other POSIX environments.

Coprocess setup is simpler, since coproc will setup pipes for you, but note you might not reliably read them if process is terminated before writing any output.

#!/bin/bash
coproc top -b -n3
cat <&${COPROC[0]}

FIFO setup would look something like this:

#!/bin/bash

# fifo setup/clean-up
tmp=$(mktemp -td)
mkfifo "$tmp/out"
trap 'rm -rf "$tmp"' EXIT

# bg job, terminates after 3s
top -b >"$tmp/out" -n3 &

# read the output
cat "$tmp/out"

but note, if a FIFO is opened in blocking mode, the writer won't be able to write to it until someone opens it for reading (and starts reading).

Killing after timeout

How you'll kill the background process depends on what setup you've used, but for a simple coproc case above:

#!/bin/bash
coproc top -b
sleep 3
kill -INT "$COPROC_PID"
cat <&${COPROC[0]}
randomir
  • 17,989
  • 1
  • 40
  • 55