В Bash, как найти наименее нумерованный неиспользуемый дескриптор файла?
В Bash - script можно ли открыть файл на "дескрипторе файла с наименьшим номером, который еще не используется"?
Я посмотрел, как это сделать, но кажется, что 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".
Ответы
Ответ 1
Я знаю, что эта ветка устарела, но считаю, что лучший ответ отсутствует, и будет полезен для других, таких как я, которые приходят сюда искать решение.
Bash и Zsh построили способы поиска неиспользуемых дескрипторов файлов, без необходимости писать скрипты. (Я не нашел такой вещи для тире, поэтому приведенные выше ответы могут быть полезны.)
Примечание: это находит наименьший неиспользуемый дескриптор файлa > 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
Ответ 2
Если это в Linux, вы всегда можете прочитать каталог /proc/self/fd/
, чтобы узнать используемые дескрипторы файлов.
Ответ 3
Я пересмотрел свой первоначальный ответ и теперь имею однострочное решение для оригинального сообщения.
Следующая функция могла бы работать в глобальном файле или источника script (например, ~/.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 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 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
Ответ 4
В Basile Starynkevitch ответ на этот вопрос, 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" выше. Если кто-то знает, как это сделать, сделайте шаг вперед! Вместо этого мне пришлось разделить задачу на два шага: один шаг, чтобы найти неиспользуемый дескриптор файла и один шаг, чтобы открыть файл на нем. Также обратите внимание, что, чтобы иметь возможность поместить шаг поиска в функцию ( "lower_unused_fd" ) и присвоить ее stdout для 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<&-"
Ответ 5
Мне нужно было поддерживать как 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
}
Например, для дублирования stdout вы можете:
newfd=$(find_unused_fd)
eval "exec $newfd>&1"