Wann würden Sie einen zusätzlichen Dateideskriptor verwenden?

74

Ich weiß, dass Sie einen Dateideskriptor erstellen und die Ausgabe darauf umleiten können. z.B

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

Aber Sie können dasselbe auch ohne den Dateideskriptor tun:

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

Ich suche ein gutes Beispiel dafür, wann Sie einen zusätzlichen Dateideskriptor verwenden müssten.

Dogbane
quelle

Antworten:

50

Die meisten Befehle haben einen einzelnen Eingabekanal (Standardeingabe, Dateideskriptor 0) und einen einzelnen Ausgabekanal (Standardausgabe, Dateideskriptor 1) oder bearbeiten mehrere Dateien, die sie selbst öffnen (Sie übergeben ihnen einen Dateinamen). (Dies ergibt sich zusätzlich aus dem Standardfehler (fd 2), der normalerweise bis zum Benutzer gefiltert wird.) Manchmal ist es jedoch zweckmäßig, einen Befehl zu haben, der als Filter für mehrere Quellen oder für mehrere Ziele fungiert. Hier ist beispielsweise ein einfaches Skript, das die ungeradzahligen Zeilen in einer Datei von den geradzahligen trennt

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

Angenommen, Sie möchten einen anderen Filter auf die ungeradzahligen und die geradzahligen Zeilen anwenden (aber nicht wieder zusammensetzen, das wäre ein anderes Problem, das von der Shell im Allgemeinen nicht realisierbar wäre). In der Shell können Sie die Standardausgabe eines Befehls nur an einen anderen Befehl weiterleiten. Um einen anderen Dateideskriptor weiterzuleiten, müssen Sie ihn zuerst auf fd 1 umleiten.

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

Ein anderer, einfacherer Anwendungsfall ist das Filtern der Fehlerausgabe eines Befehls .

exec M>&NLeitet einen Dateideskriptor für den Rest des Skripts auf einen anderen um (oder bis ein anderer solcher Befehl die Dateideskriptoren erneut ändert). Es gibt einige Überlappungen in der Funktionalität zwischen exec M>&Nund somecommand M>&N. Das execFormular ist insofern mächtiger, als es nicht verschachtelt werden muss:

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

Weitere interessante Beispiele:

Und noch mehr Beispiele:

PS Dies ist eine überraschende Frage des Autors des am häufigsten bewerteten Beitrags auf der Website, der die Umleitung durch fd 3 verwendet !

Gilles
quelle
Ich würde eher sagen, dass "die meisten Befehle entweder einen einzelnen oder einen doppelten Ausgangskanal haben - stdout (fd 1) und sehr oft stderr (fd 2)".
Rozcietrzewiacz
Können Sie mir übrigens auch erklären, warum Sie verwenden while IFS= read -r line;? So wie ich es sehe, hat IFS hier keine Auswirkung, da Sie nur einer Variablen ( Zeile ) einen Wert zuweisen . Siehe diese Frage.
Rozcietrzewiacz
@rozcietrzewiacz Ich habe stderr erwähnt und im ersten Teil meiner Antwort nachgeschlagen, warum dies IFSeinen Unterschied macht, auch wenn Sie eine einzelne Variable lesen (um das führende Leerzeichen beizubehalten).
Gilles
Könntest du nicht dasselbe mit machen sed -ne 'w odd.txt' -e 'n;w even.txt'?
Wildcard
1
@Wildcard Das Gleiche können Sie natürlich auch mit anderen Tools tun. Ziel dieser Antwort war es jedoch, Umleitungen in der Shell zu veranschaulichen.
Gilles
13

Hier ist ein Beispiel für die Verwendung zusätzlicher FDs als Chat-Kontrolle für Bash-Skripte:

#!/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
quelle
8

Im Kontext von Named Pipes (FIFOS) kann die Verwendung eines zusätzlichen Dateideskriptors ein blockierungsfreies Piping-Verhalten ermöglichen.

(
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

Siehe: Named Pipe wird im Skript vorzeitig geschlossen?

Tschad
quelle
1
du hast eine meiner alten Fragen ausgegraben :) chad hat recht, du wirst in einen rennzustand geraten.
Nr.
6

Ein zusätzlicher Dateideskriptor ist hilfreich, wenn Sie die Standardausgabe in einer Variablen abfangen und dennoch auf den Bildschirm schreiben möchten, beispielsweise in einer Bash-Skript-Benutzeroberfläche

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
quelle
2
Ich weiß, dass dies keine neue Antwort ist, aber ich musste lange darüber nachdenken, um zu sehen, was es tut, und dachte, es wäre hilfreich, wenn jemand ein Beispiel für die Verwendung dieser Funktion hinzufügt ein Befehl - df, in diesem Fall. dl.dropboxusercontent.com/u/54584985/mytest_redirect
Joe
3

Hier ist noch ein anderes Szenario, wenn die Verwendung eines zusätzlichen Dateideskriptors angemessen erscheint (in Bash):

Shell-Skript-Kennwortsicherheit für Befehlszeilenparameter

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
bernard
quelle
1

Beispiel: Verwenden von Flock, um die serielle Ausführung von Skripten mit Dateisperren zu erzwingen

Ein Beispiel ist die Verwendung der Dateisperrung, um zu erzwingen, dass Skripts systemweit seriell ausgeführt werden. Dies ist nützlich, wenn Sie nicht möchten, dass zwei Skripte derselben Art mit denselben Dateien arbeiten. Andernfalls würden sich die beiden Skripte gegenseitig stören und möglicherweise Daten beschädigen.

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

Verwenden Sie Flock funktional, indem Sie Sperren und Entsperren definieren

Sie können diese Sperr- / Entsperrlogik auch in wiederverwendbare Funktionen einbinden. Die folgende integrierte trapShell hebt die Dateisperre automatisch auf, wenn das Skript beendet wird (entweder Fehler oder Erfolg). trapBereinigen Sie Ihre Dateisperren. Der Pfad /tmp/file.locksollte fest codiert sein, damit mehrere Skripte versuchen können, ihn zu sperren.

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

Die unlockobige Logik besteht darin, die Datei zu löschen, bevor die Sperre aufgehoben wird. Auf diese Weise wird die Sperrdatei bereinigt. Da die Datei gelöscht wurde, kann eine andere Instanz dieses Programms die Dateisperre erhalten.

Verwendung von Sperr- und Entsperrfunktionen in Skripten

Sie können es in Ihren Skripten wie im folgenden Beispiel verwenden.

#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

Wenn Sie möchten, dass Ihr Code wartet, bis er gesperrt werden kann, können Sie das Skript wie folgt anpassen:

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
quelle
0

Als konkretes Beispiel habe ich gerade ein Skript geschrieben, das die Timing-Informationen eines Unterbefehls benötigt. Durch die Verwendung eines zusätzlichen Dateideskriptors konnte ich den timeBefehl stderr erfassen, ohne den Unterbefehl stdout oder stderr zu unterbrechen.

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

Dies bewirkt, dass der Punkt ls"stderr" auf "fd 3", der Punkt "fd 3" auf "stderr" des Skripts und der Punkt time"stderr" auf eine Datei verweist. Wenn das Skript ausgeführt wird, stimmen stdout und stderr mit den Unterbefehlen überein, die wie gewohnt umgeleitet werden können. Nur timedie Ausgabe von wird in die Datei umgeleitet.

$ 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
quelle