I have a server application that prints to standard output something along Listening on http://localhost:12345
, and then waits until its output is closed (e.g. via Ctrl-C). I want to start it, get its address, run commands using it, and then close or kill it. How can I do this?
I came up with this:
trap 'kill $(jobs -p)' EXIT
server > tempfile &
url=$(tail -f tempfile | perl -nle "m/(http\S )/; print \$1; exit 0;")
run-thing --using-server="$url"
Runnable code that approximates the above:
$ cat test.sh
trap 'kill $(jobs -p)' EXIT
python3 -uc 'import time; print("Listening on http://localhost:123"); time.sleep(99)' > tempfile &
url=$(tail -f tempfile | perl -nle "m/(http\S )/; print \$1; exit 0;")
echo "using server at $url, running jobs: $(jobs -p)"
$ bash test.sh; ps aux | grep [p]ython || echo server successfully killed
using server at http://localhost:123, running jobs: 2898
server successfully killed
I feel that this approach is pretty awful for a number of reasons. What else can I do?
CodePudding user response:
Using coprocesses
trap 'pkill -P $COPROC_PID' EXIT
coproc { server; }
url=$(<&${COPROC[0]} perl -nle 'm/(http\S )/; print $1; exit 0;')
run-thing --using-server="$url"
Runnable code that approximates the above:
$ cat test.sh
trap 'pkill -P $COPROC_PID' EXIT
coproc { python3 -uc 'import time; print("Listening on http://localhost:123"); time.sleep(99)'; }
url=$(<&${COPROC[0]} perl -nle 'm/(http\S )/; print $1; exit 0;')
echo "using server at $url, server $(pgrep python >/dev/null && echo is running)"
$ bash test.sh; ps aux | pgrep python || echo server successfully killed
using server at http://localhost:123, server is running
Terminated
server successfully killed
Using process substitution
trap "kill 0" EXIT
{ url=$(perl -nle 'm/(http\S )/; print $1; exit 0;'); } < <(server)
run-thing --using-server="$url"
This was found thanks to the helpful advice from libera's #bash. Runnable code that approximates the above:
$ cat test.sh
trap "kill 0" EXIT
{ url=$(perl -nle 'm/(http\S )/; print $1; exit 0;'); } \
< <(python3 -uc 'import time; print("Listening on http://localhost:123"); time.sleep(99)')
echo "using server at $url, server $(pgrep python >/dev/null && echo is running)"
$ bash test.sh; ps aux | pgrep python || echo server successfully killed
using server at http://localhost:123, server is running
server successfully killed
I'm not entirely sure why I need {}
, which is a command grouping. It's like ()
but it doesn't spawn a subshell. kill 0
will kill all processes in the current process group. This may kill the process that started current script. You can also kill the server directly. As <()
does spawn a subshell, you can find it by parent:
{ url=$(perl -nle 'm/(http\S )/; print $1; exit 0;');
run-thing --using-server="$url";
} < <(server)
pkill -P $!
CodePudding user response:
You could use a fifo as a custom file descriptor (#3 in the code) for that:
tmpdir=$(mktemp -d)
mkfifo "$tmpdir/fifo"
exec 3<> "$tmpdir/fifo"
tail -f tempfile | perl -lne 'm/(http\S )/; print $1; exit 0;' >&3 &
IFS='' read -r url <&3 # will block until one line is read
printf 'We got this: %s\n' "$url"
kill %1 # kill the background job
exec 3>&-
rm -rf "$tmpdir"
remark: with tail -f file | somecommand
you might not get any output (for an undefined amount of time) because of somecommand
internal buffering. That said, you did work around that with the exit 0
of your perl
one-liner.