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

Kva*_*ass 8 bash file-descriptor subshell process-substitution

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

exec 3< <(some command here)
...
cat <&3
exec 3>&-
Run Code Online (Sandbox Code Playgroud)

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

sor*_*tar 12

如果你不在乎文件描述符是否高于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.
Run Code Online (Sandbox Code Playgroud)

事实上,在linux中,你可以看到open fds:

$ ls /proc/$$/fd
0 1 2 255
Run Code Online (Sandbox Code Playgroud)


pax*_*blo 7

在pure中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
Run Code Online (Sandbox Code Playgroud)

这基本上可以通过测试您是否可以读取或写入给定的文件句柄来实现.假设你不能做任何事情,那么可以使用它.

查找第一个自由描述符方面,您可以使用以下内容:

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}"
Run Code Online (Sandbox Code Playgroud)

运行该脚本5作为第一个自由描述符,但您可以使用这些exec行来查看如何使早期的可用代码段允许代码片段找到它.


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

pax> ls -1 /proc/$$/fd
0
1
2
255
Run Code Online (Sandbox Code Playgroud)

所以你可以使用类似上面的脚本在那里找到一个免费的条目:

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}"
Run Code Online (Sandbox Code Playgroud)

请记住,并非所有提供的系统bash都必须具备procfs(BDS和CygWin是示例).对于Linux来说应该没问题,如果那是您要定位的操作系统.


当然,您仍然可以选择将整个shell脚本包装为:

(
    # Your current script goes here
)
Run Code Online (Sandbox Code Playgroud)

在这种情况下,文件句柄将保留在这些括号之外,您可以根据需要在其中操作它们.

  • @Kvass,除非你在循环中进行多次迭代的*检测*,否则没有区别.即使*重定向*在一个循环中,很可能你会在循环开始之前检测到前面需要的任何空闲句柄,并且只需在循环中使用*那些描述符.应该没有相当大的性能损失. (2认同)
  • @sorontar,如果你有'procfs`实际上*可用*,这可能是一个选项,但它不是普遍存在(例如,BSD).鉴于该问题仅提到"bash",我认为提供便携式解决方案更好.我会记下你的建议. (2认同)

Gri*_*vit 7

使用pre-bash-4.1语法的另一个答案会做很多不必要的子shell生成和冗余检查.它还具有最大FD数的任意截止.

下面的代码应该没有子shell生成(ulimit如果我们想要在FD数字上得到一个合适的上限,那么除了调用之外).

fd=2 max=$(ulimit -n)

while ((++fd < max)); do
   ! true <&$fd && break
done 2>/dev/null && echo $fd
Run Code Online (Sandbox Code Playgroud)
  • 基本上我们只是迭代可能的FD,直到我们达到一个我们无法欺骗的FD.
  • 为了避免Bad file descriptor来自最后一次循环迭代的错误消息,我们将stderr重定向到整个while循环.