bashfile-descriptorsubshellprocess-substitution

How to find next available file descriptor in Bash?


How can I figure out if a file descriptor is currently in use in Bash? For example, if I have a script that reads, writes, and closes fd 3, e.g.

exec 3< <(some command here)
...
cat <&3
exec 3>&-

what's the best way to ensure I'm not interfering with some other purpose for the descriptor that may have been set before my script runs? Do I need to put my whole script in a subshell?


Solution

  • In pure bash, you can use the following method to see if a given file descriptor (3 in this case) is available:

    rco="$(true 2>/dev/null >&3; echo $?)"
    rci="$(true 2>/dev/null <&3; echo $?)"
    if [[ "${rco}${rci}" = "11" ]] ; then
        echo "Cannot read or write fd 3, hence okay to use"
    fi
    

    This basically works by testing whether you can read or write to the given file handle. Assuming you can do neither, it's probably okay to use.

    In terms of finding the first free descriptor, you can use something like:

    exec 3>/dev/null    # Testing, comment out to make
    exec 4</dev/null    # descriptor available.
    
    found=none
    for fd in {0..200}; do
        rco="$(true 2>/dev/null >&${fd}; echo $?)"
        rci="$(true 2>/dev/null <&${fd}; echo $?)"
        [[ "${rco}${rci}" = "11" ]] && found=${fd} && break
    done
    echo "First free is ${found}"
    

    Running that script gives 5 as the first free descriptor but you can play around with the exec lines to see how making an earlier one available will allow the code snippet to find it.


    As pointed out in the comments, systems that provide procfs (the /proc file system) have another way in which they can detect free descriptors. The /proc/PID/fd directory will contain an entry for each open file descriptor as follows:

    pax> ls -1 /proc/$$/fd
    0
    1
    2
    255
    

    So you could use a script similar to the one above to find a free entry in there:

    exec 3>/dev/null    # Testing, comment out to make
    exec 4</dev/null    #   descriptor available.
    
    found=none
    for fd in {0..200} ; do
        [[ ! -e /proc/$$/fd/${fd} ]] && found=${fd} && break
    done
    echo "First free is ${found}"
    

    Just keep in mind that not all systems providing bash will necessarily have procfs (the BDSs and CygWin being examples). Should be fine for Linux if that's the OS you're targeting.


    Of course, you do still have the option of wrapping your entire shell script as something like:

    (
        # Your current script goes here
    )
    

    In that case, the file handles will be preserved outside those parentheses and you can manipulate them within as you see fit.