在 Bash 中,如何找到编号最低的未使用文件描述符?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/8297415/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
In Bash, how to find the lowest-numbered unused file descriptor?
提问by Mikko ?stlund
In a Bash-script, is it possible to open a file on "the lowest-numbered file descriptor not yet in use"?
在 Bash 脚本中,是否可以在“尚未使用的最低编号的文件描述符”上打开文件?
I have looked around for how to do this, but it seems that Bash always requires you to specify the number, e.g. like this:
我已经四处寻找如何做到这一点,但似乎 Bash 总是要求您指定数字,例如:
exec 3< /path/to/a/file # Open file for reading on file descriptor 3.
In contrast, I would like to be able to do something like
相反,我希望能够做类似的事情
my_file_descriptor=$(open_r /path/to/a/file)
which would open 'file' for reading on the lowest-numbered file descriptor not yet in use and assign that number to the variable 'my_file_descriptor'.
这将打开“文件”以读取尚未使用的最低编号的文件描述符,并将该编号分配给变量“my_file_descriptor”。
回答by weldabar
I know this thread is old, but believe that the best answer is missing, and would be useful to others like me who come here searching for a solution.
我知道这个线程很旧,但相信缺少最佳答案,并且对像我这样来这里寻找解决方案的其他人有用。
Bash and Zsh have built in ways to find unused file descriptors, without having to write scripts. (I found no such thing for dash, so the above answers may still be useful.)
Bash 和 Zsh 内置了查找未使用的文件描述符的方法,而无需编写脚本。(我没有发现 dash 这样的东西,所以上面的答案可能仍然有用。)
Note: this finds the lowest unused file descriptor > 10, not the lowestoverall.
注意:这会找到最低的未使用文件描述符 > 10,而不是最低的整体。
$ man bash /^REDIRECTION (paragraph 2)
$ man zshmisc /^OPENING FILE DESCRIPTORS
Example works with bsh and zsh.
示例适用于 bsh 和 zsh。
Open an unused file descriptor, and assign the number to $FD:
打开一个未使用的文件描述符,并将编号分配给 $FD:
$ exec {FD}>test.txt
$ echo line 1 >&$FD
$ echo line 2 >&$FD
$ cat test.txt
line 1
line 2
$ echo $FD
10 # this number will vary
Close the file descriptor when done:
完成后关闭文件描述符:
$ exec {FD}>&-
The following shows that the file descriptor is now closed:
以下显示文件描述符现在已关闭:
$ echo line 3 >&$FD
bash: $FD: Bad file descriptor
zsh: 10: bad file descriptor
回答by Basile Starynkevitch
If it is on Linux, you can always read the /proc/self/fd/directory to find out the used file descriptors.
如果是在 Linux 上,您可以随时读取/proc/self/fd/目录以找出使用的文件描述符。
回答by KingPong
I needed to support bothbash v3 on Mac and bash v4 on Linux and the other solutions require either bash v4 or Linux, so I came up with a solution that works for both, using /dev/fd.
我需要支持双方在Mac庆典v3和v4的bash的在Linux和其他解决方案需要既庆典V4或Linux,所以我想出了一个解决方案,两个作品,使用/dev/fd。
find_unused_fd() {
local max_fd=$(ulimit -n)
local used_fds=" $(/bin/ls -1 /dev/fd | sed 's/.*\///' | tr '25' ' ') "
local i=0
while [[ $i -lt $max_fd ]]; do
if [[ ! $used_fds =~ " $i " ]]; then
echo "$i"
break
fi
(( i = i + 1 ))
done
}
For example to dup stdout, you can do:
例如复制标准输出,你可以这样做:
newfd=$(find_unused_fd)
eval "exec $newfd>&1"
回答by Coren
I revised my original answer and now have a one line solution for the original post.
The following function could live in a global file or sourced script (e.g. ~/.bashrc):
我修改了我的原始答案,现在为原始帖子提供了一个单行解决方案。
以下函数可以存在于全局文件或源脚本(例如 ~/.bashrc)中:
# Some error code mappings from errno.h
readonly EINVAL=22 # Invalid argument
readonly EMFILE=24 # Too many open files
# Finds the lowest available file descriptor, opens the specified file with the descriptor
# and sets the specified variable's value to the file descriptor. If no file descriptors
# are available the variable will receive the value -1 and the function will return EMFILE.
#
# Arguments:
# The file to open (must exist for read operations)
# The mode to use for opening the file (i.e. 'read', 'overwrite', 'append', 'rw'; default: 'read')
# The global variable to set with the file descriptor (must be a valid variable name)
function openNextFd {
if [ $# -lt 1 ]; then
echo "${FUNCNAME[0]} requires a path to the file you wish to open" >&2
return $EINVAL
fi
local file=""
local mode=""
local var=""
# Validate the file path and accessibility
if [[ "${mode:='read'}" == 'read' ]]; then
if ! [ -r "$file" ]; then
echo "\"$file\" does not exist; cannot open it for read access" >&2
return $EINVAL
fi
elif [[ !(-w "$file") && ((-e "$file") || !(-d $(dirname "$file"))) ]]; then
echo "Either \"$file\" is not writable (and exists) or the path is invalid" >&2
return $EINVAL
fi
# Translate mode into its redirector (this layer of indirection prevents executing arbitrary code in the eval below)
case "$mode" in
'read')
mode='<'
;;
'overwrite')
mode='>'
;;
'append')
mode='>>'
;;
'rw')
mode='<>'
;;
*)
echo "${FUNCNAME[0]} does not support the specified file access mode \"$mode\"" >&2
return $EINVAL
;;
esac
# Validate the variable name
if ! [[ "$var" =~ [a-zA-Z_][a-zA-Z0-9_]* ]]; then
echo "Invalid variable name \"$var\" passed to ${FUNCNAME[0]}" >&2
return $EINVAL
fi
# we'll start with 3 since 0..2 are mapped to standard in, out, and error respectively
local fd=3
# we'll get the upperbound from bash's ulimit
local fd_MAX=$(ulimit -n)
while [[ $fd -le $fd_MAX && -e /proc/$$/fd/$fd ]]; do
((++fd))
done
if [ $fd -gt $fd_MAX ]; then
echo "Could not find available file descriptor" >&2
$fd=-1
success=$EMFILE
else
eval "exec ${fd}${mode} \"$file\""
local success=$?
if ! [ $success ]; then
echo "Could not open \"$file\" in \"$mode\" mode; error: $success" >&2
fd=-1
fi
fi
eval "$var=$fd"
return $success;
}
One would use the foregoing function as follows to open files for input and output:
可以使用上述函数如下打开文件进行输入和输出:
openNextFd "path/to/some/file" "read" "inputfile"
# opens 'path/to/some/file' for read access and stores
# the descriptor in 'inputfile'
openNextFd "path/to/other/file" "overwrite" "log"
# truncates 'path/to/other/file', opens it in write mode, and
# stores the descriptor in 'log'
And one would then use the preceding descriptors as usual for reading and writing data:
然后可以像往常一样使用前面的描述符来读取和写入数据:
read -u $inputFile data
echo "input file contains data \"$data\"" >&$log
回答by Mikko ?stlund
In Basile Starynkevitch's answer to this question, on Nov 29 2011, he writes:
在 2011 年 11 月 29 日 Basile Starynkevitch 对这个问题的回答中,他写道:
If it is on Linux, you can always read the /proc/self/fd/ directory to find out the used file descriptors.
如果是在 Linux 上,您可以随时阅读 /proc/self/fd/ 目录以找出使用的文件描述符。
Having done several experiments based on reading the fd directory, I have arrived at the following code, as the "closest match" to what I was looking for. What I was looking for was actually a bash one-liner, like
基于读取 fd 目录做了几个实验,我得到了以下代码,作为我正在寻找的“最接近的匹配”。我要找的实际上是一个 bash one-liner,比如
my_file_descriptor=$(open_r /path/to/a/file)
which would find the lowest, unused file descriptor ANDopen the file on it ANDassign it to the variable. As seen in the code below, by introducing the function "lowest_unused_fd", I at least get a "two-liner" (FD=$(lowest_unused_fd) followed byeval "exec $FD<$FILENAME") for the task. I have NOT been able to write a function that works like (the imaginary) "open_r" above. If someone knows how to do that, please step forward! Instead, I had to split the task into two steps: one step to findthe unused file descriptor and one step to openthe file on it. Also note that, to be able to place the findstep in a function ("lowest_unused_fd") and have its stdout assigned to FD, I had to use "/proc/$$/fd" instead of "/proc/self/fd" (as in Basile Starynkevitch's suggestion), since bash spawns a subshell for the execution of the function.
这将找到最低的,未使用的文件描述符和打开卷上的文件和它赋值给变量。如下面的代码所示,通过引入函数“lowest_unused_fd”,我至少得到了任务的“双线”(FD=$(lowest_unused_fd)后跟eval“exec $FD<$FILENAME”)。我一直无法编写一个像上面的(虚构的)“open_r”那样工作的函数。如果有人知道如何做到这一点,请向前迈进!相反,我不得不将任务分成两步:一步找到未使用的文件描述符,一步打开其上的文件。还要注意的是,为了能够放置find步入一个函数(“lowest_unused_fd”)并将其标准输出分配给 FD,我不得不使用“/proc/$$/fd”而不是“/proc/self/fd”(如 Basile Starynkevitch 的建议),因为 bash为函数的执行生成一个子shell。
#!/bin/bash
lowest_unused_fd () {
local FD=0
while [ -e /proc/$$/fd/$FD ]; do
FD=$((FD+1))
done
echo $FD
}
FILENAME="/path/to/file"
# Find the lowest, unused file descriptor
#+ and assign it to FD.
FD=$(lowest_unused_fd)
# Open the file on file descriptor FD.
if ! eval "exec $FD<$FILENAME"; then
exit 1
fi
# Read all lines from FD.
while read -u $FD a_line; do
echo "Read \"$a_line\"."
done
# Close FD.
eval "exec $FD<&-"
回答by IAM_AL_X
Apple Mac OS X is not Linux. I don't see any '/proc' file system on OS X.
Apple Mac OS X 不是 Linux。我在 OS X 上看不到任何“/proc”文件系统。
I guess one answer is to use "zsh", but I want to have a script that works on both OS X (aka BSD) and Linux in "bash". So, here I am, in the year 2020, with the latest version of OS X, which at this moment is Catalina, and I realize that Apple seems to have abandoned maintenance of Bash long ago; apparently in favor of Zsh.
我想一个答案是使用“zsh”,但我希望有一个脚本可以在“bash”中同时在 OS X(又名 BSD)和 Linux 上运行。所以,我在这里,在 2020 年,拥有最新版本的 OS X,此时是 Catalina,我意识到 Apple 似乎早就放弃了对 Bash 的维护;显然支持 Zsh。
Here is my multi-OS solution to find the lowest unused file descriptor on Apple Mac OS X or Linux. I created an entire Perl script, and in-lined it into the Shell script. There must be a better way, but for now, this works for me.
这是我的多操作系统解决方案,用于在 Apple Mac OS X 或 Linux 上找到最低的未使用文件描述符。我创建了一个完整的 Perl 脚本,并将它嵌入到 Shell 脚本中。一定有更好的方法,但就目前而言,这对我有用。
lowest_unused_fd() {
# For "bash" version 4.1 and higher, and for "zsh", this entire function
# is replaced by the more modern operator "{fd}", used like this:
# exec {FD}>myFile.txt; echo "hello" >&$FD;
if [ $(uname) = 'Darwin' ] ; then
lsof -p $$ -a -d 0-32 | perl -an \
-e 'BEGIN { our @currentlyUsedFds; };' \
-e '(my $digits = $F[3]) =~ s/\D//g;' \
-e 'next if $digits eq "";' \
-e '$currentlyUsedFds[$digits] = $digits;' \
-e 'END { my $ix;
for( $ix=3; $ix <= $#currentlyUsedFds; $ix++) {
my $slotContents = $currentlyUsedFds[$ix];
if( !defined($slotContents) ) {
last;
}
}
print $ix;
}' ;
else
local FD=3
while [ -e /proc/$$/fd/$FD ]; do
FD=$((FD+1))
done
echo $FD
fi;
}
The -anoptions to Perl tells it to (-n) run an implied while()loop that reads the file line by line and (-a)auto-split it into an array of words which, by convention, is named @F. The BEGINsays what to do before that while()loop, and the ENDsays what to do after. The while()loop picks out field [3]of each line, reduces it to just its leading digits, which is a port number, and saves that in an array of port numbers that are currently in use, and therefore are unavailable. The ENDblock then finds the lowest integer whose slot is not occupied.
-anPerl的选项告诉它 ( -n) 运行一个隐含的while()循环,该循环逐行读取文件并(-a)自动将其拆分为一个单词数组,按照惯例,该数组命名为@F。BEGIN说明在该while()循环之前要做什么,以及END说之后做什么。while()循环挑选出[3]每一行的字段,将其减少到仅其前导数字,这是一个端口号,并将其保存在当前正在使用的端口号数组中,因此不可用。END块然后找到其槽位未被占用的最小整数。
Update:After doing all that, I actually am not using this in my own code. I realized that the answer from KingPong and Bruno Bronsky is far more elegant. However, I will leave this answer in place; it might be interesting to somebody.
更新:完成所有这些之后,我实际上并没有在我自己的代码中使用它。我意识到 KingPong 和 Bruno Bronsky 的回答要优雅得多。但是,我会保留这个答案;对某人来说可能很有趣。

