В Bash, как найти неиспользуемый дескриптор файла с наименьшим номером? - PullRequest
38 голосов
/ 28 ноября 2011

Можно ли в 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)

, который откроет «файл» для чтения по дескриптору файла с наименьшим номером, который еще не используется, и присвоит этот номер переменной «my_file_descriptor».

Ответы [ 5 ]

59 голосов
/ 10 июня 2013

Я знаю, что эта ветка старая, но считаю, что лучший ответ отсутствует, и будет полезен для других, таких как я, которые приходят сюда в поисках решения.

Bash и Zsh имеют встроенные способы поиска неиспользуемых файловых дескрипторов без необходимости написания скриптов. (Я не нашел такой вещи для тире, поэтому приведенные выше ответы могут быть полезны.)

Примечание: здесь находится самый низкий неиспользуемый дескриптор файла> 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
7 голосов
/ 28 ноября 2011

Если это в Linux, вы всегда можете прочитать каталог /proc/self/fd/, чтобы узнать используемые дескрипторы файлов.

5 голосов
/ 05 марта 2013

Я пересмотрел свой первоначальный ответ и теперь у меня есть решение в одну строку для исходного сообщения.
Следующая функция может находиться в глобальном файле или исходном скрипте (например, ~ / .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
2 голосов
/ 15 ноября 2015

Мне нужно было поддерживать как bash v3 на Mac, так и bash v4 на Linux, а для других решений требуется либо bash 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
}

Например, чтобы дублировать стандартный вывод, вы можете сделать:

newfd=$(find_unused_fd)
eval "exec $newfd>&1"
2 голосов
/ 01 декабря 2011

В ответе Василия Старынкевича на этот вопрос 29 ноября 2011 г. он пишет:

Если это в Linux, вы всегда можете прочитать каталог / proc / self / fd /, чтобы найти используемые дескрипторы файлов.

Проведя несколько экспериментов, основанных на чтении каталога fd, я пришел к следующему коду, как «наиболее близкое соответствие» к тому, что я искал. На самом деле я искал однострочник bash, например

my_file_descriptor=$(open_r /path/to/a/file)

, который найдет самый низкий, неиспользуемый дескриптор файла И , откройте файл на нем И назначьте его переменной. Как видно из приведенного ниже кода, введя функцию «lower_unused_fd», я по крайней мере получаю «двухслойный» (FD = $ (lower_unused_fd) , за которым следует eval «exec $ FD <$ FILENAME») для задачи. Я НЕ был в состоянии написать функцию, которая работает как (воображаемый) "open_r" выше. Если кто-то знает, как это сделать, сделайте шаг вперед! Вместо этого мне пришлось разбить задачу на два шага: один шаг, чтобы <em>найти неиспользуемый дескриптор файла, и один шаг, чтобы открыть файл на нем. Также обратите внимание, что, чтобы иметь возможность поместить шаг find в функцию ("lower_unused_fd") и назначить его стандартный вывод для FD, мне пришлось использовать "/ proc / $$ / fd" вместо " / proc / self / fd "(как в предложении Бэзил Старынкевич), поскольку bash порождает подоболочку для выполнения функции.

#!/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<&-"
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...