Когда бы вы использовали дополнительный файловый дескриптор?

74

Я знаю, что вы можете создать дескриптор файла и перенаправить вывод на него. например

exec 3<> /tmp/foo # open fd 3.
echo a >&3 # write to it
exec 3>&- # close fd 3.

Но вы можете сделать то же самое без дескриптора файла:

FILE=/tmp/foo
echo a > "$FILE"

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

кендырь
источник

Ответы:

50

Большинство команд имеют один входной канал (стандартный ввод, дескриптор файла 0) и один выходной канал (стандартный вывод, дескриптор файла 1) или работают с несколькими файлами, которые они открывают сами (поэтому вы передаете им имя файла). (Это в дополнение к стандартной ошибке (fd 2), которая обычно фильтруется вплоть до пользователя.) Однако иногда удобно иметь команду, которая действует как фильтр от нескольких источников или нескольких целей. Например, вот простой скрипт, который отделяет нечетные строки в файле от четных

while IFS= read -r line; do
  printf '%s\n' "$line"
  if IFS= read -r line; then printf '%s\n' "$line" >&3; fi
done >odd.txt 3>even.txt

Теперь предположим, что вы хотите применить другой фильтр к строкам с нечетными номерами и к строкам с четными номерами (но не собирать их вместе, это было бы другой проблемой, неосуществимой в общем случае в оболочке). В оболочке вы можете передать стандартный вывод команды только другой команде; чтобы передать другой дескриптор файла, вам нужно сначала перенаправить его на fd 1.

{ while  done | odd-filter >filtered-odd.txt; } 3>&1 | even-filter >filtered-even.txt

Другой, более простой вариант использования - фильтрация вывода ошибок команды .

exec M>&Nперенаправляет файловый дескриптор на другой для оставшейся части сценария (или до тех пор, пока другая такая команда снова не изменит файловые дескрипторы). Существует некоторое совпадение в функциональности между exec M>&Nи somecommand M>&N. execФорма является более мощным , что он не должен быть вложенными:

exec 8<&0 9>&1
exec >output12
command1
exec <input23
command2
exec >&9
command3
exec <&8

Другие примеры, которые могут представлять интерес:

И еще больше примеров:

PS Это удивительный вопрос от автора самого популярного поста на сайте, который использует перенаправление через fd 3 !

Жиль "ТАК - перестань быть злым"
источник
Я бы скорее сказал, что «большинство команд имеют один или два выходных канала - stdout (fd 1) и очень часто stderr (fd 2)».
rozcietrzewiacz
Кроме того, не могли бы вы, кстати, объяснить, почему вы используете while IFS= read -r line;? На мой взгляд, IFS здесь не действует, так как вы присваиваете значение только одной переменной ( строке ). Смотрите этот вопрос.
rozcietrzewiacz
@rozcietrzewiacz Я упомянул о stderr и вижу первую часть моего ответа о том, почему IFSимеет значение, даже если вы читаете в одну переменную (это для сохранения лидирующего пробела).
Жиль "ТАК - перестань быть злым"
Не могли бы вы сделать то же самое с sed -ne 'w odd.txt' -e 'n;w even.txt'?
Wildcard
1
@Wildcard Вы можете сделать то же самое с другими инструментами, конечно. Но целью этого ответа было проиллюстрировать перенаправления в оболочке.
Жиль "ТАК - перестань быть злым"
13

Вот пример использования дополнительных FD в качестве контроля чата bash-скрипта:

#!/bin/bash

log() {
    echo $* >&3
}
info() {
    echo $* >&4
}
err() {
    echo $* >&2
}
debug() {
    echo $* >&5
}

VERBOSE=1

while [[ $# -gt 0 ]]; do
    ARG=$1
    shift
    case $ARG in
        "-vv")
            VERBOSE=3
        ;;
        "-v")
            VERBOSE=2
        ;;
        "-q")
            VERBOSE=0
        ;;
        # More flags
        *)
        echo -n
        # Linear args
        ;;
    esac
done

for i in 1 2 3; do
    fd=$(expr 2 + $i)
    if [[ $VERBOSE -ge $i ]]; then
        eval "exec $fd>&1"
    else
        eval "exec $fd> /dev/null"
    fi
done

err "This will _always_ show up."
log "This is normally displayed, but can be prevented with -q"
info "This will only show up if -v is passed"
debug "This will show up for -vv"
Fordi
источник
8

В контексте именованных каналов (fifos) использование дополнительного файлового дескриптора может включить неблокирующее поведение конвейера.

(
rm -f fifo
mkfifo fifo
exec 3<fifo   # open fifo for reading
trap "exit" 1 2 3 15
exec cat fifo | nl
) &
bpid=$!

(
exec 3>fifo  # open fifo for writing
trap "exit" 1 2 3 15
while true;
do
    echo "blah" > fifo
done
)
#kill -TERM $bpid

Смотрите: Named Pipe преждевременно закрывается в скрипте?

