Utilisation pratique pour déplacer des descripteurs de fichiers

16

Selon la page de manuel de bash:

L'opérateur de redirection

   [n]<&digit-

déplace le descripteur digitde fichier vers le descripteur de fichier nou l'entrée standard (descripteur de fichier 0) s'il nn'est pas spécifié. digitest fermé après avoir été dupliqué n.

Que signifie "déplacer" un descripteur de fichier vers un autre? Quelles sont les situations typiques d'une telle pratique?

Quentin
la source

Réponses:

14

3>&4-est une extension ksh93 également prise en charge par bash et qui est l'abréviation de 3>&4 4>&-, c'est-à-dire 3 pointe maintenant vers où 4 était utilisé, et 4 est maintenant fermé, donc ce qui était indiqué par 4 est maintenant passé à 3.

Une utilisation typique serait dans les cas où vous en avez dupliqué stdinou stdoutpour en sauvegarder une copie et que vous souhaitez la restaurer, comme dans:

Supposons que vous souhaitiez capturer le stderr d'une commande (et stderr uniquement) tout en laissant stdout seul dans une variable.

Substitution de commande var=$(cmd), crée un tuyau. L'extrémité d'écriture du tube devient cmdla sortie standard de (descripteur de fichier 1) et l'autre extrémité est lue par le shell pour remplir la variable.

Maintenant, si vous voulez stderraller à la variable, vous pouvez faire: var=$(cmd 2>&1). Maintenant, les deux fd 1 (stdout) et 2 (stderr) vont au tuyau (et finalement à la variable), ce qui n'est que la moitié de ce que nous voulons.

