Reputation: 12028
I want to open a TCP socket in bash, using the /dev/tcp/host/port
psuedo-device syntax, but I need to recover gracefully if the host/port
endpoint is not listening. This is easy if all I'm doing is sending data to the socket, or pulling data from it, since I can just use one-off redirection, i.e.:
echo 'ping' 2> >(logger) >/dev/tcp/127.0.0.1/8080
Or, reading from the socket:
cat 2> >(logger) </dev/tcp/127.0.0.1/8080
Where logger
is the standard logger that writes lines from stdin to syslog.
The problem is that in some cases I need to write a request to the socket and get a response back. So I need to open a file descriptor for the socket with exec
, but in this case, capturing the "-bash: connect: Connection refused" error is harder, since I don't want to redirect all output in the shell, which is what 2> >(logger)
does when used in the context of exec. The following works great if the endpoint is down (since bash hits an error when opening the socket, and resets all redirections), but renders my shell pretty useless if it is able to make the connection.
exec 2> >(logger) 3<>/dev/tcp/127.0.0.1/8080
So far, the best solution I've come up with is the following, where I open a file descriptor (fd 9) pointing to the original STDERR, redirect STDERR (fd 2) to my logger, open the socket on fd 3, then set fd 2 back to the original STDERR, and close my temporary STDERR alias (fd 9).
exec 9>&2 2> >(logger) 3<>/dev/tcp/127.0.0.1/8080 2>&9 9>&-
Which seems like a lot of work for something so simple. Is there a way to tell bash that I want to redirect STDERR while bash runs the exec
command, instead of considering the redirection as a literal argument to exec
?
Please keep your answers / suggestions limited to bash (up to bash 4.3.30). I know about telnet
, netcat
, socat
, etc. The idea for the project I'm working on (where this bash issue came up) arose from frustration at how slow a particular tool is, which is due to how it proxies all of its shell helpers off to external (python) interpreters. Also, I'd like to avoid mkfifo
if possible.
Upvotes: 1
Views: 1793
Reputation: 2292
eval 'exec 3<>/dev/tcp/127.0.0.1/8080' 2> >(logger)
or, much better because it does not destroy the old file descriptor 3:
eval 'exec {fd}<>/dev/tcp/127.0.0.1/8080' 2> >(logger)
which uses the smallest free file descriptor and assigns it to fd.
unset fd
eval 'exec {fd}<>/dev/tcp/127.0.0.1/8080' 2> >(logger)
if test $? != 0 # status of the last command - 0 if the connection succeeded
then
echo cannot open connection >&2
fi
# $? shows the last status only. Later you can check success this way:
if test -z "$fd"
then
echo could not open connection >&2 # past tense because it's later now
else
echo some data to transmit >&$fd
IFS= read -r just_on_line_of_response <&$fd
fi
Once you are done, delete the connection using
exec {fd}>&-
This will work as well:
eval "exec $fd>&-"
But this fails miserably plus closes your stdout:
exec $fd>&-
Upvotes: 1