Je veux rediriger à la fois stdout et stderr d'un processus vers un fichier unique. Comment je fais ça dans Bash?
Jetez un oeil ici . Devrait être:
yourcommand &>filename
(redirige à la fois stdout
et stderr
vers le nom du fichier).
do_something 2>&1 | tee -a some_file
Cela va rediriger stderr vers stdout et stdout vers some_file
et l’imprimer sur stdout.
Vous pouvez rediriger stderr vers stdout et le 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 que dans bash. Dans Bourne Shell, cela peut être interprété comme exécutant la commande en arrière-plan. Aussi le format est plus lisible 2 (est STDERR) redirigé vers 1 (STDOUT).
EDIT: modification de 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, un simple écho va écrire dans $ LOG_FILE. Utile pour démoniser.
À l'auteur du message original,
Cela dépend de ce que vous devez accomplir. Si vous avez juste besoin de rediriger une commande que vous appelez à partir de votre script, les réponses sont déjà données. Mine concerne la redirection au sein du script en cours qui affecte toutes les commandes/fonctions intégrées (y compris les fourches) après le fragment de code mentionné.
Une autre solution intéressante consiste à rediriger simultanément std-err/out ET vers un enregistreur ou un fichier journal, ce qui implique de scinder "un flux" en deux. Cette fonctionnalité est fournie par la commande 'tee' qui peut écrire/ajouter plusieurs descripteurs de fichier (fichiers, sockets, pipes, 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, cela pourrait être un tuyau, une prise ou autre chose.
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 2>&1
lui dit de rediriger STDERR (descripteur de fichier 2) vers STDOUT (descripteur de fichier 1).
Remarque: L'ordre est important, 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
Réponse courte: Command >filename 2>&1
ou Command &>filename
Explication:
Considérez le code suivant qui affiche 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 omettions le '&', cette commande afficherait stdout
sur la sortie standard, créerait un fichier nommé "2" et y écrirait stderror
.
En testant le code ci-dessus, vous pouvez voir par vous-même exactement comment fonctionnent les opérateurs de redirection. Par exemple, en changeant quel fichier lequel des deux descripteurs 1,2
est redirigé vers /dev/null
, les deux lignes de code suivantes suppriment tout ce qui se trouve dans la sortie standard et tout ce qui provient de stderror (imprimer 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 ceci page Web sur les tableaux de descripteurs de fichiers . En supposant que vous ayez fait cette lecture, nous pouvons procéder. Notez que Bash traite de gauche à droite; Bash voit donc d'abord >/dev/null
(qui est identique à 1>/dev/null
) et définit le descripteur de fichier 1 pour qu'il pointe vers/dev/null au lieu de la sortie standard. Ceci fait, Bash se déplace alors vers la droite et voit 2>&1
. Ceci définit le descripteur de fichier 2 pour qu'il pointe vers le même fichier que le descripteur de fichier 1 (et non sur le descripteur de fichier 1 lui-même !!!! (voir - cette ressource sur les pointeurs pour plus d’informations)). Comme le descripteur de fichier 1 pointe sur/dev/null et que le descripteur de fichier 2 pointe sur le même fichier que le descripteur de fichier 1, le descripteur de fichier 2 pointe également sur/dev/null. Ainsi, les deux descripteurs de fichier pointent vers/dev/null, raison pour laquelle aucune sortie n'est rendue.
Pour tester si vous comprenez vraiment le concept, essayez de deviner le résultat lorsque nous inversons l'ordre de redirection:
(echo "stdout"; echo "stderror" >&2) 2>&1 >/dev/null
stderror
Le raisonnement est le suivant: en évaluant de gauche à droite, Bash voit 2> & 1, et définit ainsi le descripteur de fichier 2 pour qu'il pointe au même endroit que le descripteur de fichier 1, c.-à-d. Stdout. Il définit ensuite le descripteur de fichier 1 (rappelez-vous que>/dev/null = 1>/dev/null) pour qu'il pointe vers>/dev/null, supprimant ainsi tout ce qui serait normalement envoyé à la sortie standard. Ainsi, il ne nous reste que 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 est juste un pointeur sur 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, à la suite de la redirection de 1 vers/dev/null, le code 2>&1 >/dev/null
donnerait à la chaîne de pointeur 2 -> 1 ->/dev/null, et le code ne générerait rien, contrairement à ce que nous avons vu ci-dessus.
Enfin, je noterais qu’il existe un moyen plus simple de procéder:
À partir de la section 3.6.4 ici , nous voyons que nous pouvons utiliser l'opérateur &>
pour rediriger stdout et stderr. Ainsi, pour rediriger les sorties stderr et stdout de toute commande vers \dev\null
(ce qui supprime la sortie), il suffit de taper $ 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
est! = >/dev/null 2>&1
. L'un génère une sortie et l'autre pas!Enfin, jetez un coup d’œil à ces excellentes ressources:
Documentation Bash sur la redirection , Explication des tables de descripteur de fichier , 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" )
C'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. Il me fallait donc dupliquer la sortie stderr via un tee.
C'est la solution que j'ai trouvée:
command 3>&1 1>&2 2>&3 1>>logfile | tee -a logfile
Pour la situation, quand "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 séparément STDOUT et STDERR (de STDERR à "sort -c" ou de STDERR à "sort -h").
Dans des situations où vous envisagez d'utiliser des éléments tels que exec 2>&1
, je trouve plus facile à lire si possible en réécrivant le code à l'aide de fonctions bash telles que celle-ci:
function myfunc(){
[...]
}
myfunc &>mylog.log
Manière "la plus facile" (uniquement pour bash4): ls * 2>&- 1>&-
.
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"
Pour tcsh, je dois utiliser la commande suivante:
command >& file
Si vous utilisez command &> file
, vous obtiendrez l'erreur "Invalid null command".
@ fernando-fabreti
En ajoutant à ce que vous avez fait j'ai légèrement modifié les fonctions et supprimé le & - 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"