Чад
источник
1
ты откопал один из моих старых вопросов :) Чад прав, ты попадешь в состояние гонки.
n0pe
6

Дополнительный файловый дескриптор подходит для случаев, когда вы хотите перехватить стандартный вывод в переменной, но все же хотите вывести его на экран, например, в пользовательском интерфейсе bash-скрипта.

arg1 string to echo 
arg2 flag 0,1 print or not print to 3rd fd stdout descriptor   
function ecko3 {  
if [ "$2" -eq 1 ]; then 
    exec 3>$(tty) 
    echo -en "$1" | tee >(cat - >&3)
    exec 3>&- 
else 
    echo -en "$1"  
fi 
}
Адам Майкл Данищевский
источник
2
Я знаю, что это не новый ответ, но мне пришлось некоторое время смотреть на это, чтобы увидеть, что он делает, и подумал, что было бы полезно, если бы кто-то добавил пример использования этой функции. Это одно эхо и захватывает весь вывод команда - df, в этом случае. dl.dropboxusercontent.com/u/54584985/mytest_redirect
Джо
3

Вот еще один сценарий, когда использование дополнительного файлового дескриптора кажется подходящим (в Bash):

Защита паролем сценария командной строки параметров командной строки

env -i bash --norc   # clean up environment
set +o history
read -s -p "Enter your password: " passwd
exec 3<<<"$passwd"
mycommand <&3  # cat /dev/stdin in mycommand
Бернард
источник
1

Пример: использование flock для принудительного запуска сценариев с блокировками файлов

Одним из примеров является использование блокировки файлов для принудительного запуска сценариев по всей системе. Это полезно, если вы не хотите, чтобы два скрипта одного типа работали с одинаковыми файлами. В противном случае два сценария будут мешать друг другу и, возможно, повреждать данные.

#exit if any command returns a non-zero exit code (like flock when it fails to lock)
set -e

#open file descriptor 3 for writing
exec 3> /tmp/file.lock

#create an exclusive lock on the file using file descriptor 3
#exit if lock could not be obtained
flock -n 3

#execute serial code

#remove the file while the lock is still obtained
rm -f /tmp/file.lock

#close the open file handle which releases the file lock and disk space
exec 3>&-

Функционально использовать стадо, определив блокировку и разблокировку

Вы также можете обернуть эту логику блокировки / разблокировки в многократно используемые функции. Следующая trapвстроенная оболочка автоматически снимет блокировку файла при выходе из скрипта (либо ошибка, либо успех). trapпомогает очистить файл блокировки. Путь /tmp/file.lockдолжен быть жестко закодированным, чтобы несколько скриптов могли попытаться заблокировать его.

# obtain a file lock and automatically unlock it when the script exits
function lock() {
  exec 3> /tmp/file.lock
  flock -n 3 && trap unlock EXIT
}

# release the file lock so another program can obtain the lock
function unlock() {
  # only delete if the file descriptor 3 is open
  if { >&3 ; } &> /dev/null; then
    rm -f /tmp/file.lock
  fi
  #close the file handle which releases the file lock
  exec 3>&-
}

unlockЛогика выше , чтобы удалить файл до снятия блокировки. Таким образом, он очищает файл блокировки. Поскольку файл был удален, другой экземпляр этой программы может получить блокировку файла.

Использование функций блокировки и разблокировки в скриптах

Вы можете использовать его в своих скриптах, как в следующем примере.

#exit if any command returns a non-zero exit code (like flock when it fails to lock)
set -e

#try to lock (else exit because of non-zero exit code)
lock

#system-wide serial locked code

unlock

#non-serial code

Если вы хотите, чтобы ваш код ожидал блокировки, вы можете настроить скрипт следующим образом:

set -e

#wait for lock to be successfully obtained
while ! lock 2> /dev/null; do
  sleep .1
done

#system-wide serial locked code

unlock

#non-serial code
Сэм Глеске
источник
0

В качестве конкретного примера я только что написал сценарий, которому нужна информация о синхронизации из подкоманды. Использование дополнительного файлового дескриптора позволило мне захватить timestderr команды, не прерывая stdout или stderr подкоманды.

(time ls -9 2>&3) 3>&2 2> time.txt

Для этого нужно указать lsstderr на fd 3, указать fd 3 на stderr скрипта и указать timestderr на файл. Когда скрипт выполняется, его stdout и stderr совпадают с подкомандой, которую можно перенаправить как обычно. Только timeвыходные данные перенаправляются в файл.

$ echo '(time ls my-example-script.sh missing-file 2>&3) 3>&2 2> time.txt' > my-example-script.sh
$ chmod +x my-example-script.sh 
$ ./my-example-script.sh 
ls: missing-file: No such file or directory
my-example-script.sh
$ ./my-example-script.sh > /dev/null
ls: missing-file: No such file or directory
$ ./my-example-script.sh 2> /dev/null
my-example-script.sh
$ cat time.txt

real    0m0.002s
user    0m0.001s
sys 0m0.001s
Бен Бланк
источник