web-dev-qa-db-fra.com

Comment fonctionnent les descripteurs de fichiers?

Quelqu'un peut-il me dire pourquoi cela ne fonctionne pas? Je joue avec les descripteurs de fichiers, mais je me sens un peu perdu.

#!/bin/bash
echo "This"
echo "is" >&2
echo "a" >&3
echo "test." >&4

Les trois premières lignes fonctionnent bien, mais les deux dernières erreurs disparaissent. Pourquoi?

66
Trcx

Les descripteurs de fichier 0, 1 et 2 sont respectivement pour stdin, stdout et stderr.

Les descripteurs de fichiers 3, 4, .. 9 concernent des fichiers supplémentaires. Pour les utiliser, vous devez d'abord les ouvrir. Par exemple:

exec 3<> /tmp/foo  #open fd 3.
echo "test" >&3
exec 3>&- #close fd 3.

Pour plus d'informations, consultez Advanced Bash-Scripting Guide: Chapter 20. I/O Redirection .

97
dogbane

C'est une vieille question mais ne chose a besoin d'être clarifiée.

Bien que les réponses de Carl Norum et dogbane soient correctes, l'hypothèse est de changer votre script pour le faire fonctionner.

Ce que je voudrais souligner, c'est que vous n'avez pas besoin de changer le script:

#!/bin/bash
echo "This"
echo "is" >&2
echo "a" >&3
echo "test." >&4

Cela fonctionne si vous l'appelez différemment:

./fdtest 3>&1 4>&1

ce qui signifie rediriger les descripteurs de fichiers 3 et 4 vers 1 (qui est la sortie standard).

Le fait est que le script est parfaitement correct en voulant écrire dans des descripteurs autres que 1 et 2 (stdout et stderr) si ces descripteurs sont fournis par le processus parent.

Votre exemple est en fait assez intéressant car ce script peut écrire dans 4 fichiers différents:

./fdtest >file1.txt 2>file2.txt 3>file3.txt 4>file4.txt

Vous avez maintenant la sortie dans 4 fichiers distincts:

$ for f in file*; do echo $f:; cat $f; done
file1.txt:
This
file2.txt:
is
file3.txt:
a
file4.txt:
test.

Ce qui est plus intéressant à ce sujet, c'est que votre programme n'a pas besoin d'avoir des autorisations d'écriture pour ces fichiers, car il ne les ouvre pas réellement.

Par exemple, lorsque j'exécute Sudo -s pour changer l'utilisateur en root, créez un répertoire en tant que root et essayez d'exécuter la commande suivante en tant qu'utilisateur normal (rsp dans mon cas) comme ceci:

# su rsp -c '../fdtest >file1.txt 2>file2.txt 3>file3.txt 4>file4.txt'

Je reçois une erreur:

bash: file1.txt: Permission denied

Mais si je fais la redirection en dehors de su:

# su rsp -c '../fdtest' >file1.txt 2>file2.txt 3>file3.txt 4>file4.txt

(notez la différence entre guillemets simples) ça marche et j'obtiens:

# ls -alp
total 56
drwxr-xr-x 2 root root 4096 Jun 23 15:05 ./
drwxrwxr-x 3 rsp  rsp  4096 Jun 23 15:01 ../
-rw-r--r-- 1 root root    5 Jun 23 15:05 file1.txt
-rw-r--r-- 1 root root   39 Jun 23 15:05 file2.txt
-rw-r--r-- 1 root root    2 Jun 23 15:05 file3.txt
-rw-r--r-- 1 root root    6 Jun 23 15:05 file4.txt

qui sont 4 fichiers appartenant à root dans un répertoire appartenant à root - même si le script n'avait pas d'autorisations pour créer ces fichiers.

Un autre exemple serait d'utiliser la prison chroot ou un conteneur et d'exécuter un programme à l'intérieur où il n'aurait pas accès à ces fichiers même s'il était exécuté en tant que root et de rediriger ces descripteurs en externe là où vous en avez besoin, sans donner réellement accès à l'ensemble du fichier système ou toute autre chose à ce script.

Le fait est que vous avez découvert un mécanisme très intéressant et utile. Vous n'êtes pas obligé d'ouvrir tous les fichiers à l'intérieur de votre script comme cela a été suggéré dans d'autres réponses. Parfois, il est utile de les rediriger lors de l'invocation du script.

Pour résumer, ceci:

echo "This"

est en fait équivalent à:

echo "This" >&1

et exécuter le programme en tant que:

./program >file.txt

est le même que:

./program 1>file.txt

Le numéro 1 est juste un numéro par défaut et il est standard.

