Generally, a file descriptor is an index for an entry in a kernel-resident data structure containing the details of all open files. In Linux you manipulate files based on their descriptors.
The File descriptor is an integer that correspondents to the opened data stream. When a program starts, three descriptors are opened for I/O:
0 - input
1 - output
2 - error output
To demonstrate how these works let's create the following simple script:
And let's run it as a regular use:
As you can see we got the string "TEST" as well as an error message. All error messages in Linux are associated with the error file descriptor with an id of 2 and all non-error output with the id of 1. We can use simple redirects to change the output.
Let's redirect the standard out, which in the previous example printed the TEST string to /dev/null:
Since we redirected the standard out to /dev/null only the error is printed. If we don't want to see the error we can redirect it to /dev/null instead:
Or if we don't want any output at all we can do this in the following ways:
In the first example we redirect both standard out and error explicitly. In the second example &> accomplishes the same result, saving us some typing. And in the third example we redirect standard out first to /dev/null and then redirect error to standard output, which already points to /dev/null.
Now that we know how redirects work, lets create a stream to a file:
Line 1 creates a new file descriptor with id of 3 in the current shell (hence the exec) and redirects all input and output to the file testfile. Line 2 echoes TEST and redirects the standard out (id 1) to descriptor 3, which already points to the testfile. Line 3 closes the file descriptor.
If we cat the testfile we should see the TEST string:
We can redirect the new file descriptor (id 3) to standard input (id 0) and be able to read the content of the testfile that way:
By using redirects in this way we can actually use the two "special files" that the kernel provides:
/dev/tcp/host/port - If host is a valid hostname or Internet address, and port is an integer port number or service name, bash attempts to open a TCP connection to the corresponding socket.
/dev/udp/host/port - If host is a valid hostname or Internet address, and port is an integer port number or service name, bash attempts to open a UDP connection to the corresponding socket.
We can connect to a web server using it's IP address and port, and send a HTTP get request and then read the result by doing:
This will fetch index.html from google.com.
Here's an example of a simple bash IRC bot, that joins a channel and responds to few commands using streams and redirects:
Yet another example of using redirects is the following scenario: Let's say you have a server behind a NAT that you want to access from the Internet. There are many ways of doing this involving, port forwarding, or ssh tunnels, but I'll demonstrate exporting /bin/bash and using the /dev/tcp file system I've mentioned earlier.
1. On a server that is publicly accessible start netcat in listening mode on a port:
2. On the server behind the NAT export bash to the netcat server:
On your server running nc you'll now have a bash prompt waiting for you to use (notice the change of the hostname in the prompt).