Si nous le faisons var=$(cmd 2>&1-)(abréviation de var=$(cmd 2>&1 >&-), seul cmdstderr va maintenant dans le tuyau, mais fd 1 est fermé. Si cmdessaie d'écrire une sortie, cela retournerait avec une EBADFerreur, s'il ouvre un fichier, il obtiendra le premier fd libre et le fichier ouvert lui sera assigné à stdoutmoins que la commande ne s'y oppose! Pas ce que nous voulons non plus.

Si nous voulons que la cmdsortie standard soit laissée seule, c'est-à-dire qu'elle pointe vers la même ressource qu'elle a pointée en dehors de la substitution de commande, alors nous devons en quelque sorte mettre cette ressource à l'intérieur de la substitution de commande. Pour cela, nous pouvons faire une copie de l' stdout extérieur de la substitution de commandes pour la prendre à l'intérieur.

{
  var=$(cmd)
} 3>&1

C'est une façon plus propre d'écrire:

exec 3>&1
var=$(cmd)
exec 3>&-

(qui a également l'avantage de restaurer fd 3 au lieu de le fermer à la fin).

Puis sur le {(ou le exec 3>&1) et jusqu'au }, les deux fd 1 et 3 pointent vers la même ressource fd 1 pointée initialement. fd 3 pointera également vers cette ressource dans la substitution de commande (la substitution de commande redirige uniquement le fd 1, stdout). Donc ci-dessus, pour cmd, nous avons pour les fds 1, 2, 3:

  1. le tuyau à var
  2. intacte
  3. identique à ce que 1 pointe vers l'extérieur de la substitution de commande

Si nous le changeons en:

{
  var=$(cmd 2>&1 >&3)
} 3>&1-

Il devient alors:

  1. identique à ce que 1 pointe vers l'extérieur de la substitution de commande
  2. le tuyau à var
  3. identique à ce que 1 pointe vers l'extérieur de la substitution de commande

Maintenant, nous avons ce que nous voulions: stderr va dans le tuyau et stdout reste intact. Cependant, nous divulguons ce fd 3 à cmd.

Bien que les commandes (par convention) supposent que les fds 0 à 2 soient ouverts et soient des entrées, sorties et erreurs standard, elles ne supposent rien des autres fds. Très probablement, ils laisseront ce fd 3 intact. S'ils ont besoin d'un autre descripteur de fichier, ils n'en feront qu'un open()/dup()/socket()...qui retournera le premier descripteur de fichier disponible. Si (comme un script shell qui le fait exec 3>&1) ils ont besoin de l'utiliser fdspécifiquement, ils l'attribueront d'abord à quelque chose (et dans ce processus, la ressource détenue par notre fd 3 sera libérée par ce processus).

C'est une bonne pratique de fermer ce fd 3 car cmdil ne l'utilise pas, mais ce n'est pas grave si nous le laissons assigné avant d'appeler cmd. Les problèmes peuvent être: cela cmd(et potentiellement d'autres processus qu'il engendre) a un fd de moins à sa disposition. Un problème potentiellement plus grave est de savoir si la ressource vers laquelle pointe fd peut finir par être détenue par un processus généré par celui-ci cmden arrière-plan. Cela peut être un problème si cette ressource est un canal ou un autre canal de communication inter-processus (comme lorsque votre script est exécuté en tant que script_output=$(your-script)), car cela signifie que la lecture du processus à l'autre extrémité ne verra jamais la fin du fichier jusqu'à ce que le processus d'arrière-plan se termine.

Alors ici, il vaut mieux écrire:

{
  var=$(cmd 2>&1 >&3 3>&-)
} 3>&1

Qui, avec bashpeut être raccourci pour:

{
  var=$(cmd 2>&1 >&3-)
} 3>&1

Pour résumer les raisons pour lesquelles il est rarement utilisé:

  1. c'est du sucre non standard et juste syntaxique. Vous devez trouver un équilibre entre l'enregistrement de quelques frappes et le fait de rendre votre script moins portable et moins évident pour les personnes non habituées à cette fonctionnalité inhabituelle.
  2. La nécessité de fermer le fd d'origine après la duplication est souvent ignorée car la plupart du temps, nous n'en souffrons pas, nous le faisons donc simplement à la >&3place de >&3-ou >&3 3>&-.

La preuve qu'il est rarement utilisé, comme vous l'avez découvert, c'est qu'il est faux en bash . En bash compound-command 3>&4-ou any-builtin 3>&4-laisse fd 4 fermé même après compound-commandou any-builtinest revenu. Un correctif pour résoudre le problème est désormais disponible (2013-02-19).

Stéphane Chazelas
la source
Merci, maintenant je sais ce qu'est le déplacement d'un fd. J'ai 4 questions de base concernant le 2ème extrait (et les fds en général): 1) Dans cmd1, vous faites 2 (stderr) pour être une copie de 3, et si la commande utilisait en interne ce 3 fd? 2) Pourquoi 3> & 1 et 4> & 1 fonctionnent-ils? La duplication de 3 et 4 ne prend effet que dans ces deux cmds, le shell actuel est-il également affecté? 3) Pourquoi fermez-vous 4 en cmd1 et 3 en cmd2? Ces commandes n'utilisent pas les fds mentionnés, n'est-ce pas? 4) Dans le dernier extrait, que se passe-t-il si un fd est dupliqué sur un inexistant (en cmd1 et cmd2), je veux dire 3 et 4, respectivement?
Quentin
@Quentin, j'ai utilisé un exemple plus simple et expliqué un peu plus en espérant qu'il soulève maintenant moins de questions qu'il n'y répond. Si vous avez encore des questions qui ne sont pas directement liées à la syntaxe de déplacement fd, je vous suggère de poser une question distincte
Stéphane Chazelas
{ var=$(cmd 2>&1 >&3) ; } 3>&1-N'est-ce pas une faute de frappe en terminant 1?
Quentin
@Quentin, c'est une faute de frappe dans la mesure où je ne pense pas avoir l'intention de l'inclure, mais cela ne fait aucune différence car rien à l'intérieur des accolades n'utilise ce fd 1 (c'était tout l'intérêt de le dupliquer en fd 3: parce que l'original 1 sinon ne serait pas accessible à l'intérieur $(...)).
Stéphane Chazelas
1
@Quentin En entrant {...}, fd 3 pointe vers ce que fd 1 avait l'habitude de pointer et fd 1 est fermé, puis en entrant $(...), fd 1 est réglé sur le tuyau qui alimente $var, puis pour cmd2 aussi, puis 1 sur 3 points à, c'est le 1. extérieur. Le fait que 1 reste fermé par la suite est un bogue dans bash, je vais le signaler. ksh93 d'où vient cette fonctionnalité n'a pas ce bogue.
Stéphane Chazelas
4

Cela signifie qu'il doit pointer vers le même endroit que l'autre descripteur de fichier. Vous devez faire ce que très rarement, en dehors de la manutention séparée évidente du descripteur d'erreur standard ( stderr, fd 2, /dev/stderr -> /proc/self/fd/2). Il peut être utile dans certains cas complexes.

Le guide Advanced Bash Scripting contient cet exemple de niveau de journal plus long et cet extrait:

# Redirecting only stderr to a pipe.
exec 3>&1                              # Save current "value" of stdout.
ls -l 2>&1 >&3 3>&- | grep bad 3>&-    # Close fd 3 for 'grep' (but not 'ls').
#              ^^^^   ^^^^
exec 3>&-                              # Now close it for the remainder of the script.

Dans Source Mage's Sorcery, nous l'utilisons par exemple pour discerner différentes sorties d'un même bloc de code:

  (
    # everything is set, so run the actual build infrastructure
    run_build
  ) 3> >(tee -a $C_LOG >> /dev/stdout) \
    2> >(tee -a $C_LOG 1>&2 > $VOYEUR_STDERR) \
     > >(tee -a $C_LOG > $VOYEUR_STDOUT)

Il a une substitution de processus supplémentaire ajoutée pour des raisons de journalisation (VOYEUR décide si les données doivent être affichées à l'écran ou simplement se connecter), mais certains messages doivent toujours être présentés. Pour ce faire, nous les imprimons dans le descripteur de fichier 3, puis nous le traitons spécialement.

lynxlynxlynx
la source
0

Sous Unix, les fichiers sont gérés par des descripteurs de fichiers (les plus petits entiers, par exemple l'entrée standard est 0, la sortie standard est 1, l'erreur standard est 2; lorsque vous ouvrez d'autres fichiers, ils reçoivent normalement le plus petit descripteur inutilisé). Donc, si vous connaissez les entrailles du programme, et que vous souhaitez envoyer la sortie qui va au descripteur de fichier 5 à la sortie standard, vous déplaceriez le descripteur 5 à 1. C'est de là que 2> errorsvient la, et les constructions aiment 2>&1dupliquer les erreurs dans le flux de sortie.

Donc, presque jamais utilisé (je me souviens vaguement de l'avoir utilisé une ou deux fois avec colère au cours de mes 25+ années d'utilisation presque exclusive d'Unix), mais en cas de besoin absolument essentiel.

vonbrand
la source
Mais pourquoi ne pas dupliquer le descripteur de fichier 1 de la manière suivante: 5> & 1? Je ne comprends pas à quoi sert de déplacer FD car le noyau est sur le point de le fermer juste après ...
Quentin
Cela ne reproduit pas 5 en 1, il envoie 5 à l'endroit où va 1. Et avant de demander; oui, il existe plusieurs notations différentes, récupérées à partir de divers obus précurseurs.
vonbrand
Je ne comprends toujours pas. Si 5>&1envoie 5 à l'endroit où va 1, alors que fait exactement 1>&5-, en plus de fermer 5?
Quentin