2008-12-12 8 views
57

Ich habe ein Skript, das interaktiv von nicht-technischen Benutzern ausgeführt wird. Das Skript schreibt Statusaktualisierungen in STDOUT, sodass der Benutzer sicher sein kann, dass das Skript ordnungsgemäß ausgeführt wird.Wie bekomme ich STDOUT und STDERR, um zum Terminal und einer Protokolldatei zu gehen?

Ich möchte beide STDOUT und STDERR zum Terminal umgeleitet (so dass der Benutzer sehen kann, dass das Skript funktioniert und sehen, ob es ein Problem gab). Ich möchte auch, dass beide Streams in eine Protokolldatei umgeleitet werden.

Ich habe eine Reihe von Lösungen im Netz gesehen. Manche funktionieren nicht und andere sind fürchterlich kompliziert. Ich habe eine praktikable Lösung entwickelt (die ich als Antwort eingeben werde), aber es ist kludgy.

Die perfekte Lösung wäre eine einzelne Codezeile, die in den Anfang eines Skripts integriert werden könnte, das beide Streams sowohl an das Terminal als auch an eine Protokolldatei sendet.

EDIT: Umleiten von STDERR zu STDOUT und Verrohrung das Ergebnis zu Tee funktioniert, aber es hängt davon ab, die Benutzer daran erinnern, Umleitung und Pipe die Ausgabe. Ich möchte, dass die Protokollierung narrensicher und automatisch ist (weshalb ich die Lösung in das Skript selbst einbetten möchte).

+0

Für andere Leser: ähnliche Frage: http://stackoverflow.com/questions/692000/how-do-i-write-stderr-to-a-file-while-using-tee-with-a-pipe – pevik

Antwort

92

Verwenden Sie "tee", um zu einer Datei und dem Bildschirm umzuleiten. In Abhängigkeit von der verwendeten Shell, müssen Sie zuerst stderr umleiten stdout mit

./a.out 2>&1 | tee output 

oder

./a.out |& tee output 

In csh gibt es einen eingebauten Befehl namens „Skript“, das alles erfassen wird, dass Geht auf den Bildschirm zu einer Datei. Du startest es, indem du "script" eintippst und dann tust, was auch immer du aufnehmen willst, dann drücke Strg-D, um die Skriptdatei zu schließen. Ich kenne keine Entsprechung für sh/bash/ksh.

Auch da Sie jetzt haben gezeigt, dass diese Ihre eigene sh-Skripte sind, die Sie ändern können, können Sie die Umleitung durch rund um das gesamte Skript mit Klammern oder Klammern intern tun, wie

#!/bin/sh 
    { 
    ... whatever you had in your script before 
    } 2>&1 | tee output.file 
+3

Ich wusste nicht, dass Sie Befehle in den Shell-Skripten einteilen können. Interessant. – Jamie

+1

Ich schätze auch die Bracket-Verknüpfung! Aus irgendeinem Grund "2> & 1 | tee -a filename 'speicherte stderr nicht in der Datei von meinem Skript, aber es funktionierte gut, als ich den Befehl kopierte und ihn in das Terminal einfügte! Der Bracket-Trick funktioniert jedoch gut. –

+3

Beachten Sie, dass die Unterscheidung zwischen stdout und stderr verloren geht, da tee alles auf stdout druckt. – Flimm

1

Verwenden Sie das T-Stück Programm und dup stderr zu stdout.

program 2>&1 | tee > logfile 
2

Ich habe ein Skript namens "RunScript.sh" erstellt. Der Inhalt dieses Skript ist:

${APP_HOME}/${1}.sh ${2} ${3} ${4} ${5} ${6} 2>&1 | tee -a ${APP_HOME}/${1}.log 

ich es so nennen:

./RunScript.sh ScriptToRun Param1 Param2 Param3 ... 

Dies funktioniert, aber es erfordert die Anwendung des Scripts über ein externes Skript ausgeführt werden. Es ist ein bisschen klatschig.

+6

Sie verlieren Gruppierung von Argumenten mit Leerzeichen mit ** $ 1 $ 2 $ 3 ... **, sollten Sie (mit Anführungszeichen) verwenden: ** "$ @" ** – NVRAM

3

die stderr umleiten diese auf Ihren Befehl an stdout anhängen: 2>&1 Denn für Terminal zur Ausgabe und Protokollierung in die Datei sollten Sie tee

zusammen wie folgt aussehen würde beide verwenden:

mycommand 2>&1 | tee mylogfile.log 

EDIT: Für die Einbettung in Ihr Skript würden Sie das gleiche tun.So Ihr Skript

#!/bin/sh 
whatever1 
whatever2 
... 
whatever3 

würde am Ende als

#!/bin/sh 
(whatever1 
whatever2 
... 
whatever3) 2>&1 | tee mylogfile.log 
+0

Beachten Sie, dass die Unterscheidung zwischen stdout und stderr sein wird verloren, als Tee druckt alles auf Stdout. – Flimm

0

Verwenden Sie den script Befehl in Ihrem Skript (man 1-Skript)

einen Wrapper Shell erstellen (2 Linien), das Skript einrichtet () und ruft dann den Ausgang auf.

Teil 1: wrap.sh

#!/bin/sh 
script -c './realscript.sh' 
exit 

Teil 2: realscript.sh

#!/bin/sh 
echo 'Output' 

Ergebnis:

~: sh wrap.sh 
Script started, file is typescript 
Output 
Script done, file is typescript 
~: cat typescript 
Script started on fr. 12. des. 2008 kl. 18.07 +0100 
Output 

Script done on fr. 12. des. 2008 kl. 18.07 +0100 
~: 
1

