Kiedy użyjesz dodatkowego deskryptora pliku?

74

Wiem, że możesz utworzyć deskryptor pliku i przekierować do niego dane wyjściowe. na przykład

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

Ale możesz zrobić to samo bez deskryptora pliku:

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

Szukam dobrego przykładu, kiedy musiałbyś użyć dodatkowego deskryptora pliku.

dogbane
źródło

Odpowiedzi:

50

Większość poleceń ma jeden kanał wejściowy (standardowe wejście, deskryptor pliku 0) i jeden kanał wyjściowy (standardowe wyjście, deskryptor pliku 1) lub działają na kilku plikach, które same otwierają (więc podajesz im nazwę pliku). (Jest to dodatek do standardowego błędu (fd 2), który zwykle filtruje całą drogę do użytkownika.) Czasami jednak wygodne jest posiadanie polecenia, które działa jak filtr z kilku źródeł lub z kilku celów. Na przykład, oto prosty skrypt, który oddziela nieparzyste linie w pliku od parzystych

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

Załóżmy teraz, że chcesz zastosować inny filtr do linii o liczbach nieparzystych i do linii o parzystych numerach (ale nie odkładaj ich razem, to byłby inny problem, niewykonalny w stosunku do powłoki). W powłoce można tylko potokować standardowe wyjście polecenia do innego polecenia; aby potokować inny deskryptor pliku, musisz najpierw przekierować go do fd 1.

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

Innym, prostszym przypadkiem użycia jest filtrowanie wyniku błędu polecenia .

exec M>&Nprzekierowuje deskryptor pliku na inny na pozostałą część skryptu (lub do momentu, gdy inne takie polecenie ponownie nie zmieni deskryptorów plików). Funkcjonalność między exec M>&Ni występuje w pewnym stopniu somecommand M>&N. execForma jest bardziej wydajny w tym, że nie musi być zagnieżdżone:

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

Inne przykłady, które mogą być interesujące:

I jeszcze więcej przykładów:

PS To zaskakujące pytanie autora najpopularniejszego postu na stronie, który korzysta z przekierowania przez fd 3 !

Gilles
źródło
Wolę powiedzieć, że „większość poleceń ma jeden lub dwa kanały wyjściowe - stdout (fd 1) i bardzo często stderr (fd 2)”.
rozcietrzewiacz
Czy możesz przy okazji wyjaśnić, dlaczego używasz while IFS= read -r line;? Z mojego punktu widzenia IFS nie ma tu żadnego wpływu, ponieważ przypisujesz wartość tylko jednej zmiennej ( linii ). Zobacz to pytanie.
rozcietrzewiacz
@rozcietrzewiacz Wspomniałem o stderr i zapoznaj się z pierwszą częścią mojej odpowiedzi, dlaczego IFSrobi różnicę, nawet jeśli czytasz jedną zmienną (aby zachować wiodące białe znaki).
Gilles
Nie mógłbyś zrobić tego samego sed -ne 'w odd.txt' -e 'n;w even.txt'?
Wildcard
1
@Wildcard Na pewno możesz zrobić to samo z innymi narzędziami. Ale celem tej odpowiedzi było zilustrowanie przekierowań w powłoce.
Gilles
13

Oto przykład użycia dodatkowych FD jako kontroli chattiness skryptu 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
źródło
8

W kontekście nazwanych potoków (fifos) użycie dodatkowego deskryptora pliku może umożliwić zachowanie nieblokującego potoku.

(
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

Zobacz: Nazwane zamknięcie potoku w skrypcie?

Czad
źródło
1
wykopałeś jedno z moich starych pytań :) Czad ma rację, wpadniesz w stan wyścigu.
n0pe
6

Dodatkowy deskryptor pliku jest przydatny, gdy chcesz złapać standardowe wyjście w zmiennej, ale nadal chcesz zapisać na ekranie, na przykład w interfejsie użytkownika skryptu 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 
}
Adam Michael Danischewski
źródło
2
Wiem, że to nie jest nowa odpowiedź, ale musiałem przez chwilę na to patrzeć, aby zobaczyć, co robi, i pomyślałem, że byłoby pomocne, gdyby ktoś dodał przykład użycia tej funkcji. Ta echo i przechwytuje całą moc polecenie - df, w tym przypadku. dl.dropboxusercontent.com/u/54584985/mytest_redirect
Joe
1

Przykład: użycie flocka do wymuszenia szeregowego działania skryptów z blokadami plików

Jednym z przykładów jest użycie blokady plików, aby zmusić skrypty do szeregowego uruchamiania w całym systemie. Jest to przydatne, jeśli nie chcesz, aby dwa skrypty tego samego rodzaju działały na tych samych plikach. W przeciwnym razie oba skrypty zakłócałyby się wzajemnie i powodowałyby uszkodzenie danych.

#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>&-

Użyj stada funkcjonalnie, definiując blokadę i odblokowanie

Możesz także zawinąć tę logikę blokowania / odblokowywania w funkcje wielokrotnego użytku. Następująca trapwbudowana powłoka automatycznie zwolni blokadę pliku, gdy skrypt zakończy działanie (błąd lub sukces). trappomaga wyczyścić blokady plików. Ścieżka /tmp/file.lockpowinna być ścieżką zakodowaną na stałe, aby wiele skryptów mogło próbować się na niej zablokować.

# 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>&-
}

Powyższa unlocklogika polega na usunięciu pliku przed zwolnieniem blokady. W ten sposób czyści plik blokady. Ponieważ plik został usunięty, inna instancja tego programu może uzyskać blokadę pliku.

Wykorzystanie funkcji blokowania i odblokowywania w skryptach

Możesz użyć go w swoich skryptach, jak w poniższym przykładzie.

#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

Jeśli chcesz, aby Twój kod czekał, aż będzie mógł się zablokować, możesz dostosować skrypt w następujący sposób:

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
Sam Gleske
źródło
0

Jako konkretny przykład właśnie napisałem skrypt, który potrzebuje informacji o taktowaniu z podkomendy. Użycie dodatkowego deskryptora pliku pozwoliło mi przechwycić timestderr polecenia bez przerywania stdout lub stderr podkomendy.

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

To robi to punkt lsstderr do fd 3, punkt fd 3 do stderr skryptu i punkt timestderr do pliku. Po uruchomieniu skryptu jego stdout i stderr są takie same jak polecenia podkomendy, które można przekierować jak zwykle. Tylko timedane wyjściowe są przekierowywane do pliku.

$ 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
Ben Blank
źródło