Quand utiliseriez-vous un descripteur de fichier supplémentaire?

74

Je sais que vous pouvez créer un descripteur de fichier et rediriger la sortie vers celui-ci. par exemple

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

Mais vous pouvez faire la même chose sans le descripteur de fichier:

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

Je cherche un bon exemple de la nécessité d'utiliser un descripteur de fichier supplémentaire.

dogbane
la source

Réponses:

50

La plupart des commandes ont un seul canal d’entrée (entrée standard, descripteur de fichier 0) et un seul canal de sortie (sortie standard, descripteur de fichier 1) ou bien agissent sur plusieurs fichiers qu’elles s’ouvrent seules (vous leur transmettez donc un nom de fichier). (Cela vient en plus de l'erreur standard (fd 2), qui filtre généralement jusqu'à l'utilisateur.) Il est cependant parfois pratique de disposer d'une commande qui agit comme un filtre à partir de plusieurs sources ou de plusieurs cibles. Par exemple, voici un script simple qui sépare les lignes impaires d'un fichier de celles paires.

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

Supposons maintenant que vous souhaitiez appliquer un filtre différent aux lignes de numéro impair et aux lignes de numéro pair (mais ne les remettez pas ensemble, ce serait un problème différent, non réalisable du shell en général). Dans le shell, vous pouvez uniquement diriger la sortie standard d'une commande vers une autre commande. Pour diriger un autre descripteur de fichier, vous devez d’abord le rediriger vers fd 1.

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

Un autre cas d'utilisation plus simple consiste à filtrer le résultat d'erreur d'une commande .

exec M>&Nredirige un descripteur de fichier vers un autre pour le reste du script (ou jusqu'à ce qu'une autre commande modifie à nouveau les descripteurs de fichier). Certaines fonctionnalités se chevauchent entre exec M>&Net somecommand M>&N. La execforme est plus puissante en ce sens qu'elle n'a pas besoin d'être imbriquée:

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

Autres exemples pouvant présenter un intérêt:

Et pour encore plus d'exemples:

PS C'est une question surprenante de l'auteur de l'article le plus voté sur le site, qui utilise la redirection via fd 3 !

Gilles, arrête de faire le mal
la source
Je dirais plutôt que "la plupart des commandes ont un canal de sortie simple ou double - stdout (fd 1) et très souvent stderr (fd 2)".
rozcietrzewiacz
Aussi, pourriez-vous au fait expliquer pourquoi vous utilisez while IFS= read -r line;? À mon avis, IFS n’a aucun effet ici puisque vous n’attribuez une valeur à une seule variable ( ligne ). Voir cette question.
rozcietrzewiacz
@rozcietrzewiacz J'ai parlé de stderr et je vois dans la première partie de ma réponse pourquoi cela IFSfait une différence même si vous lisez une variable unique (c'est pour conserver les principaux espaces).
Gilles, arrête de faire le mal
Ne pourriez-vous pas faire la même chose avec sed -ne 'w odd.txt' -e 'n;w even.txt'?
Wildcard
1
@Wildcard Vous pouvez faire la même chose avec d'autres outils, bien sûr. Mais le but de cette réponse était d’illustrer les redirections dans le shell.
Gilles, arrête d'être méchant
13

Voici un exemple d'utilisation de FD supplémentaires comme contrôle de conversation en 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"
Fordi
la source
8

Dans le contexte des canaux nommés (fifos), l'utilisation d'un descripteur de fichier supplémentaire peut permettre un comportement de canalisation non bloquant.

(
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

Voir: Pipe nommée se fermant prématurément dans le script?

chad
la source
1
vous avez déterré une de mes vieilles questions :) chad a raison, vous allez vous retrouver dans une situation de compétition.
n0pe
6

Un descripteur de fichier supplémentaire est utile lorsque vous souhaitez capturer la sortie standard dans une variable tout en souhaitant écrire à l'écran, par exemple dans une interface utilisateur de 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 
}
Adam Michael Danischewski
la source
2
Je sais que ce n’est pas une réponse nouvelle, mais j’ai dû regarder cela assez longtemps pour voir ce que cela faisait et pensais que ce serait utile si quelqu'un ajoutait un exemple d'utilisation de cette fonction. une commande - df, dans ce cas. dl.dropboxusercontent.com/u/54584985/mytest_redirect
Joe
1

Exemple: utilisation de flock pour forcer les scripts à s'exécuter en série avec des verrous de fichiers

Un exemple consiste à utiliser le verrouillage de fichier pour forcer les scripts à s'exécuter en série sur l'ensemble du système. Ceci est utile si vous ne voulez pas que deux scripts du même type agissent sur les mêmes fichiers. Sinon, les deux scripts pourraient interférer et éventuellement altérer des données.

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

Utilisez fonctionnellement flock en définissant verrouiller et déverrouiller

Vous pouvez également intégrer cette logique de verrouillage / déverrouillage à des fonctions réutilisables. Le trapshell suivant libère automatiquement le verrou de fichier lorsque le script se termine (erreur ou succès). trapaide à nettoyer vos verrous de fichiers. Le chemin /tmp/file.lockdoit être un chemin codé en dur afin que plusieurs scripts puissent essayer de le verrouiller.

# 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 unlocklogique ci-dessus consiste à supprimer le fichier avant que le verrou ne soit libéré. De cette façon, il nettoie le fichier de verrouillage. Comme le fichier a été supprimé, une autre instance de ce programme peut obtenir le verrou de fichier.

Utilisation des fonctions de verrouillage et de déverrouillage dans les scripts

Vous pouvez l'utiliser dans vos scripts comme dans l'exemple suivant.

#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

Si vous voulez que votre code attende jusqu'à ce qu'il soit capable de se verrouiller, vous pouvez ajuster le script comme suit:

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
la source
0

À titre d'exemple concret, je viens d'écrire un script qui nécessite les informations de minutage d'une sous-commande. L'utilisation d'un descripteur de fichier supplémentaire m'a permis de capturer le timestderr de la commande sans interrompre le stdout ou le stderr de la sous-commande.

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

Cela permet de faire pointer lsstderr to fd 3, fd 3 vers stderr du script et timestderr vers un fichier. Lorsque le script est exécuté, ses propriétés stdout et stderr sont identiques à celles de la sous-commande, qui peuvent être redirigées normalement. Seule timela sortie est redirigée vers le fichier.

$ 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
la source