Domanda

Voglio reindirizzare sia stdout che stderr di un processo su un singolo file. Come posso farlo in Bash?

È stato utile?

Soluzione

Dai un'occhiata qui . Dovrebbe essere:

yourcommand &>filename

(reindirizza stdout e stderr sul nome file).

Altri suggerimenti

do_something 2>&1 | tee -a some_file

Questo reindirizzerà stderr su stdout e stdout su some_file e stampandolo su stdout.

Puoi reindirizzare stderr su stdout e stdout in un file:

some_command >file.log 2>&1 

Vedi http://tldp.org/LDP/abs/html/io -redirection.html

Questo formato è preferito rispetto al più popolare & amp; > formato che funziona solo in bash. Nella shell Bourne potrebbe essere interpretato come l'esecuzione del comando in background. Inoltre, il formato è più leggibile 2 (è STDERR) reindirizzato a 1 (STDOUT).

EDIT: modificato l'ordine come indicato nei commenti

# 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'"

Ora, l'eco semplice scriverà in $ LOG_FILE. Utile per demonizzare.

All'autore del post originale,

Dipende da cosa devi raggiungere. Se hai solo bisogno di reindirizzare dentro / fuori un comando chiamato dal tuo script, le risposte sono già fornite. Il mio riguarda il reindirizzamento di all'interno dello script corrente che influenza tutti i comandi / incorporati (include le forcelle) dopo lo snippet di codice citato.


Un'altra soluzione interessante riguarda il reindirizzamento a entrambi std-err / out AND al logger o al file di log in una sola volta che comporta la divisione di "un flusso". in due. Questa funzionalità è fornita dal comando 'tee' che può scrivere / aggiungere a più descrittori di file (file, socket, pipe, ecc.) Contemporaneamente: 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
}

Quindi, dall'inizio. Supponiamo di avere un terminale collegato a / dev / stdout (FD # 1) e / dev / stderr (FD # 2). In pratica, potrebbe essere una pipe, una presa o altro.

  • Crea FD n. 3 e n. 4 e punta alla stessa "posizione". come # 1 e # 2 rispettivamente. La modifica di FD # 1 non influirà da ora in poi su FD # 3. Ora, gli FD n. 3 e n. 4 indicano rispettivamente STDOUT e STDERR. Questi saranno usati come reale terminale STDOUT e STDERR.
  • 1 > > (...) reindirizza STDOUT per comandare tra parentesi
  • parens (sotto-shell) esegue la lettura 'tee' dallo STDOUT (pipe) di exec e reindirizza al comando 'logger' tramite un'altra pipe per sotto-shell in parentesi. Allo stesso tempo, copia lo stesso input su FD # 3 (terminale)
  • la seconda parte, molto simile, riguarda lo stesso trucco per STDERR e FD n. 2 e n. 4.

Il risultato dell'esecuzione di uno script con la riga sopra e inoltre questa:

echo "Will end up in STDOUT(terminal) and /var/log/messages"

... è il seguente:

$ ./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

Se vuoi vedere un'immagine più chiara, aggiungi queste 2 righe allo script:

ls -l /proc/self/fd/
ps xf
bash your_script.sh 1>file.log 2>&1

1 > file.log indica alla shell di inviare STDOUT al file file.log e 2 > & amp; 1 indica per reindirizzare STDERR (descrittore di file 2) su STDOUT (descrittore di file 1).

Nota: l'ordine è importante come sottolineato da liw.fi, 2 > & amp; 1 1 > file.log non funziona.

Curiosamente, funziona:

yourcommand &> filename

Ma questo dà un errore di sintassi:

