78 votes

Comment fonctionnent les descripteurs de fichiers ?

Quelqu'un peut-il me dire pourquoi cela ne fonctionne pas ? Je m'amuse 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 se plantent. Pourquoi ?

118voto

dogbane Points 85749

Les descripteurs de fichiers 0, 1 et 2 correspondent respectivement à stdin, stdout et stderr.

Les descripteurs de fichiers 3, 4, 9 sont destinés à 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 le site Guide avancé des scripts Bash : Chapitre 20. Redirection des E/S .

1 votes

C'est ce que je cherche ! Je dois donc spécifier un fichier pour qu'il l'utilise comme lieu de stockage temporaire avec la commande exec, puis les fermer lorsque j'ai terminé ? Désolé, je suis un peu confus avec la commande exec, je ne l'utilise pas beaucoup.

1 votes

Oui, mais ce n'est pas temporaire. Le fichier existera même après la fin de votre programme.

0 votes

Cela pourrait bien fonctionner alors, j'essaie de porter quelques scripts pour qu'ils soient compatibles avec le planificateur de tâches crontab, mais j'ai des difficultés car cron ne permet pas le piping de stdout dans les scripts.

91voto

rsp Points 11526

C'est une vieille question mais une chose doit être clarifiée .

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

Ce que j'aimerais 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'invoquez 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 bien. en voulant écrire sur des descripteurs autres que les 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

Maintenant vous avez la sortie dans 4 fichiers séparés :

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

Qu'est-ce que plus intéressant est que votre programme n'a pas besoin d'avoir les droits d'écriture pour ces fichiers, car il ne les ouvre pas.

Par exemple, lorsque je lance sudo -s pour changer l'utilisateur en Root, créer un répertoire en tant que Root, et essayer 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 les guillemets simples) ça marche et je reçois :

# 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 de permissions pour créer ces fichiers.

Un autre exemple serait d'utiliser chroot jail 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 toujours rediriger ces descripteurs vers l'extérieur où vous en avez besoin, sans réellement donner accès à l'ensemble du système de fichiers ou à quoi que ce soit d'autre à ce script.

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

En résumé ça :

echo "This"

est en fait équivalent à :

echo "This" >&1

et en exécutant le programme en tant que :

./program >file.txt

est la même chose que :

./program 1>file.txt

Le nombre 1 est juste un nombre par défaut et c'est stdout.

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

Ajout de >&- (ce qui est identique à 1>&- ) signifie la fermeture de la sortie standard. Ajout de 2>&- signifierait fermer le stderr.

Vous pouvez même faire une 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

des empreintes :

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

Mais vous pouvez faire en sorte que les descripteurs 3 et 4 fonctionnent, mais que le numéro 1 échoue en courant :

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

Il sort :

./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 obtenez :

a
test.

Pourquoi ? Rien n'a échoué ? Il a fait 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 manière pour avoir une idée de la façon dont les descripteurs et leur redirection fonctionnent.

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

0 votes

Réponse très intéressante. Merci

0 votes

En fait, le descripteur de fichier 1 est stdout ; stdin est le descripteur de fichier 0.

0 votes

@programmerjake Oops. La faute de frappe a été corrigée. Merci de l'avoir signalé.

19voto

Carl Norum Points 114072

Il échoue parce que ces descripteurs de fichiers ne pointent vers rien ! Les descripteurs de fichiers par défaut normaux sont l'entrée standard 0 la sortie standard 1 et le flux d'erreur standard 2 . Puisque votre script n'ouvre aucun autre fichier, il n'y a pas d'autres descripteurs de fichiers valides. Vous pouvez ouvrir un fichier dans 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.

0 votes

Y a-t-il un moyen de lui faire écrire la sortie sur le terminal ? Je veux pouvoir tout voir dans le terminal, mais je veux pouvoir envoyer la sortie où je veux, c'est-à-dire ./script 2>out.2 3>out.3 4>out.4

0 votes

@Trcx, si vous souhaitez écrire dans le terminal, utilisez stdout o stderr . Pourquoi auriez-vous besoin ou voudriez-vous utiliser d'autres fichiers pour cela ?

0 votes

Les scripts que j'essaie de rendre compatibles avec crontab doivent écrire plusieurs fichiers, mais crontab ne permet pas d'écrire des fichiers à partir de scripts (parce qu'il manque le support de stdout) Cependant, je peux utiliser crontab pour écrire la sortie des scripts dans un fichier. Je pensais que je pourrais avoir les scripts écrire sur les différentes sorties, puis avoir crontab séparer tout dans les fichiers appropriés. Je cherchais juste un moyen astucieux d'écrire dans les fichiers sans utiliser stdout. Mais grâce à vous, je me suis rendu compte que j'avais trop réfléchi. (encore une fois :P ) Merci pour votre aide !

2voto

AGipson Points 51

Pour ajouter à la 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 essayant de rediriger vers lui au début et si cela échoue, ouvrez le descripteur de fichier numéroté souhaité à quelque chose comme /dev/null . Je le fais régulièrement au sein de scripts et j'exploite les descripteurs de fichiers supplémentaires pour renvoyer des détails ou des réponses supplémentaires au-delà. 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 écarter les éventuelles bash: #: Bad file descriptor et la || est utilisé pour traiter la commande suivante exec #>/dev/null lorsque la précédente sort avec un état non nul. Dans le cas où le descripteur de fichier est déjà ouvert, les deux tests renverraient un statut zéro et la commande exec ... ne serait pas exécutée.

L'appel du script sans redirections donne :

# ./script.sh
This
is

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

Appeler le script avec une redirection définie donne des résultats :

# ./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 ajoute au fichier.

En fin de compte, vous pouvez définir des fichiers par défaut vers lesquels rediriger à l'intérieur du 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 voulez.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X