J'ai un certain script bash, qui veut conserver l' /dev/stdout
emplacement d' origine avant de remplacer le 1er descripteur de fichier par un autre emplacement.
Alors, naturellement, j'ai écrit quelque chose comme
old_stdout=$(readlink -f /dev/stdout)
Et ça n'a pas marché. Très vite, je comprends quel était le problème:
test@ubuntu:~$ echo $(readlink -f /dev/stdout)
/proc/5175/fd/pipe:[31764]
test@ubuntu:~$ readlink -f /dev/stdout
/dev/pts/18
Évidemment, $()
s'exécute dans un sous-shell, qui est canalisé vers le shell parent.
La question est donc: existe-t-il un moyen fiable (limité à la portabilité entre les distributions Linux) d'enregistrer l' /dev/stdout
emplacement en tant que chaîne dans un script bash?
bash
io-redirection
io
alexey.e.egorov
la source
la source
/dev/stdout
résoudrait le problème d'impression des messages en mode silencieux. L'alternative consiste à rediriger toutes les autres actions qui produisent une sortie, et il y en a un certain nombre. Environ 100 fois plus que les messages d'interaction avec l'utilisateur.stderr
. C'est, par exemple, pourquoi les invites vontstderr
par défaut.stderr
doit également être réorienté et enregistré, car le script appelle un certain nombre de programmes externes et tous les messages d'erreur possibles doivent être collectés et enregistrés.Réponses:
Pour enregistrer un descripteur de fichier, vous le dupliquez sur un autre fd. L'enregistrement d'un chemin d'accès au fichier correspondant ne suffit pas, vous devez enregistrer le mode d'ouverture, les drapeaux d'ouverture, la position actuelle dans le fichier, etc. Et bien sûr, pour les tuyaux ou les sockets anonymes, cela ne fonctionnerait pas car ceux-ci n'ont pas de chemin. Ce que vous voulez enregistrer, c'est la description du fichier ouvert à laquelle le fd fait référence, et la duplication d'un fd renvoie en fait un nouveau fd à la même description de fichier ouvert .
Pour dupliquer un descripteur de fichier sur un autre, avec un shell de type Bourne, la syntaxe est:
Ci-dessus, fd 1 est dupliqué sur fd 3.
Quel que soit le fd 3 qui était déjà ouvert auparavant, il sera fermé, mais notez que les fds 3 à 9 (généralement plus, jusqu'à 99 avec
yash
) sont réservés à cette fin (et n'ont aucune signification particulière contrairement à 0, 1 ou 2), le shell sait ne pas les utiliser pour ses propres affaires internes. La seule raison pour laquelle fd 3 aurait été ouvert au préalable est que vous l'avez fait dans le script 1 ou qu'il a été divulgué par l'appelant.Ensuite, vous pouvez remplacer stdout par autre chose:
Et plus tard, pour restaurer stdout:
(
3>&-
étant de fermer le descripteur de fichier dont nous n'avons plus besoin).Maintenant, le problème avec cela est que, sauf dans ksh, chaque commande que vous exécutez après
exec 3>&1
héritera de ce fd 3. C'est une fuite de fd. En général, ce n'est pas grave, mais cela peut poser problème.ksh
définit l' indicateur close-on-exec sur ces fds (pour les fds de plus de 2), mais les autres shells et autres shells n'ont aucun moyen de définir ce flag manuellement.Le travail autour d'un autre shell consiste à fermer le fd 3 pour chaque commande, comme:
Lourd. Ici, la meilleure façon serait de ne pas utiliser
exec
du tout, mais de rediriger les groupes de commandes:Là, c'est le shell qui prend soin de sauvegarder stdout et de le restaurer ensuite (et il le fait en interne en le dupliquant sur un fd (au-dessus de 9, au-dessus de 99 pour
yash
) avec l' indicateur close-on-exec défini).Remarque 1
Désormais, la gestion de ces fds 3 à 9 peut être lourde et problématique si vous les utilisez largement ou dans des fonctions, en particulier si votre script utilise du code tiers qui peut à son tour utiliser ces fds.
Quelques coquilles (
zsh
,bash
,ksh93
, tous ajouté la fonction ( suggérée par Oliver Kiddle dezsh
) dans le même temps en 2005 après avoir été discuté parmi leurs développeurs) ont une syntaxe alternative pour attribuer le premier fd libre au- dessus de 10 au lieu qui aide dans ce cas:la source
rc.local
service, par exemple, donc vous devriez vraiment avoir utilisé quelque chose commeexec {FD}>&1
ou quelque chose. Mais cela n'est pris en charge que dans bash 4, ce qui est vraiment triste. Ce n'est donc pas vraiment portable.eval "exec $i>&1"
c'est une chose que j'aimerais éviter, à cause de sa lourdeur. Puis-je vraiment compter sur le fait que les fds au-dessus de 9 seraient gratuits alors?bash
vous permettra de vous tirer une balle dans le pied.Comme vous pouvez le voir, les scripts bash ne sont pas comme un langage de programmation ordinaire où vous pouvez attribuer des descripteurs de fichiers.
La solution la plus simple consiste à utiliser un sous-shell pour exécuter ce que vous voulez rediriger afin que le traitement puisse être rétabli vers le shell supérieur qui a ses E / S standard intactes.
Une autre solution consisterait
tty
à identifier le périphérique TTY et à contrôler les E / S dans votre script. Par exemple:et puis vous pouvez ..
la source
$$
vous obtiendrait le PID de processus actuel, en cas de shell interactif ou de script le PID de shell correspondant.Vous pouvez donc utiliser:
Exemple:
la source
/proc
structure spécifique entraîne des problèmes de portabilité, tout comme l'utilisation/dev/stdout
comme mentionné dans la question./proc
structure spécifique ? Cela fonctionnerait sur n'importe quel Linux qui aprocfs
..procfs
est presque toujours présent, mais nous voyons souvent des questions de portabilité et une bonne méthodologie de développement inclut la prise en compte de la portabilité vers d'autres systèmes.bash
peut fonctionner sur une multitude de systèmes d'exploitation.