yourcommand &>> filename
syntax error near unexpected token `>'

Devi usare:

yourcommand 1>> filename 2>&1

Risposta breve: Comando > nomefile 2 > & amp; 1 o Comando > > nomefile


Spiegazione:

Considera il seguente codice che stampa la parola "stdout" a stdout e la parola "stderror" a stderror.

$ (echo "stdout"; echo "stderror" >&2)
stdout
stderror

Nota che "& amp;" l'operatore dice a bash che 2 è un descrittore di file (che punta allo stderr) e non un nome di file. Se avessimo lasciato fuori "& amp;", questo comando avrebbe stampato stdout su stdout e creato un file chiamato " 2 " e scrivi stderror lì.

Sperimentando il codice sopra, puoi vedere di persona come funzionano gli operatori di reindirizzamento. Ad esempio, cambiando quale file quale dei due descrittori 1,2 , viene reindirizzato a / dev / null le seguenti due righe di codice eliminano tutto dallo stdout e tutto da rispettivamente stderror (stampa ciò che rimane).

$ (echo "stdout"; echo "stderror" >&2) 1>/dev/null
stderror
$ (echo "stdout"; echo "stderror" >&2) 2>/dev/null
stdout

Ora, possiamo spiegare perché la soluzione per cui il seguente codice non produce alcun output:

(echo "stdout"; echo "stderror" >&2) >/dev/null 2>&1

Per capirlo veramente, ti consiglio vivamente di leggere questo pagina web sulle tabelle dei descrittori di file . Supponendo che abbiate fatto quella lettura, possiamo procedere. Si noti che i processi di Bash da sinistra a destra; così Bash vede prima > / dev / null (che è lo stesso di 1 > / dev / null ) e imposta il descrittore di file 1 in modo che punti a / dev / null invece dello stdout. Fatto ciò, Bash si sposta a destra e vede 2 > & amp; 1 . In questo modo il descrittore di file 2 punta allo stesso file del descrittore di file 1 (e non al descrittore di file 1 stesso !!!! (vedere questa risorsa sui puntatori per maggiori informazioni)). Poiché il descrittore di file 1 punta a / dev / null e il descrittore di file 2 punta allo stesso file del descrittore di file 1, il descrittore di file 2 ora punta anche a / dev / null. Quindi entrambi i descrittori di file puntano a / dev / null, e questo è il motivo per cui non viene riprodotto alcun output.


Per verificare se capisci davvero il concetto, prova a indovinare l'output quando cambiamo l'ordine di reindirizzamento:

(echo "stdout"; echo "stderror" >&2)  2>&1 >/dev/null
  

stderror

Il ragionamento qui è che valutando da sinistra a destra, Bash vede 2 > & amp; 1, e quindi imposta il descrittore di file 2 in modo che punti allo stesso posto del descrittore di file 1, cioè stdout. Quindi imposta il descrittore di file 1 (ricorda che > / dev / null = 1 > / dev / null) in modo che punti a > / dev / null, eliminando così tutto ciò che normalmente verrebbe inviato allo standard out. Quindi tutto ciò che ci rimane è quello che non è stato inviato a stdout nella subshell (il codice tra parentesi) - cioè "stderror".   La cosa interessante da notare è che anche se 1 è solo un puntatore allo stdout, reindirizzando il puntatore 2 a 1 tramite 2 > & amp; 1 NON forma una catena di puntatori 2 - > 1 - > stdout. Se lo facesse, a seguito del reindirizzamento di 1 su / dev / null, il codice 2 > & amp; 1 > / dev / null darebbe la catena di puntatori 2 - > 1 - > / dev / null, e quindi il codice non genererebbe nulla, in contrasto con quanto visto sopra.


Infine, noterei che esiste un modo più semplice per farlo:

Dalla sezione 3.6.4 qui , vediamo che possiamo usare l'operatore & amp; > per reindirizzare sia stdout che stderr. Pertanto, per reindirizzare l'output stderr e stdout di qualsiasi comando su \ dev \ null (che elimina l'output), digitiamo semplicemente $ command & amp; > / Dev / null o nel caso del mio esempio:

$ (echo "stdout"; echo "stderror" >&2) &>/dev/null

Key takeaways:

  • I descrittori di file si comportano come puntatori (sebbene i descrittori di file non siano gli stessi dei puntatori di file)
  • Reindirizzamento di un fi
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" )

È relativo: scrivere stdOut & amp; stderr su syslog.

Funziona quasi, ma non da xinted; (

Volevo che una soluzione avesse l'output di stdout più stderr scritto in un file di registro e stderr ancora su console. Quindi avevo bisogno di duplicare l'output stderr tramite tee.

Questa è la soluzione che ho trovato:

command 3>&1 1>&2 2>&3 1>>logfile | tee -a logfile
  • Primo scambio stderr e stdout
  • quindi aggiungere lo stdout al file di registro
  • pipe stderr da inserire e aggiungerlo anche al file di registro

Per situazione, quando " tubazioni " è necessario è possibile utilizzare:

  

| & amp;

Ad esempio:

echo -ne "15\n100\n"|sort -c |& tee >sort_result.txt

o

TIMEFORMAT=%R;for i in `seq 1 20` ; do time kubectl get pods |grep node >>js.log  ; done |& sort -h

Queste soluzioni basate su bash possono convogliare separatamente STDOUT e STDERR (da STDERR di "sort -c" o da STDERR a "sort -h").

" " più facile; way (solo bash4): ls * 2 > & amp; - 1 > & amp; - .

Le seguenti funzioni possono essere utilizzate per automatizzare il processo di commutazione degli output tra stdout / stderr e un file di registro. Esempio di utilizzo all'interno dello script:

#!/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"
    }

Per tcsh, devo usare il seguente comando:

command >& file

Se usi il comando & amp; > file , darà " Comando null non valido " di errore.

@ fernando-fabreti

Aggiungendo a quello che hai fatto, ho leggermente modificato le funzioni e rimosso la chiusura di & amp; - e ha funzionato per me.

    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"

In situazioni in cui pensi di usare cose come exec 2 > & amp; 1 trovo più facile da leggere se possibile riscrivere il codice usando funzioni bash come questa:

function myfunc(){
  [...]
}

myfunc &>mylog.log
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top