Mais même ce programme:

#!/bin/bash
echo "This"

peut produire une erreur "Bad descriptor". Comment? Lorsqu'il est exécuté en tant que:

./fdtest2 >&-

La sortie sera:

./fdtest2: line 2: echo: write error: Bad file descriptor

Ajouter >&- (qui est identique à 1>&-) signifie fermer la sortie standard. Ajouter 2>&- signifierait la fermeture du stderr.

Vous pouvez même faire ne chose plus compliquée. Votre script original:

#!/bin/bash
echo "This"
echo "is" >&2
echo "a" >&3
echo "test." >&4

lorsqu'il est exécuté avec juste:

./fdtest

impressions:

This
is
./fdtest: line 4: 3: Bad file descriptor
./fdtest: line 5: 4: Bad file descriptor

Mais vous pouvez faire fonctionner les descripteurs 3 et 4, mais le numéro 1 échoue en exécutant:

./fdtest 3>&1 4>&1 1>&-

Il génère:

./fdtest: line 2: echo: write error: Bad file descriptor
is
a
test.

Si vous voulez que les descripteurs 1 et 2 échouent, exécutez-le comme ceci:

./fdtest 3>&1 4>&1 1>&- 2>&-

Vous recevez:

a
test.

Pourquoi? Rien n'a échoué? Oui mais sans stderr (descripteur de fichier numéro 2) vous n'avez pas vu les messages d'erreur!

Je pense qu'il est très utile d'expérimenter de cette façon pour avoir une idée du fonctionnement des descripteurs et de leur redirection.

Votre script est en effet un exemple très intéressant - et je soutiens que il n'est pas du tout cassé, vous l'utilisiez mal! :)

57
rsp

Il échoue car ces descripteurs de fichiers ne pointent vers rien! Les descripteurs de fichier par défaut normaux sont l'entrée standard 0, la sortie standard 1, et le flux d'erreur standard 2. Étant donné que votre script n'ouvre aucun autre fichier, il n'existe aucun autre descripteur de fichier valide. Vous pouvez ouvrir un fichier en bash en utilisant exec. Voici une modification de votre exemple:

#!/bin/bash
exec 3> out1     # open file 'out1' for writing, assign to fd 3
exec 4> out2     # open file 'out2' for writing, assign to fd 4

echo "This"      # output to fd 1 (stdout)
echo "is" >&2    # output to fd 2 (stderr)
echo "a" >&3     # output to fd 3
echo "test." >&4 # output to fd 4

Et maintenant, nous allons l'exécuter:

$ ls
script
$ ./script 
This
is
$ ls
out1    out2    script
$ cat out*
a
test.
$

Comme vous pouvez le voir, la sortie supplémentaire a été envoyée aux fichiers demandés.

18
Carl Norum

Pour ajouter à réponse de rsp et répondre à la question dans les commentaires de cette réponse de @ MattClimbs .

Vous pouvez tester si le descripteur de fichier est ouvert ou non en tentant de le rediriger tôt et s'il échoue, ouvrez le descripteur de fichier numéroté souhaité à quelque chose comme /dev/null. Je le fais régulièrement dans les scripts et j'utilise les descripteurs de fichiers supplémentaires pour transmettre des détails ou des réponses supplémentaires au-delà de return #.

script.sh

#!/bin/bash
2>/dev/null >&3 || exec 3>/dev/null
2>/dev/null >&4 || exec 4>/dev/null

echo "This"
echo "is" >&2
echo "a" >&3
echo "test." >&4

Le stderr est redirigé vers /dev/null Pour ignorer la réponse bash: #: Bad file descriptor Possible et le || Est utilisé pour traiter la commande suivante exec #>/dev/null Lorsque la précédente se termine avec un statut non nul. Dans le cas où le descripteur de fichier est déjà ouvert, les deux tests renverraient un état zéro et la commande exec ... Ne serait pas exécutée.

Appeler le script sans aucune redirection donne:

# ./script.sh
This
is

Dans ce cas, les redirections pour a et test sont expédiées à /dev/null

L'appel du script avec une redirection définie donne:

# ./script.sh 3>temp.txt 4>>temp.txt
This
is
# cat temp.txt
a
test.

La première redirection 3>temp.txt Écrase le fichier temp.txt Tandis que 4>>temp.txt S'ajoute au fichier.

En fin de compte, vous pouvez définir des fichiers par défaut vers lesquels rediriger dans le script si vous voulez autre chose que /dev/null Ou vous pouvez changer la méthode d'exécution du script et rediriger ces descripteurs de fichiers supplémentaires où vous le souhaitez.

0
AGipson