web-dev-qa-db-fra.com

comment sortir du texte à la fois à l'écran et au fichier dans un script Shell?

Actuellement, j'ai un script Shell qui enregistre les messages dans un fichier journal comme celui-ci:

log_file="/some/dir/log_file.log"
echo "some text" >> $log_file
do_some_command
echo "more text" >> $log_file
do_other_command

Lors de l'exécution de ce script, il n'y a pas de sortie à l'écran et, comme je me connecte au serveur via PuTTY, je dois ouvrir une autre connexion et faire "tail -f log_file_path.log", car je ne peux pas terminer l'exécution script et je veux voir la sortie en temps réel.

Évidemment, ce que je veux, c'est que les messages texte soient imprimés à l'écran et dans un fichier, mais je voudrais le faire en une seule ligne, pas deux lignes, dont l'une n'a pas de redirection vers le fichier.

Comment y parvenir?

49
jurchiks

Cela marche:

command | tee -a "$log_file"

tee enregistre l'entrée dans un fichier (utilisez -a à ajouter plutôt qu'à remplacer) et copie également l'entrée sur la sortie standard.

73
l0b0

Vous pouvez utiliser un ici-doc et. source pour un modèle de collecteur général efficace et convivial POSIX.

. 8<<-\IOHERE /proc/self/fd/8

command
… 
fn() { declaration ; } <<WHATEVER
# though a nested heredoc might be finicky
# about stdin depending on Shell
WHATEVER
cat -u ./stdout | ./works.as >> expect.ed
IOHERE

Lorsque vous ouvrez l'hérédoc, vous signalez au shell avec un jeton d'entrée IOHERE qu'il doit rediriger son entrée vers le descripteur de fichier que vous spécifiez jusqu'à ce qu'il rencontre l'autre extrémité de votre jeton de limitation. J'ai regardé autour de moi, mais je n'ai pas vu de nombreux exemples d'utilisation du numéro de redirection fd comme je l'ai montré ci-dessus en combinaison avec l'opérateur heredoc, bien que son utilisation soit clairement spécifiée dans les directives de base de la commande shell POSIX. La plupart des gens le pointent simplement sur stdin et tirent, mais je trouve que le sourcing de scriptlets de cette manière peut empêcher stdin et les applications constituantes de se plaindre des chemins d'E/S bloqués.

Le contenu de l'hérédoc est diffusé dans le descripteur de fichier que vous spécifiez, qui est à son tour interprété comme Shell-code et exécuté par le. intégré, mais pas sans spécifier un chemin spécifique pour. . Si le chemin/proc/self vous pose problème, essayez/dev/fd/n ou/proc/$$. Cette même méthode fonctionne sur les tuyaux, soit dit en passant:

cat ./*.sh | . /dev/stdin

Est probablement au moins aussi imprudent qu'il n'y paraît. Vous pouvez faire la même chose avec sh, bien sûr, mais le but de. Est d'exécuter dans l'environnement Shell actuel, ce qui est probablement ce que vous voulez, et, selon votre Shell, il est beaucoup plus probable de travailler avec un hérédoc que avec un tube anonyme standard.

Quoi qu'il en soit, comme vous l'avez probablement remarqué, je n'ai toujours pas répondu à votre question. Mais si vous y réfléchissez, de la même manière que l'hérédoc diffuse tout votre code vers .'s in, il vous fournit également un point de sortie simple et simple:

. 5<<EOIN /dev/fd/5 |\ 
    tee -a ./log.file | cat -u >$(tty)
script
… 
more script
EOIN

Ainsi, toute la sortie standard du terminal à partir du code exécuté dans votre hérédoc est acheminée. et peut facilement être retiré d'un seul tuyau. J'ai inclus l'appel de chat sans tampon parce que je ne suis pas clair sur la direction actuelle de la sortie standard, mais c'est probablement redondant (presque certainement comme il est écrit de toute façon) et le pipeline peut probablement se terminer juste au départ.

Vous pouvez également remettre en question la citation antislash manquante dans le deuxième exemple. Cette partie est importante à comprendre avant de vous lancer et pourrait vous donner quelques idées sur la façon dont elle peut être utilisée. Un limiteur hérédoc cité (jusqu'à présent, nous avons utilisé IOHERE et EOIN, et le premier que j'ai cité avec une barre oblique inverse, bien que les guillemets "simples" ou "doubles" servent le même but) empêcheront le shell d'effectuer toute expansion de paramètre sur le contenu, mais un limiteur non cité laissera son contenu ouvert à l'expansion. Les conséquences de cela lorsque votre hérédoc est. les sources sont dramatiques:

. 3<<HD ${fdpath}/3
: \${vars=$(printf '${var%s=$((%s*2))},' `seq 1 100`)} 
HD
echo $vars
> 4,8,12… 
echo $var1 $var51
> 4 104

Parce que je n'ai pas cité le limiteur heredoc, Shell a développé le contenu en le lisant et avant de servir le descripteur de fichier résultant. éxécuter. Cela a essentiellement abouti à l'analyse des commandes deux fois - les extensions de toute façon. Parce que j'ai ajouté une barre oblique inverse à l'expansion du paramètre $ vars, le shell a ignoré sa déclaration lors de la première passe et n'a supprimé la barre oblique inverse que le contenu étendu de printf pourrait être évalué par null quand. sourced le script sur la deuxième passe.

Cette fonctionnalité est fondamentalement exactement ce que le dangereux programme intégré eval Shell peut fournir, même si la citation est beaucoup plus facile à gérer dans un hérédoc qu'avec eval, et peut être tout aussi dangereuse. Sauf si vous le planifiez soigneusement, il est probablement préférable de citer le limiteur "EOF" par habitude. Je dis juste.

EDIT: Eh, j'y repense et je pense que c'est un peu trop étiré. Si TOUT ce que vous devez faire est de concaténer plusieurs sorties dans un seul tuyau, la méthode la plus simple consiste simplement à utiliser un:

{ or ( command ) list ; } | tee -a ea.sy >>pea.sy

Les curlies tenteront d'exécuter le contenu dans le shell actuel tandis que les parens seront automatiquement supprimés. Pourtant, n'importe qui peut vous le dire et, du moins à mon avis, le. La solution heredoc est une information beaucoup plus précieuse, surtout si vous souhaitez comprendre le fonctionnement réel du shell.

S'amuser!

8
mikeserv

J'ai trouvé cette réponse en cherchant à modifier un script d'installation pour écrire un journal d'installation.

Mon script est déjà plein d'instructions d'écho comme:

echo "Found OLD run script $oldrunscriptname"
echo "Please run OLD tmunsetup script first!"

Et je ne voulais pas qu'une instruction tee l'exécute (ou un autre script pour appeler l'existant avec un tee), alors j'ai écrit ceci:

#!/bin/bash
# A Shell subroutine to echo to screen and a log file

LOGFILE="junklog"

echolog()
(
echo $1
echo $1 >> $LOGFILE
)


echo "Going"
echolog "tojunk"

# eof

Alors maintenant, dans mon script d'origine, je peux simplement changer 'echo' en 'echolog' où je veux la sortie dans un fichier journal.

3
AndruWitta