Quando useresti un descrittore di file aggiuntivo?


74

So che puoi creare un descrittore di file e reindirizzare l'output su di esso. per esempio

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

Ma puoi fare la stessa cosa senza il descrittore di file:

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

Sto cercando un buon esempio di quando dovresti usare un descrittore di file aggiuntivo.

Risposte:


50

La maggior parte dei comandi ha un singolo canale di input (input standard, descrittore di file 0) e un singolo canale di output (output standard, descrittore di file 1) oppure operano su più file che si aprono da soli (quindi si passa loro un nome di file). (Questo è in aggiunta all'errore standard (fd 2), che di solito filtra fino all'utente). Tuttavia, a volte è conveniente avere un comando che agisce come filtro da più fonti o da più target. Ad esempio, ecco un semplice script che separa le righe dispari in un file da quelle pari

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

Supponiamo ora di voler applicare un filtro diverso alle linee dei numeri dispari e alle linee dei numeri pari (ma non rimetterli insieme, sarebbe un problema diverso, non realizzabile dalla shell in generale). Nella shell, è possibile reindirizzare l'output standard di un comando solo a un altro comando; per reindirizzare un altro descrittore di file, devi prima reindirizzarlo a fd 1.

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

Un altro caso d'uso più semplice è filtrare l'output dell'errore di un comando .

exec M>&Nreindirizza un descrittore di file a un altro per il resto dello script (o fino a quando un altro comando di questo tipo non modifica nuovamente i descrittori di file). C'è una sovrapposizione di funzionalità tra exec M>&Ne somecommand M>&N. Il execmodulo è più potente in quanto non deve essere nidificato:

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

Altri esempi che potrebbero essere di interesse:

E per ancora più esempi:

PS Questa è una domanda sorprendente proveniente dall'autore del post più votato sul sito che utilizza il reindirizzamento tramite fd 3 !


Preferirei dire che "la maggior parte dei comandi ha un canale di uscita singolo o doppio - stdout (fd 1) e molto spesso stderr (fd 2)".
rozcietrzewiacz,

Inoltre, potresti spiegare perché usi while IFS= read -r line;? Per come la vedo io, IFS non ha alcun effetto qui poiché assegni il valore a una sola variabile ( linea ). Vedi questa domanda
rozcietrzewiacz,

@rozcietrzewiacz Ho menzionato stderr e vedo la prima parte della mia risposta sul perché IFSfa la differenza anche se stai leggendo una in una singola variabile (è per mantenere lo spazio bianco principale).
Gilles 'SO- smetti di essere malvagio' il

Non potresti fare lo stesso con sed -ne 'w odd.txt' -e 'n;w even.txt'?
Wildcard il

1
@Wildcard Potresti fare lo stesso con altri strumenti, certo. Ma l'obiettivo di questa risposta era illustrare i reindirizzamenti nella shell.
Gilles 'SO- smetti di essere malvagio' il

13

Ecco un esempio dell'utilizzo di FD extra come controllo della chattiness degli script 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

Nel contesto di named pipe (fifos) l'uso di un descrittore di file aggiuntivo può abilitare un comportamento di piping non bloccante.

(
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

Vedi: Named Pipe che si chiude prematuramente nello script?


1
hai scavato una delle mie vecchie domande :) Ciad ha ragione, ti imbatterai in una condizione di gara.
n0pe

6

Un descrittore di file aggiuntivo è utile quando si desidera catturare lo stdout in una variabile ma si desidera comunque scrivere sullo schermo, ad esempio nell'interfaccia utente di uno script 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
So che questa non è una nuova risposta, ma ho dovuto fissarlo un po 'per vedere cosa fa e ho pensato che sarebbe stato utile se qualcuno avesse aggiunto un esempio di questa funzione in uso. Questa echo e cattura l'intero output di un comando - df, in questo caso. dl.dropboxusercontent.com/u/54584985/mytest_redirect
Joe


1

Esempio: utilizzo di flock per forzare l'esecuzione in serie degli script con blocchi dei file

Un esempio è quello di utilizzare il blocco dei file per forzare l'esecuzione degli script a livello di sistema in serie. Ciò è utile se non si desidera che due script dello stesso tipo operino sugli stessi file. Altrimenti, i due script interferirebbero tra loro e probabilmente con i dati corrotti.

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

Utilizzare il gregge in modo funzionale definendo blocco e sblocco

Puoi anche avvolgere questa logica di blocco / sblocco in funzioni riutilizzabili. Il seguente trapbuilt-in della shell rilascerà automaticamente il blocco del file alla chiusura dello script (errore o esito positivo). trapaiuta a ripulire i blocchi dei file. Il percorso /tmp/file.lockdovrebbe essere un percorso codificato in modo tale che più script possano provare a bloccarlo.

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

La unlocklogica sopra è eliminare il file prima che il blocco venga rilasciato. In questo modo pulisce il file di blocco. Poiché il file è stato eliminato, un'altra istanza di questo programma è in grado di ottenere il blocco dei file.

Utilizzo delle funzioni di blocco e sblocco negli script

Puoi usarlo nei tuoi script come nell'esempio seguente.

#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

Se vuoi che il tuo codice aspetti fino a quando non è in grado di bloccare, puoi regolare lo script come:

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

Come esempio concreto, ho appena scritto uno script che necessita delle informazioni di temporizzazione da un sottocomando. L'uso di un descrittore di file aggiuntivo mi ha permesso di catturare lo timestderr del comando senza interrompere lo stdout o lo stderr del sottocomando.

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

Quello che fa è puntare lo lsstderr su fd 3, puntare fd 3 sullo stderr dello script e puntare lo timestderr su un file. Quando viene eseguito lo script, i suoi stdout e stderr sono gli stessi dei sottocomandi, che possono essere reindirizzati come al solito. Solo time's output viene reindirizzato al file.

$ 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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.