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

15

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

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

如何最好地确保我的脚本不会干扰在我脚本运行之前可能已经设置的描述符的其他用途?我需要把整个脚本放在子shell中吗?


我没有看到[[[的选项来测试“文件描述符是否打开”。最接近的方法是-t number,它测试文件描述符是否打开并连接到终端。让脚本解释这些数字很麻烦;你不能使用>&$number,所以你必须玩弄子shell之类的东西。编写一个测试给定文件描述符的程序不难,但生成用于使用任意数字的shell脚本可能会很棘手。因此,大多数人都不会费心去做。 - Jonathan Leffler
通常脚本在子shell中运行,除非你使用source命令。 - jarno
1
这个回答解决了你的问题吗?在Bash中,如何找到最低编号的未使用文件描述符? - AdminBee
5个回答

30

如果您不关心文件描述符是否大于9,您可以请求shell本身提供一个。当然,fd保证由自己的shell释放。

自bash 4.1+(2009-12-31)以来可用的功能 {varname}样式自动文件描述符分配

$ 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中,您可以使用以下命令查看打开的文件描述符(open fds):
$ ls /proc/$$/fd
0 1 2 255

1
你需要 Bash 4.1 或更高版本才能使用这个功能。 {varname} 样式的自动文件描述符分配 4.1-alpha - user8017719
我正在运行4.4版本。 - Kvass
1
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - user8017719
@Kvass 如果你执行 exec {my_var} > hellofile,你会得到那个错误。注意大于号前面的空格。 - wjandrea

9

在纯粹的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(例如 BDSs 和 CygWin)。如果您的目标是 Linux,那么应该没问题。


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

(
    # Your current script goes here
)

在这种情况下,文件句柄将在括号外保留,您可以在其中自由地操纵它们。

1
它们的性能是否相同?有没有一种方法可以在不产生子shell性能惩罚的情况下完成这个任务?(注意:我对bash性能的熟悉程度有限,但我知道通常应尽可能避免使用子shell) - Kvass
2
@Kvass,除非你在一个循环中进行检测并且迭代次数很多,否则这不会有任何影响。即使重定向在循环内部,很可能你会在循环开始之前先检测到所需的空闲句柄,并且在循环中简单地使用这些描述符。这不应该导致明显的性能损失。 - paxdiablo
选择 ls /proc/$$/fd 列表中未提供的数字有什么问题吗? - user8017719
2
@sorontar,如果您实际上可以使用procfs,那可能是一个选项,但它并不普遍存在(例如BSD)。鉴于问题仅提到了bash,我认为提供一个可移植的解决方案更好。我会记下您的建议。 - paxdiablo
1
根据这个答案,您只需要测试一个方向。因此,rcorci任选其一即可。也许您可以缩减所需的命令。 - user8017719

8
使用pre-bash-4.1语法的另一个答案会产生很多不必要的子shell,以及冗余的检查。此外,它还有一个最大FD编号的任意截止值。
以下代码可以完成任务,而无需生成任何子shell(除了进行 ulimit 调用以获取FD号码的合理上限)。
fd=2 max=$(ulimit -n) &&
while ((++fd < max)); do
   ! <&$fd && break
done 2>/dev/null &&
echo $fd
  • 基本上我们只是迭代可能的FD,直到我们到达一个无法复制的FD。
  • 为了避免最后一次循环迭代出现“坏文件描述符”错误消息,我们将整个while循环的stderr重定向。

2

对于那些喜欢一行代码且没有Bash-4.1+的人:

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

1
请注意,并非所有操作系统都提供 /proc 目录;特别是 OSX 不支持。 - dimo414

0

我决定将@paxdiablo提供的精彩答案总结为一个带有两个辅助函数的单一shell函数:

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
}

有一种简化方式——在不失去主要功能的情况下避免使用辅助函数:

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和对特定FD的读写)
  • 仅使用内置功能

  1. 因为您在子shell中使用了$$,它将不会使用当前子shell的fd空间,而是使用父级的fd空间,从而导致错误(在多次调用和打开时)。考虑让函数设置一个变量而不是回显它,这将需要对其进行$()调用。这将使它保持在相同的子shell空间中。
  2. 我无法解释这种行为,但在某些情况下,方法fd_used_rw跳过了一个“不可见的fd 15”(实际上是打开的,但在/proc中不显示。我没有解释)。尽管fd_used_sym更快,但fd_used_rw更安全。
- Andy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接