如何在 Bash 中找到下一个可用的文件描述符?

How to find next available file descriptor in Bash?

如何确定文件描述符当前是否正在 Bash 中使用?例如,如果我有一个读取、写入和关闭 fd 3 的脚本,例如

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

确保我不会干扰可能在我的脚本运行之前设置的描述符的其他目的的最佳方法是什么?我需要将整个脚本放在一个子 shell 中吗?

在纯bash中,您可以使用以下方法查看给定的文件描述符(在本例中为3)是否可用:

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

这基本上是通过测试您是否可以读取或写入给定的文件句柄来工作的。假设你两者都做不到,使用它可能没问题。

找到第一个空闲描述符而言,您可以使用类似的东西:

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}"

运行 该脚本将 5 作为第一个自由描述符,但您可以尝试使用 exec 行,看看如何使较早的可用描述符允许代码片段找到它。


正如评论中指出的那样,提供 procfs 的系统(/proc 文件系统)有另一种检测空闲描述符的方法。 /proc/PID/fd 目录将为每个打开的文件描述符包含一个条目,如下所示:

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

因此您可以使用类似于上述脚本的脚本在其中找到一个免费条目:

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}"

请记住,并非所有提供 bash 的系统都必然具有 procfs(BDS 和 CygWin 就是例子)。如果那是 OS 您的目标,那么 Linux 应该没问题。


当然,您仍然可以选择将整个 shell 脚本包装成如下形式:

(
    # Your current script goes here
)

在这种情况下,文件句柄将保留在这些括号之外,您可以在其中随意操作它们。

如果不介意文件描述符是否在9以上,可以要求shell自己提供一个。当然fd是自己保证免费的shell.

自 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.

事实上,在linux中,你可以看到打开的fds:

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

另一个使用 pre-bash-4.1 语法的答案做了很多不必要的子 shell 生成和冗余检查。它也有一个任意的 cut-off 作为最大 FD 数量。

下面的代码应该可以解决没有子 shell 产生的问题(除了 ulimit 调用,如果我们想获得合适的 FD 数量上限)。

fd=2 max=$(ulimit -n) &&
while ((++fd < max)); do
   ! <&$fd && break
done 2>/dev/null &&
echo $fd
  • 基本上我们只是迭代可能的 FD,直到我们找到一个我们无法复制的 FD。
  • 为了避免来自上次循环迭代的 Bad file descriptor 错误消息,我们为整个 while 循环重定向 stderr。

对于那些喜欢单线且没有 Bash-4.1+ 可用的人:

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

我决定将@paxdiablo 给出的精彩答案总结为一个 shell 函数和两个辅助函数:

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

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

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
}

有一些简化 -- 在不丢失主要功能的情况下摆脱辅助功能:

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
}

这两个函数都检查文件描述符的可用性并输出第一个找到的空闲文件描述符的编号。好处如下:

  • 两种检查方式都已实现(通过 /proc/$$/fd/X 和 R/W 到特定的 FD)
  • 仅使用内置函数