2008-12-12 26 views
57

Tôi có một tập lệnh sẽ được người dùng không sử dụng kỹ thuật tương tác. Tập lệnh viết các cập nhật trạng thái cho STDOUT để người dùng có thể chắc chắn rằng tập lệnh đang chạy OK.Làm thế nào để có được cả hai STDOUT và STDERR để đi đến các thiết bị đầu cuối và một tập tin đăng nhập?

Tôi muốn cả STDOUT và STDERR được chuyển hướng đến thiết bị đầu cuối (để người dùng có thể thấy rằng tập lệnh đang hoạt động cũng như xem có sự cố hay không). Tôi cũng muốn cả hai luồng được chuyển hướng đến tệp nhật ký.

Tôi đã nhìn thấy một loạt các giải pháp trên mạng. Một số không làm việc và những người khác là phức tạp khủng khiếp. Tôi đã phát triển một giải pháp khả thi (mà tôi sẽ nhập như là một câu trả lời), nhưng nó là kludgy.

Giải pháp hoàn hảo sẽ là một dòng mã có thể được kết hợp vào đầu của bất kỳ tập lệnh nào gửi cả hai luồng tới cả thiết bị đầu cuối và tệp nhật ký.

CHỈNH SỬA: Chuyển hướng STDERR sang STDOUT và tạo kết quả cho công việc phát bóng, nhưng nó phụ thuộc vào người dùng nhớ chuyển hướng và dẫn đầu ra. Tôi muốn đăng nhập để được đánh lừa bằng chứng và tự động (đó là lý do tại sao tôi muốn để có thể nhúng các giải pháp vào kịch bản chính nó.)

+0

Đối với những người đọc khác: câu hỏi tương tự: http://stackoverflow.com/questions/692000/how-do-i-write-stderr-to-a-file-while-using-tee-with-a-pipe – pevik

Trả lời

92

Sử dụng "tee" để chuyển hướng đến một tập tin và màn hình. Tùy thuộc vào vỏ bạn sử dụng, trước tiên bạn phải chuyển hướng stderr stdout sử dụng

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

hoặc

./a.out |& tee output 

Trong csh, có một built-in lệnh gọi là "kịch bản" mà sẽ nắm bắt được tất cả những gì chuyển đến màn hình vào một tập tin. Bạn bắt đầu bằng cách gõ "script", sau đó làm bất cứ điều gì bạn muốn chụp, sau đó nhấn control-D để đóng tập tin kịch bản. Tôi không biết tương đương với sh/bash/ksh.

Ngoài ra, vì bây giờ bạn đã chỉ ra rằng đây là những kịch bản sh riêng bạn mà bạn có thể sửa đổi, bạn có thể làm chuyển hướng trong nội bộ bởi xung quanh toàn bộ kịch bản với niềng răng hoặc khung, như

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

Tôi không biết bạn có thể đặt các lệnh trong các kịch bản lệnh shell. Hấp dẫn. – Jamie

+1

Tôi cũng đánh giá cao phím tắt Bracket! Vì một số lý do, '2> & 1 | tee -a filename' đã không lưu stderr vào tập tin từ kịch bản của tôi, nhưng nó hoạt động tốt khi tôi sao chép lệnh và dán nó vào terminal! Tuy nhiên, thủ thuật của khung hoạt động tốt. –

+3

Lưu ý rằng sự khác biệt giữa stdout và stderr sẽ bị mất, khi tee in tất cả mọi thứ để stdout. – Flimm

1

Sử dụng tee chương trình và stderr dup để stdout.

program 2>&1 | tee > logfile 
2

Tôi đã tạo tập lệnh có tên "RunScript.sh". Nội dung của kịch bản này là:

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

Tôi gọi nó là như thế này:

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

này hoạt động, nhưng nó đòi hỏi kịch bản của ứng dụng được chạy qua một kịch bản bên ngoài. Đó là một chút kludgy.

+6

Bạn sẽ mất nhóm các đối số chứa khoảng trắng với ** $ 1 $ 2 $ 3 ... **, bạn nên sử dụng (w/quotes): ** "$ @" ** – NVRAM

3

để chuyển hướng stderr stdout thêm này theo lệnh của bạn: 2>&1 Đối outputting đến thiết bị đầu cuối và đăng nhập vào tập tin bạn nên sử dụng tee

Cả hai cùng nhau sẽ trông như thế này:

mycommand 2>&1 | tee mylogfile.log 

EDIT: Để nhúng vào tập lệnh của bạn, bạn sẽ làm như vậy.Vì vậy, kịch bản của bạn

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

sẽ kết thúc như

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

Lưu ý rằng sự khác biệt giữa stdout và stderr sẽ là bị mất, như tee in tất cả mọi thứ để stdout. – Flimm

0

Sử dụng lệnh script trong kịch bản của bạn (người đàn ông 1 script)

Tạo một shellscript wrapper (2 dòng) mà thiết lập kịch bản () và sau đó gọi thoát.

Phần 1: wrap.sh

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

Phần 2: realscript.sh

#!/bin/sh 
echo 'Output' 

Kết quả:

~: 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

Một năm sau, đây là một kịch bản bash cũ cho khai thác gỗ bất cứ điều gì. Ví dụ,
teelog make ... bản ghi để một tên đăng nhập được tạo ra (và xem các trick cho khai thác gỗ lồng make s quá.)

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

Tôi biết rằng đây là cách muộn để được thêm một bình luận nhưng tôi chỉ phải nói lời cảm ơn cho kịch bản này. Rất hữu ích và tài liệu tốt! – stephenmm

+0

Cảm ơn @stephenmm; đó là * không bao giờ * quá muộn để nói "hữu ích" hoặc "có thể được cải thiện". – denis

10

Tiếp cận một nửa thập kỷ sau đó ...

Tôi tin rằng đây là " giải pháp hoàn hảo "được OP tìm kiếm.

Dưới đây là một liner bạn có thể thêm vào phía trên cùng của kịch bản Bash của bạn:

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

Dưới đây là một kịch bản nhỏ thiệu việc sử dụng của nó:

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

(Lưu ý: Đây chỉ làm việc với Bash. Nó sẽ không làm việc với/bin/sh.)

Chuyển thể từ here; bản gốc đã không, từ những gì tôi có thể nói, bắt STDERR trong logfile. Đã sửa bằng ghi chú từ here.

+0

Lưu ý rằng sự khác biệt giữa stdout và stderr sẽ bị mất, khi tee in tất cả mọi thứ để stdout. – Flimm

+0

@Flimm stderr có thể được chuyển hướng đến quá trình tee khác nhau mà một lần nữa có thể được chuyển hướng đến stderr. – jarno

Các vấn đề liên quan