-->

How to find next available file descriptor in Bash

2020-05-28 10:46发布

问题:

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?

回答1:

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.



回答2:

If you do not care if the file descriptor is above 9, you may ask the shell itself to provide one. Of course, the fd is guaranteed to be free by the own shell.

Feature available since bash 4.1+ (2009-12-31) {varname} style automatic file descriptor allocation

$ exec {var}>hellofile
$ echo "$var"
15

$ echo "this is a test" >&${var}
$ cat hellofile
this is a test

$ exec {var}>&-                      # to close the fd.

In fact, in linux, you can see the open fds with:

$ ls /proc/$$/fd
0 1 2 255


回答3:

The other answer that uses pre-bash-4.1 syntax does a lot unnecessary subshell spawning and redundant checks. It also has an arbitrary cut-off for the maximum FD number.

The following code should do the trick with no subshell spawning (other than for a ulimit call if we want to get a decent upper limit on FD numbers).

fd=2 max=$(ulimit -n)

while ((++fd < max)); do
   ! true <&$fd && break
done 2>/dev/null && echo $fd
  • Basically we just iterate over possible FDs until we reach one we can't dupe.
  • In order to avoid the Bad file descriptor error message from the last loop iteration, we redirect stderr for the whole while loop.


回答4:

For those who prefer one-liners and doesnt have Bash-4.1+ available:

{ seq 0 255; ls -1 /proc/$$/fd; } | sort -n | uniq -u | head -1


回答5:

I decided to summarize the brilliant answer given by @paxdiablo into the single shell function with two auxiliary ones:

fd_used_sym() {
    [ -e "/proc/$$/fd/$1" ]
}

fd_used_rw() {
    : 2>/dev/null >&$1 || : 2>/dev/null <&$1
}

fd_free() {
    local fd_check
    if [ -e "/proc/$$/fd" ]
    then
        fd_check=fd_used_sym
    else
        fd_check=fd_used_rw
    fi

    for n in {0..255}
    do
        eval $fd_check $n || {
            echo "$n"
            return
        }
    done
}

There is sort of simplifications -- escape from auxiliary functions without losing the main functionality:

fd_free() {
    local fd_check
    if [ -e "/proc/$$/fd" ]
    then
        fd_check='[ -e "/proc/$$/fd/$n" ]'
    else
        fd_check=': 2>/dev/null >&$n || : 2>/dev/null <&$n'
    fi

    for n in {0..255}
    do
        eval $fd_check || {
            echo "$n"
            return
        }
    done
}

Both functions check availability of the file descriptor and output the number of the first found free file descriptor. Benefits are following:

  • both check ways are implemented (via /proc/$$/fd/X and R/W to a particular FD)
  • builtins are used only