14

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?

codeforester
  • 39,467
  • 16
  • 112
  • 140
Kvass
  • 8,294
  • 12
  • 65
  • 108
  • I don't see an option to `[` or `[[` that tests for 'file descriptor open'. The closest approach is `-t number` which tests whether the file descriptor is open _and_ connected to terminal. It's hell to get the script to interpret the numbers; you can't use `>&$number` so you would have to mess around with sub-shells or something. It wouldn't be hard to write a program that tests a given file descriptor, but generating the shell script to use an arbitrary number might be tricky. Most people, therefore, don't bother. – Jonathan Leffler Jan 12 '17 at 01:58
  • Usually scripts run in subshell, unless you source it. – jarno Nov 07 '19 at 15:32
  • 1
    Does this answer your question? [In Bash, how to find the lowest-numbered unused file descriptor?](https://stackoverflow.com/questions/8297415/in-bash-how-to-find-the-lowest-numbered-unused-file-descriptor) – AdminBee Oct 08 '21 at 08:30

5 Answers5

30

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
  • 1
    You need a bash 4.1 or more recent for this feature to work. [{varname} style automatic file descriptor allocation 4.1-alpha](http://wiki.bash-hackers.org/scripting/bashchanges) –  Jan 13 '17 at 00:08
  • I'm running 4.4 though – Kvass Jan 13 '17 at 01:33
  • 1
    It is running perfectly well here on 4.1 4.2 4.3 and 4.4. Are you copying the whole syntax: `exec {var}>hellofile` ? Yes redirection included as shown!!. –  Jan 13 '17 at 02:03
  • @Kvass If you do `exec {my_var} >hellofile` you will get that error. Note the space before the greater-than sign. – wjandrea Jul 31 '18 at 04:04
9

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.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • 1
    Are they equally performant? Is there a way to do this without incurring a subshell performance penalty? (note: my familiarity with bash performance is limited, but I understand subshells in general are best avoided when possible) – Kvass Jan 12 '17 at 02:08
  • 2
    @Kvass, unless you're doing the *detection* in a loop with many iterations, it will make no difference. Even if the *redirections* are within a loop, most likely you'll detect whatever free handles you need up front *before* the loop starts, and simply *use* those descriptors within the loop. There should be no sizable performance penalty. – paxdiablo Jan 12 '17 at 02:18
  • What's wrong with selecting a number not in the list provided by `ls /proc/$$/fd` ? –  Jan 12 '17 at 18:43
  • 2
    @sorontar, if you have `procfs` actually *available* to you, that may well be an option but it's not ubiquitous (e.g., the BSDs). Given the question mentions only `bash`, I thought it better to provide a portable solution. I'll make a note of your suggestion. – paxdiablo Jan 13 '17 at 00:33
  • 1
    According to [this answer](http://stackoverflow.com/a/41641522/6843677) you only need to test on one direction. So either `rco` or `rci` is plenty. Maybe you can trim down the needed commands. –  Jan 13 '17 at 20:41
8

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
   ! <&$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.
Grisha Levit
  • 8,194
  • 2
  • 38
  • 53
1

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
reddot
  • 764
  • 7
  • 15
  • 1
    Note that not all operating systems provide [`/proc`](https://en.wikipedia.org/wiki/Procfs); In particular OSX does not. – dimo414 Jun 14 '20 at 07:54
0

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
jsxt
  • 1,097
  • 11
  • 28
  • 1. Because you are using `$$` in a subshell, it will not be using the current subshells fd space, but the parents instead, resulting in errors (on multiple call+opens). Considering having the function set a variable rather than echoing it which would requiring a `$()` call to it. This will keep it in the same subshell space 2. I can't explain this other behavior, but in one case method fd_used_rw skips an "invisible fd 15 (that is actually open, but does not appear in /proc. I don't have an explanation for this). Even though fd_used_sym is faster, fd_used_rw is safer – Andy Jul 28 '20 at 16:14