Bash Redirection for Built-in Commands

Consider we write a bash script that runs a while cycle with I/O redirection.

$ cat redirection.sh
read a_line
echo "1: I have scanned: $a_line"

while read a_line; do
echo "2: While has scanned: $a_line"
done < /tmp/for_a_while

read a_line
echo "3: finally, I have scanned: $a_line"

Let us prepare input files and run the script like this:

$ echo "1
2
3" > /tmp/for_bash
$ echo "4" > /tmp/for_a_while
$ bash redirection.sh < /tmp/for_bash

What will be printed? It’s easy to guess that the while should print 4, but what will be scanned by the third read? It should continue reading /tmp/for_bash, however, bash doesn’t spawn a new process for the while cycle and therefore cannot redirect stdin for this child process. So, the interpreter has to redirect its own stdin during the cycle and somehow restore it after the while. File position should be preserved so that the next line will be scanned (i.e., “2”): otherwise, we will read “1” again.

Run the script and ensure that it behaves exactly as was expected:

1: I have scanned: 1
2: While has scanned: 4
3: finally, I have scanned: 2

Let’s add some debug code and add a tricky thing: we will remove /tmp/for_bash in out while. We will use a special bash variable $$ that is pid of current bash interpreter and get file descriptor data from /proc filesystem. (We cannot write /proc/self/fd since it will refer to ls process but not to bash.)

ls -l /proc/$$/fd/

read a_line
echo "1: I have scanned: $a_line"

while read a_line; do
ls -l /proc/$$/fd/
echo "2: While has scanned: $a_line"
rm -f /tmp/for_bash
done < /tmp/for_a_while

ls -l /proc/$$/fd/

read a_line
echo "3: Finally, I have scanned: $a_line"

Test it:

$ bash redirection.sh < /tmp/for_bash
total 0
lr-x------ 1 alessio alessio 64 Oct  3 12:13 0 -> /tmp/for_bash
lrwx------ 1 alessio alessio 64 Oct  3 12:13 1 -> /dev/pts/1
lrwx------ 1 alessio alessio 64 Oct  3 12:13 2 -> /dev/pts/1
lr-x------ 1 alessio alessio 64 Oct  3 12:13 255 -> /home/alessio/projects/rsrch/bash/redirection.sh
l-wx------ 1 alessio alessio 64 Oct  3 12:13 8 -> pipe:[9740]
1: I have scanned: 1
total 0
lr-x------ 1 alessio alessio 64 Oct  3 12:13 0 -> /tmp/for_a_while
lrwx------ 1 alessio alessio 64 Oct  3 12:13 1 -> /dev/pts/1
lr-x------ 1 alessio alessio 64 Oct  3 12:13 10 -> /tmp/for_bash
lrwx------ 1 alessio alessio 64 Oct  3 12:13 2 -> /dev/pts/1
lr-x------ 1 alessio alessio 64 Oct  3 12:13 255 -> /home/alessio/projects/rsrch/bash/redirection.sh
l-wx------ 1 alessio alessio 64 Oct  3 12:13 8 -> pipe:[9740]
2: While has scanned: 4
total 0
lr-x------ 1 alessio alessio 64 Oct  3 12:13 0 -> /tmp/for_bash (deleted)
lrwx------ 1 alessio alessio 64 Oct  3 12:13 1 -> /dev/pts/1
lrwx------ 1 alessio alessio 64 Oct  3 12:13 2 -> /dev/pts/1
lr-x------ 1 alessio alessio 64 Oct  3 12:13 255 -> /home/alessio/projects/rsrch/bash/redirection.sh
l-wx------ 1 alessio alessio 64 Oct  3 12:13 8 -> pipe:[9740]
3: Finally, I have scanned: 2

The first ls is simple: we have stdin redirected from /tmp/for_bash and both stdout and stderr write to pseudo terminal.

The second ls shows the magic: bash doesn’t close /tmp/for_bash to redirect stdin in the cycle. Instead, it duplicates file descriptor 10 that will remain opened at the same position as out stdin was before the while. This is done with a special call dup2(0, 10). Now bash can safely redirect its own stdin and temporarily forget about file descriptor 10.

The third ls is predictable now. Bash calls dup2(10, 0) and makes stdin refer to the good old /tmp/for_bash that was deleted by rm but its data still resides on the filesystem until all its file descriptors are closed. Now bash reads “2” without any problem and exits. Here /tmp/for_bash is really removed and inaccessible:

$ ls /tmp/for_bash
ls: cannot access /tmp/for_bash: No such file or directory

The same principles are used by bash for other code executed in current interpreter, such as {} or for.

Well, bash is an excellent tool that just does the right thing! Of course, if you don’t forget about string quotation 😉

Advertisements

One response to “Bash Redirection for Built-in Commands

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s