Sockets In Your Shell

Something I learned recently and I thought was amazing - you can create sockets straight from your shell! Well, assuming you use bash or zsh - from some surface level digging, I couldn’t find anything for fish.

Here’s how it works:

bash

Bash supports tcp and udp connections out of the box, and does so with an imaginary device in /dev. Enter

$ echo "text!" > /dev/$PROTO/$HOST/$PORT

And you’ll create a connection to HOST:PORT. $PROTO can be tcp or udp. If the connection can’t be made, writing to/reading the file will fail.

Along with being easy to access from the terminal, it’s very handy for scripts, especially if you don’t have nc/telnet. For example, if a local build of a web app runs on port 8000, you can check if it’s running with:1

#!/bin/bash
if exec 3>/dev/tcp/localhost/8000 ; then
	echo "server up!"
else
	echo "server down."
fi

And then use that information somewhere else.

If you’re unfamiliar, exec without any arguments is used to redirect file descriptors and files. By associating fd 3 with /dev/tcp/localhost/4000, it attempts to create a file there and thus a connection. We use > to open the socket for writing, although we don’t need to write anything in this case.

By using <> we can open a file for reading and writing, and use it to create a super simple curl:

#!/bin/bash
exec 3<>/dev/tcp/"$1"/80
echo -e "GET / HTTP/1.1\n" >&3
cat <&3
$ ./simplecurl www.google.com
HTTP/1.1 200 OK
Date: Thu, 03 Dec 2020 00:57:30 GMT
Expires: -1
....
<google website>

I’m sure you can see the power of being able to open sockets with bash alone. Go play around with it!

zsh

zsh has an external module you can load in order to use it’s socket capabilities. It doesn’t support udp like bash, but it’s more powerful in a few ways!

To load the module, put the following in your .zshrc or run it in your shell:

$ zmodload zsh/net/tcp

We now have access to the zsh networking builtin - ztcp!

ztcp allows creating connections, like bash, but also allows listening for connections.

Straight from the zsh docs, we can create a connection between two machines with ztcp:

# host machine:
ztcp -l 7128
lfd=$REPLY
ztcp -a $lfd
talkfd=$REPLY

# client machine
ztcp HOST 7128
talkfd=$REPLY

The $REPLY variable here is a file descriptor returned by the last ztcp command, referring to the socket/connection it just created.

So, talkfd on both machines is a file descriptor for talking to the other:

# host machine
echo -e "hello!" >&$talkfd

# client machine
read -r line <&$talkfd; print -r - $line
> hello!

Again, there’s a lot more you can do, especially with the ability to listen for connections.

Hope this was as interesting to you as it was to me!


  1. Thanks to u/barubary for pointing out why the previous version of this code was wrong!