Коли ви використовуєте додатковий дескриптор файлу?


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"

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

Дивіться: Названий Труба передчасно закривається в сценарії?


1
Ви викопали одне з моїх старих запитань :) Чад правильний, ви зіткнетеся з умовою гонки.
n0pe

6

Додатковий дескриптор файлів хороший для того, коли ви хочете спіймати stdout у змінній, але все ще хочете виписати на екран, наприклад, у користувальницькому інтерфейсі bash script

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
Джо


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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.