Sunday, July 25, 2010

Closing Arbitrary File Descriptors in Bash

While writing a post on file descriptors, I ended up on a tangent trying to answer a question that seemed like it should be simple and obvious. How do you close arbitrary file descriptors in a bash shell script? The chapter on I/O Redirection in the Advanced Bash-Scripting Guide shows the typical examples that you see everywhere. All of these examples use a hardcoded number to identify the descriptor, not a variable. Try to use a variable and bash gets confused thinking the value is the name of a command you wish to execute. For example:
#!/bin/bash
num=5
exec $num>&-
When you run it yields:
$ ./test.sh 
./test.sh: line 3: exec: 5: not found
I eventually found the answer on another blog that had a post about daemonizing bash. The technique is to use the built-in eval command. The variable will be substituted in a string and the string will get executed by eval. It is kind of obvious once I saw how someone else had done it, but for whatever reason it did not occur to me. Since I like examples, here is a script with a closefd function for closing file descriptors:
#!/bin/bash

function closefd {
    local fd=$1
    echo "closing $fd"
    eval "
        exec $fd>&-
        exec $fd<&-
    "
}

function listfds {
    local pid=$1
    local label=$2
    ls /proc/$pid/fd | sort -n | awk '{printf("%s ", $1)}' | sed "s/^/$label: /"
    echo ""
}

# Record the PID of this shell, need to make sure we are tracking fds of this
# shell and not some forked child
pid=$$

# Run commands
listfds $pid "before"
closefd 5
listfds $pid "middle"
closefd 13
listfds $pid "after"
You can run this script with a simple wrapper that opens up a bunch of files that will be inherited when a new process is forked. In my case I used the testclose C program from the forking file descriptors post after removing the call to closefrom. Running the script shows output like:
$ ./testclose closefd.sh
opened 20 files
before: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 255 
closing 5
middle: 0 1 2 3 4 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 255 
closing 13
after: 0 1 2 3 4 6 7 8 9 10 11 12 14 15 16 17 18 19 20 21 22 255 
child 20710 exited with status 0

No comments:

Post a Comment