在Bash中,如何找到编号最小的未使用文件描述符?

Mik*_*und 40 bash file-io

在Bash脚本中,是否可以在"尚未使用的编号最小的文件描述符"上打开文件?

我四处寻找如何做到这一点,但似乎Bash总是要求你指定数字,例如:

exec 3< /path/to/a/file    # Open file for reading on file descriptor 3.
Run Code Online (Sandbox Code Playgroud)

相反,我希望能够做类似的事情

my_file_descriptor=$(open_r /path/to/a/file)
Run Code Online (Sandbox Code Playgroud)

这将打开'file'以读取尚未使用的编号最小的文件描述符,并将该编号分配给变量'my_file_descriptor'.

小智 63

我知道这个帖子已经过时了,但是相信最好的答案是缺失的,对于像我这样来寻找解决方案的人来说会很有用.

Bash和Zsh已经内置了查找未使用的文件描述符的方法,而无需编写脚本.(我发现破折号没有这样的东西,所以上面的答案可能仍然有用.)

注意:这会找到最低的未使用文件描述符> 10,而不是总体最低值.

$ man bash /^REDIRECTION (paragraph 2)
$ man zshmisc /^OPENING FILE DESCRIPTORS
Run Code Online (Sandbox Code Playgroud)

示例适用于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
Run Code Online (Sandbox Code Playgroud)

完成后关闭文件描述符:

$ exec {FD}>&-
Run Code Online (Sandbox Code Playgroud)

以下显示文件描述符现已关闭:

$ echo line 3 >&$FD
bash: $FD: Bad file descriptor
zsh: 10: bad file descriptor
Run Code Online (Sandbox Code Playgroud)

  • 非常感谢不让这个帖子的年龄阻止你发布这个!事实上,你的回答是第一个(这个帖子)<i>真的</ i>提供了我在18个月前发布问题时所寻找的东西; 其他答案真的是"解决方案".但是,Bash 4.0或更早版本不支持解决方案中使用的{FD}>功能.它是在Bash 4.1-alpha中引入的,因此在其他答案中提供的解决方法无论如何对于陷入Bash 4.0或更早版本的人来说都是有价值的. (7认同)
  • 真好!也适用于典型的“ flock”场景:`(flock $ FD; echo得到了锁;){FD}&gt; mylock` (2认同)

Bas*_*tch 7

如果它在Linux上,您可以随时读取/proc/self/fd/目录以找出使用过的文件描述符.


Cor*_*ren 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;
}
Run Code Online (Sandbox Code Playgroud)

可以使用以下功能打开文件进行输入和输出:

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

然后,可以像往常一样使用前面的描述符来读写数据:

read -u $inputFile data
echo "input file contains data \"$data\"" >&$log
Run Code Online (Sandbox Code Playgroud)


Kin*_*ong 5

我需要支持双方在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 '\012\015' '  ') "
  local i=0
  while [[ $i -lt $max_fd ]]; do
    if [[ ! $used_fds =~ " $i " ]]; then
      echo "$i"
      break
    fi
    (( i = i + 1 ))
  done
}
Run Code Online (Sandbox Code Playgroud)

例如复制标准输出,你可以这样做:

newfd=$(find_unused_fd)
eval "exec $newfd>&1"
Run Code Online (Sandbox Code Playgroud)

  • 我喜欢这个主意。我把它打到了 1 内线。`/bin/ls -1 /dev/fd | /bin/ls -1 sed 's/.*\///' | 排序 -n | awk 'n&lt;$1{退出}{n=$1+1}END{print n}'` (2认同)