Ein Jahr später, hier ist ein alter für die Protokollierung Bash-Skript etwas. Zum Beispiel
teelog make ... Protokolle an einen generierten Protokollnamen (und sehen Sie die Trick für die Protokollierung verschachtelt make s auch.)

#!/bin/bash 
me=teelog 
Version="2008-10-9 oct denis-bz" 

Help() { 
cat <<! 

    $me anycommand args ... 

logs the output of "anycommand ..." as well as displaying it on the screen, 
by running 
    anycommand args ... 2>&1 | tee `day`-command-args.log 

That is, stdout and stderr go to both the screen, and to a log file. 
(The Unix "tee" command is named after "T" pipe fittings, 1 in -> 2 out; 
see http://en.wikipedia.org/wiki/Tee_(command)). 

The default log file name is made up from "command" and all the "args": 
    $me cmd -opt dir/file logs to `day`-cmd--opt-file.log . 
To log to xx.log instead, either export log=xx.log or 
    $me log=xx.log cmd ... 
If "logdir" is set, logs are put in that directory, which must exist. 
An old xx.log is moved to /tmp/\$USER-xx.log . 

The log file has a header like 
    # from: command args ... 
    # run: date pwd etc. 
to show what was run; see "From" in this file. 

Called as "Log" (ln -s $me Log), Log anycommand ... logs to a file: 
    command args ... > `day`-command-args.log 
and tees stderr to both the log file and the terminal -- bash only. 

Some commands that prompt for input from the console, such as a password, 
don't prompt if they "| tee"; you can only type ahead, carefully. 

To log all "make" s, including nested ones like 
    cd dir1; \$(MAKE) 
    cd dir2; \$(MAKE) 
    ... 
export MAKE="$me make" 

! 
    # See also: output logging in screen(1). 
    exit 1 
} 


#------------------------------------------------------------------------------- 
# bzutil.sh denisbz may2008 -- 

day() { # 30mar, 3mar 
    /bin/date +%e%h | tr '[A-Z]' '[a-z]' | tr -d ' ' 
} 

edate() { # 19 May 2008 15:56 
    echo `/bin/date "+%e %h %Y %H:%M"` 
} 

From() { # header # from: $* # run: date pwd ... 
    case `uname` in Darwin) 
     mac=" mac `sw_vers -productVersion`" 
    esac 
    cut -c -200 <<! 
${comment-#} from: [email protected] 
${comment-#} run: `edate` in $PWD `uname -n` $mac `arch` 

! 
    # mac $PWD is pwd -L not -P real 
} 

    # log name: day-args*.log, change this if you like -- 
logfilename() { 
    log=`day` 
    [[ $1 == "sudo" ]] && shift 
    for arg 
    do 
     log="$log-${arg##*/}" # basename 
     ((${#log} >= 100)) && break # max len 100 
    done 
      # no blanks etc in logfilename please, tr them to "-" 
    echo $logdir/` echo "$log".log | tr -C '.:+=[:alnum:]_\n' - ` 
} 

#------------------------------------------------------------------------------- 
case "$1" in 
-v* | --v*) 
    echo "$0 version: $Version" 
    exit 1 ;; 
"" | -*) 
    Help 
esac 

    # scan log= etc -- 
while [[ $1 == [a-zA-Z_]*=* ]]; do 
    export "$1" 
    shift 
done 

: ${logdir=.} 
[[ -w $logdir ]] || { 
    echo >&2 "error: $me: can't write in logdir $logdir" 
    exit 1 
    } 
: ${log=` logfilename "[email protected]" `} 
[[ -f $log ]] && 
    /bin/mv "$log" "/tmp/$USER-${log##*/}" 


case ${0##*/} in # basename 
log | Log) # both to log, stderr to caller's stderr too -- 
{ 
    From "[email protected]" 
    "[email protected]" 
} > $log 2> >(tee /dev/stderr) # bash only 
    # see http://wooledge.org:8000/BashFAQ 47, stderr to a pipe 
;; 

*) 
#------------------------------------------------------------------------------- 
{ 
    From "[email protected]" # header: from ... date pwd etc. 

    "[email protected]" 2>&1 # run the cmd with stderr and stdout both to the log 

} | tee $log 
    # mac tee buffers stdout ? 

esac 
+0

Ich weiß, dass es zu spät ist, einen Kommentar hinzuzufügen, aber ich musste mich nur für dieses Skript bedanken. Sehr nützlich und gut dokumentiert! – stephenmm

+0

Danke @stephenmm; es ist * nie * zu spät, um "nützlich" zu sagen oder "könnte verbessert werden". – denis

10

Annäherung an ein halbes Jahrzehnt später ...

Ich glaube, das ist die " perfekte Lösung "gesucht vom OP.

Hier ist ein Einzeiler Sie an die Spitze des Bash-Skript hinzufügen:

exec > >(tee -a $HOME/logfile) 2>&1 

Hier ist ein kleines Skript seine Verwendung demonstriert:

#!/usr/bin/env bash 

exec > >(tee -a $HOME/logfile) 2>&1 

# Test redirection of STDOUT 
echo test_stdout 

# Test redirection of STDERR 
ls test_stderr___this_file_does_not_exist 

(Hinweis: Dies ist nur mit Bash funktioniert. Es wird nicht arbeiten mit/bin/sh.)

Angepasst von here; Das Original hat STDERR in der Logdatei nicht abgefangen. Fixiert mit einer Notiz von here.

+0

Beachten Sie, dass die Unterscheidung zwischen stdout und stderr verloren gehen wird, da tee alles auf stdout druckt. – Flimm

+0

@Flimm stderr könnte zu verschiedenen Tee-Prozess umgeleitet werden, die wiederum an stderr umgeleitet werden könnte. – jarno