Je veux rediriger à la fois stdout et stderr d'un processus vers un seul fichier. Comment faire ça dans Bash?
Je veux rediriger à la fois stdout et stderr d'un processus vers un seul fichier. Comment faire ça dans Bash?
Réponses:
Jetez un oeil ici . Devrait être:
yourcommand &>filename
(redirige les deux stdout
et stderr
vers le nom de fichier).
#!/bin/bash
plutôt que #!/bin/sh
, car dans requiert bash.
do_something 2>&1 | tee -a some_file
Cela va rediriger stderr vers stdout et stdout vers some_file
et l' imprimer vers stdout.
do_something &>filename
ne le fait pas. +1.
Ambiguous output redirect.
idée pourquoi?
$?
ne fait plus référence à l'état de sortie de do_something
, mais à l'état de sortie de tee
.
Vous pouvez rediriger stderr vers stdout et la stdout dans un fichier:
some_command >file.log 2>&1
Voir http://tldp.org/LDP/abs/html/io-redirection.html
Ce format est préféré au format &> le plus populaire qui ne fonctionne qu'en bash. Dans Bourne shell, cela pourrait être interprété comme exécutant la commande en arrière-plan. Le format est également plus lisible 2 (est STDERR) redirigé vers 1 (STDOUT).
EDIT: a changé l'ordre comme indiqué dans les commentaires
# Close STDOUT file descriptor
exec 1<&-
# Close STDERR FD
exec 2<&-
# Open STDOUT as $LOG_FILE file for read and write.
exec 1<>$LOG_FILE
# Redirect STDERR to STDOUT
exec 2>&1
echo "This line will appear in $LOG_FILE, not 'on screen'"
Maintenant, l'écho simple écrira dans $ LOG_FILE. Utile pour démoniser.
À l'auteur de l'article d'origine,
Cela dépend de ce que vous devez réaliser. Si vous avez juste besoin de rediriger vers / depuis une commande que vous appelez depuis votre script, les réponses sont déjà données. Le mien concerne la redirection dans le script actuel qui affecte toutes les commandes / commandes intégrées (y compris les fourches) après l'extrait de code mentionné.
Une autre solution intéressante consiste à rediriger à la fois std-err / out ET vers un enregistreur ou un fichier journal à la fois, ce qui implique de diviser "un flux" en deux. Cette fonctionnalité est fournie par la commande 'tee' qui peut écrire / ajouter à plusieurs descripteurs de fichiers (fichiers, sockets, tuyaux, etc.) à la fois: tee FILE1 FILE2 ...> (cmd1)> (cmd2) ...
exec 3>&1 4>&2 1> >(tee >(logger -i -t 'my_script_tag') >&3) 2> >(tee >(logger -i -t 'my_script_tag') >&4)
trap 'cleanup' INT QUIT TERM EXIT
get_pids_of_ppid() {
local ppid="$1"
RETVAL=''
local pids=`ps x -o pid,ppid | awk "\\$2 == \\"$ppid\\" { print \\$1 }"`
RETVAL="$pids"
}
# Needed to kill processes running in background
cleanup() {
local current_pid element
local pids=( "$$" )
running_pids=("${pids[@]}")
while :; do
current_pid="${running_pids[0]}"
[ -z "$current_pid" ] && break
running_pids=("${running_pids[@]:1}")
get_pids_of_ppid $current_pid
local new_pids="$RETVAL"
[ -z "$new_pids" ] && continue
for element in $new_pids; do
running_pids+=("$element")
pids=("$element" "${pids[@]}")
done
done
kill ${pids[@]} 2>/dev/null
}
Donc, depuis le début. Supposons que nous ayons un terminal connecté à / dev / stdout (FD # 1) et / dev / stderr (FD # 2). En pratique, il peut s'agir d'un tuyau, d'une douille ou autre.
Le résultat de l'exécution d'un script ayant la ligne ci-dessus et en plus celle-ci:
echo "Will end up in STDOUT(terminal) and /var/log/messages"
...est comme suit:
$ ./my_script
Will end up in STDOUT(terminal) and /var/log/messages
$ tail -n1 /var/log/messages
Sep 23 15:54:03 wks056 my_script_tag[11644]: Will end up in STDOUT(terminal) and /var/log/messages
Si vous voulez voir une image plus claire, ajoutez ces 2 lignes au script:
ls -l /proc/self/fd/
ps xf
bash your_script.sh 1>file.log 2>&1
1>file.log
demande au shell d'envoyer STDOUT au fichier file.log
et lui 2>&1
dit de rediriger STDERR (descripteur de fichier 2) vers STDOUT (descripteur de fichier 1).
Remarque: la commande est importante, comme l'a souligné liw.fi, 2>&1 1>file.log
ne fonctionne pas.
Curieusement, cela fonctionne:
yourcommand &> filename
Mais cela donne une erreur de syntaxe:
yourcommand &>> filename
syntax error near unexpected token `>'
Vous devez utiliser:
yourcommand 1>> filename 2>&1
&>>
semble fonctionner sur BASH 4:$ echo $BASH_VERSION 4.1.5(1)-release $ (echo to stdout; echo to stderr > /dev/stderr) &>> /dev/null
Réponse courte: Command >filename 2>&1
ouCommand &>filename
Explication:
Considérez le code suivant qui imprime le mot "stdout" sur stdout et le mot "stderror" sur stderror.
$ (echo "stdout"; echo "stderror" >&2)
stdout
stderror
Notez que l'opérateur '&' indique à bash que 2 est un descripteur de fichier (qui pointe vers le stderr) et non un nom de fichier. Si nous omettons le «&», cette commande s'imprime stdout
sur stdout, crée un fichier nommé «2» et stderror
y écrit .
En expérimentant avec le code ci-dessus, vous pouvez voir par vous-même exactement comment fonctionnent les opérateurs de redirection. Par exemple, en modifiant le fichier, lequel des deux descripteurs 1,2
est redirigé vers /dev/null
les deux lignes de code suivantes, supprimez respectivement tout de la stdout et tout de stderror (en imprimant ce qui reste).
$ (echo "stdout"; echo "stderror" >&2) 1>/dev/null
stderror
$ (echo "stdout"; echo "stderror" >&2) 2>/dev/null
stdout
Maintenant, nous pouvons expliquer pourquoi la solution pourquoi le code suivant ne produit aucune sortie:
(echo "stdout"; echo "stderror" >&2) >/dev/null 2>&1
Pour vraiment comprendre cela, je vous recommande fortement de lire cette page Web sur les tableaux de descripteurs de fichiers . En supposant que vous ayez fait cette lecture, nous pouvons continuer. Notez que Bash traite de gauche à droite; Bash voit donc en >/dev/null
premier (ce qui est le même que 1>/dev/null
) et définit le descripteur de fichier 1 pour pointer vers / dev / null au lieu de la sortie standard. Cela fait, Bash se déplace ensuite vers la droite et voit 2>&1
. Cela définit le descripteur de fichier 2 pour pointer vers le même fichier que le descripteur de fichier 1 (et non pour le descripteur de fichier 1 lui-même !!!! (voir cette ressource sur les pointeurspour plus d'informations)) . Étant donné que le descripteur de fichier 1 pointe vers / dev / null et que le descripteur de fichier 2 pointe vers le même fichier que le descripteur de fichier 1, le descripteur de fichier 2 pointe désormais également vers / dev / null. Ainsi, les deux descripteurs de fichiers pointent vers / dev / null, et c'est pourquoi aucune sortie n'est rendue.
Pour tester si vous comprenez vraiment le concept, essayez de deviner la sortie lorsque nous changeons l'ordre de redirection:
(echo "stdout"; echo "stderror" >&2) 2>&1 >/dev/null
stderror
Le raisonnement ici est que l'évaluation de gauche à droite, Bash voit 2> & 1, et définit ainsi le descripteur de fichier 2 pour pointer au même endroit que le descripteur de fichier 1, c'est-à-dire stdout. Il définit ensuite le descripteur de fichier 1 (rappelez-vous que> / dev / null = 1> / dev / null) pour pointer vers> / dev / null, supprimant ainsi tout ce qui serait normalement envoyé vers la sortie standard. Ainsi, tout ce qui nous reste est ce qui n'a pas été envoyé à stdout dans le sous-shell (le code entre parenthèses) - c'est-à-dire "stderror". La chose intéressante à noter est que même si 1 n'est qu'un pointeur vers la sortie standard, la redirection du pointeur 2 vers 1 via 2>&1
NE forme PAS une chaîne de pointeurs 2 -> 1 -> stdout. Si c'est le cas, suite à la redirection de 1 vers / dev / null, le code2>&1 >/dev/null
donnerait la chaîne de pointeurs 2 -> 1 -> / dev / null, et donc le code ne générerait rien, contrairement à ce que nous avons vu ci-dessus.
Enfin, je note qu'il existe un moyen plus simple de procéder:
De la section 3.6.4 ici , nous voyons que nous pouvons utiliser l'opérateur &>
pour rediriger stdout et stderr. Ainsi, pour rediriger à la fois la sortie stderr et stdout de n'importe quelle commande \dev\null
(qui supprime la sortie), nous tapons simplement
$ command &> /dev/null
ou dans le cas de mon exemple:
$ (echo "stdout"; echo "stderror" >&2) &>/dev/null
Points clés à retenir:
2>&1 >/dev/null
c'est! = >/dev/null 2>&1
. L'un génère une sortie et l'autre pas!Enfin, jetez un œil à ces excellentes ressources:
Documentation Bash sur la redirection , explication des tableaux de descripteurs de fichiers , introduction aux pointeurs
LOG_FACILITY="local7.notice"
LOG_TOPIC="my-prog-name"
LOG_TOPIC_OUT="$LOG_TOPIC-out[$$]"
LOG_TOPIC_ERR="$LOG_TOPIC-err[$$]"
exec 3>&1 > >(tee -a /dev/fd/3 | logger -p "$LOG_FACILITY" -t "$LOG_TOPIC_OUT" )
exec 2> >(logger -p "$LOG_FACILITY" -t "$LOG_TOPIC_ERR" )
Il est lié: Ecrire stdOut & stderr dans syslog.
Cela fonctionne presque, mais pas de xinted; (
Je voulais une solution pour que la sortie de stdout plus stderr soit écrite dans un fichier journal et stderr toujours sur la console. J'ai donc dû dupliquer la sortie stderr via tee.
Voici la solution que j'ai trouvée:
command 3>&1 1>&2 2>&3 1>>logfile | tee -a logfile
Pour la situation, lorsque la "tuyauterie" est nécessaire, vous pouvez utiliser:
| &
Par exemple:
echo -ne "15\n100\n"|sort -c |& tee >sort_result.txt
ou
TIMEFORMAT=%R;for i in `seq 1 20` ; do time kubectl get pods |grep node >>js.log ; done |& sort -h
Ces solutions basées sur bash peuvent diriger STDOUT et STDERR séparément (de STDERR de "sort -c" ou de STDERR à "sort -h").
Les fonctions suivantes peuvent être utilisées pour automatiser le processus de basculement des sorties entre stdout / stderr et un fichier journal.
#!/bin/bash
#set -x
# global vars
OUTPUTS_REDIRECTED="false"
LOGFILE=/dev/stdout
# "private" function used by redirect_outputs_to_logfile()
function save_standard_outputs {
if [ "$OUTPUTS_REDIRECTED" == "true" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: Cannot save standard outputs because they have been redirected before"
exit 1;
fi
exec 3>&1
exec 4>&2
trap restore_standard_outputs EXIT
}
# Params: $1 => logfile to write to
function redirect_outputs_to_logfile {
if [ "$OUTPUTS_REDIRECTED" == "true" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: Cannot redirect standard outputs because they have been redirected before"
exit 1;
fi
LOGFILE=$1
if [ -z "$LOGFILE" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: logfile empty [$LOGFILE]"
fi
if [ ! -f $LOGFILE ]; then
touch $LOGFILE
fi
if [ ! -f $LOGFILE ]; then
echo "[ERROR]: ${FUNCNAME[0]}: creating logfile [$LOGFILE]"
exit 1
fi
save_standard_outputs
exec 1>>${LOGFILE%.log}.log
exec 2>&1
OUTPUTS_REDIRECTED="true"
}
# "private" function used by save_standard_outputs()
function restore_standard_outputs {
if [ "$OUTPUTS_REDIRECTED" == "false" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: Cannot restore standard outputs because they have NOT been redirected"
exit 1;
fi
exec 1>&- #closes FD 1 (logfile)
exec 2>&- #closes FD 2 (logfile)
exec 2>&4 #restore stderr
exec 1>&3 #restore stdout
OUTPUTS_REDIRECTED="false"
}
Exemple d'utilisation à l'intérieur du script:
echo "this goes to stdout"
redirect_outputs_to_logfile /tmp/one.log
echo "this goes to logfile"
restore_standard_outputs
echo "this goes to stdout"
@ fernando-fabreti
Ajoutant à ce que vous avez fait, j'ai légèrement changé les fonctions et supprimé la fermeture & - et cela a fonctionné pour moi.
function saveStandardOutputs {
if [ "$OUTPUTS_REDIRECTED" == "false" ]; then
exec 3>&1
exec 4>&2
trap restoreStandardOutputs EXIT
else
echo "[ERROR]: ${FUNCNAME[0]}: Cannot save standard outputs because they have been redirected before"
exit 1;
fi
}
# Params: $1 => logfile to write to
function redirectOutputsToLogfile {
if [ "$OUTPUTS_REDIRECTED" == "false" ]; then
LOGFILE=$1
if [ -z "$LOGFILE" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: logfile empty [$LOGFILE]"
fi
if [ ! -f $LOGFILE ]; then
touch $LOGFILE
fi
if [ ! -f $LOGFILE ]; then
echo "[ERROR]: ${FUNCNAME[0]}: creating logfile [$LOGFILE]"
exit 1
fi
saveStandardOutputs
exec 1>>${LOGFILE}
exec 2>&1
OUTPUTS_REDIRECTED="true"
else
echo "[ERROR]: ${FUNCNAME[0]}: Cannot redirect standard outputs because they have been redirected before"
exit 1;
fi
}
function restoreStandardOutputs {
if [ "$OUTPUTS_REDIRECTED" == "true" ]; then
exec 1>&3 #restore stdout
exec 2>&4 #restore stderr
OUTPUTS_REDIRECTED="false"
fi
}
LOGFILE_NAME="tmp/one.log"
OUTPUTS_REDIRECTED="false"
echo "this goes to stdout"
redirectOutputsToLogfile $LOGFILE_NAME
echo "this goes to logfile"
echo "${LOGFILE_NAME}"
restoreStandardOutputs
echo "After restore this goes to stdout"
Dans les situations où vous envisagez d'utiliser des choses comme exec 2>&1
je trouve plus facile à lire si possible la réécriture de code en utilisant des fonctions bash comme ceci:
function myfunc(){
[...]
}
myfunc &>mylog.log