web-dev-qa-db-fra.com

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

Je sais que vous pouvez créer un descripteur de fichier et y rediriger la sortie. 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 cas où vous auriez à utiliser un descripteur de fichier supplémentaire.

80
dogbane

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 opèrent sur plusieurs fichiers qu'elles ouvrent par elles-mêmes (vous leur passez donc un nom de fichier). (Cela s'ajoute à l'erreur standard (fd 2), qui filtre généralement jusqu'à l'utilisateur.) Il est cependant parfois pratique d'avoir 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 vouliez appliquer un filtre différent aux lignes impaires et aux lignes paires (mais ne pas les remonter, ce serait un problème différent, impossible à réaliser à partir 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 est filtrage de la sortie d'erreur d'une commande .

exec M>&N redirige un descripteur de fichier vers un autre pour le reste du script (ou jusqu'à ce qu'une autre de ces commandes modifie à nouveau les descripteurs de fichier). Il existe un certain chevauchement des fonctionnalités entre exec M>&N et somecommand M>&N. La forme exec est plus puissante en ce 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 être intéressants:

Et pour encore plus d'exemples:

P.S. C'est une question surprenante venant de l'auteur du le post le plus voté sur le site qui utilise la redirection via fd !

Voici un exemple d'utilisation de FD supplémentaires comme contrôle de discussion de 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"
14
Fordi

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: Named Pipe fermant prématurément dans le script?

8
chad

Un descripteur de fichier supplémentaire est utile lorsque vous souhaitez intercepter la sortie standard dans une variable tout en souhaitant l'é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 
}

Voici encore un autre scénario lorsque l'utilisation d'un descripteur de fichier supplémentaire semble appropriée (dans Bash):

Sécurité du mot de passe du script shell des paramètres de ligne de commande

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
3
bernard

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

Un exemple est d'utiliser le verrouillage de fichiers pour forcer les scripts à s'exécuter en série à l'échelle du système. Ceci est utile si vous ne souhaitez pas que deux scripts du même type fonctionnent sur les mêmes fichiers. Sinon, les deux scripts interféreraient entre eux et pourraient corrompre les 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>&-

Utiliser le troupeau de manière fonctionnelle en définissant le verrouillage et le déverrouillage

Vous pouvez également envelopper cette logique de verrouillage/déverrouillage dans des fonctions réutilisables. Ce qui suit trap intégré au shell libérera automatiquement le verrouillage du fichier à la fin du script (erreur ou succès). trap aide à nettoyer vos verrous de fichiers. Le chemin /tmp/file.lock doit être un chemin codé en dur pour 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 logique unlock ci-dessus consiste à supprimer le fichier avant la libération du verrou. De cette façon, il nettoie le fichier de verrouillage. Étant donné que le fichier a été supprimé, une autre instance de ce programme est en mesure d'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 attend qu'il soit capable de se verrouiller, vous pouvez ajuster le script comme:

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
1
Sam Gleske

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

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

Cela fait pointer le stderr de ls vers fd 3, pointer fd 3 vers le stderr du script et pointer le stderr de time vers un fichier. Lorsque le script est exécuté, sa stdout et sa stderr sont les mêmes que celles de la sous-commande, qui peuvent être redirigées comme d'habitude. Seule la sortie de time 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
1
Ben Blank