在Bash脚本中,是否可以在“尚未使用的编号最小的文件描述符”上打开文件?
我已经四处查看了如何执行此操作,但似乎Bash始终要求您指定数字,例如像这样:
exec 3< /path/to/a/file # Open file for reading on file descriptor 3.
相反,我希望能够做类似
的事情my_file_descriptor=$(open_r /path/to/a/file)
将打开'file'以读取尚未使用的编号最小的文件描述符,并将该编号分配给变量'my_file_descriptor'。
答案 0 :(得分:59)
我知道这个帖子已经过时了,但我相信最好的答案都会丢失,对于像我这样来寻找解决方案的人来说会很有用。
Bash和Zsh已经内置了查找未使用的文件描述符的方法,而无需编写脚本。 (我发现破折号没有这样的东西,所以上面的答案可能仍然有用。)
注意:这会找到最低的未使用文件描述符&gt; 10,而不是最低整体。
$ man bash /^REDIRECTION (paragraph 2)
$ man zshmisc /^OPENING FILE DESCRIPTORS
示例适用于bsh和zsh。
打开一个未使用的文件描述符,并将号码分配给$ 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
完成后关闭文件描述符:
$ exec {FD}>&-
以下显示文件描述符现已关闭:
$ echo line 3 >&$FD
bash: $FD: Bad file descriptor
zsh: 10: bad file descriptor
答案 1 :(得分:7)
如果是在Linux上,您可以随时阅读/proc/self/fd/
目录以找出使用过的文件描述符。
答案 2 :(得分:5)
我修改了我的原始答案,现在有一个原始帖子的单行解决方案 以下函数可以存在于全局文件或源脚本中(例如〜/ .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="$1"
local mode="$2"
local var="$3"
# 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;
}
可以使用上述功能如下打开输入和输出文件:
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'
然后,人们会像往常一样使用前面的描述符来读取和写入数据:
read -u $inputFile data
echo "input file contains data \"$data\"" >&$log
答案 3 :(得分:2)
在Basile Starynkevitch对这个问题的回答中,他在2011年11月29日写道:
如果它在Linux上,您可以随时读取/ proc / self / fd /目录以找出使用过的文件描述符。
基于读取fd目录完成了几个实验后,我得到了以下代码,作为与我所寻找的“最接近的匹配”。我正在寻找的实际上是一个bash one-liner,比如
my_file_descriptor=$(open_r /path/to/a/file)
将找到最低的,未使用的文件描述符 AND 在其上打开文件 AND 将其分配给变量。如下面的代码所示,通过引入函数“lowest_unused_fd”,我至少得到一个“双线”(FD = $(lowest_unused_fd)后跟 eval“exec $ FD&lt; $ FILENAME” )为任务。我无法编写一个像上面的(虚构的)“open_r”一样的函数。如果有人知道该怎么做,请向前迈进!相反,我不得不将任务分成两个步骤:一步到找到未使用的文件描述符,一步到打开文件就可以了。还要注意,为了能够在函数中放置 find 步骤(“lowest_unused_fd”)并将其stdout分配给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<&-"
答案 4 :(得分:2)
我需要在Mac上支持两个 bash v3,在Linux上支持bash v4,其他解决方案需要bash v4或Linux,所以我想出了一个适用于两者的解决方案,使用{{ 1}}。
/dev/fd
例如对于dup stdout,你可以这样做:
find_unused_fd() {
local max_fd=$(ulimit -n)
local used_fds=" $(/bin/ls -1 /dev/fd | sed 's/.*\///' | tr '\012\015' ' ') "
local i=0
while [[ $i -lt $max_fd ]]; do
if [[ ! $used_fds =~ " $i " ]]; then
echo "$i"
break
fi
(( i = i + 1 ))
done
}
答案 5 :(得分:0)
Apple Mac OS X不是Linux。我在OS X上看不到任何'/ proc'文件系统。
我猜一个答案是使用“ zsh”,但是我想在“ bash”中有一个适用于OS X(aka BSD)和Linux的脚本。因此,现在是2020年,我使用的是最新版本的OS X(目前是Catalina),我意识到苹果似乎早就放弃了对Bash的维护。显然赞成Zsh。
这是我的多操作系统解决方案,用于在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;
}
Perl的-an
选项告诉它(-n
)运行一个隐含的while()
循环,逐行读取文件,然后(-a)
自动将其拆分为一组约定为@F
的单词,BEGIN
说在while()
循环之前要做什么,而END
说在此之后要做什么。 while()
循环会挑选出每行的字段[3]
,将其缩减为仅前导数字(即端口号),并将其保存在当前使用的端口号数组中,因此然后END
块会找到其插槽未被占用的最小整数。
更新:完成所有操作后,实际上我没有在自己的代码中使用此功能。我意识到KingPong和Bruno Bronsky的回答要优雅得多。但是,我将保留此答案;这可能对某人很有趣。