Reputation: 5488
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?
Upvotes: 1
Views: 96
Reputation: 5488
trap '[[ -v SERVER_PID ]] && pkill -P $SERVER_PID' EXIT
coproc SERVER { server; }
url=$(<&${SERVER[0]} perl -nle 'm/(http\S+)/; print $1; exit 0;')
run-thing --using-server="$url"
Here, -v
checks if the variable was set, in case the script fails before launching the server. Since the coprocess is not launched directly but in a subshell, you want to use pkill -P
that kills a process by its parent PID. Finally, <&${SERVER[0]}
reads from its stdout. 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
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. Just like coproc
does, <()
spawns a subshell, so you want to find the process by parent:
{ url=$(perl -nle 'm/(http\S+)/; print $1; exit 0;');
run-thing --using-server="$url";
} < <(server)
pkill -P $!
Upvotes: 1
Reputation: 17216
You could use a fifo and a custom file descriptor (#3
in the code):
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.
Upvotes: 1