Chỉ hiển thị stderr trên màn hình nhưng ghi cả stdout và stderr vào tệp


24

Làm thế nào tôi có thể sử dụng phép thuật BASH để đạt được điều này?
Tôi muốn chỉ nhìn thấy đầu ra stderr trên màn hình,
nhưng tôi muốn cả stdout và stderr được ghi vào một tệp.

Làm rõ: Tôi muốn cả stdout và stderr kết thúc trong cùng một tệp. Theo thứ tự chúng xảy ra.
Thật không may, không có câu trả lời dưới đây làm điều này.


Câu trả lời:


14

Ngay cả khi không có bất kỳ sự chuyển hướng nào, hoặc không có gì nhưng >logfile 2>&1bạn không được đảm bảo sẽ thấy đầu ra theo thứ tự tạo.

Đối với người mới bắt đầu, thiết bị xuất chuẩn từ ứng dụng sẽ được đệm dòng (đến tty) hoặc được đệm (với đường ống) nhưng thiết bị xuất chuẩn không bị chặn, do đó mối quan hệ giữa thứ tự đầu ra bị phá vỡ khi có liên quan đến đầu đọc. Các giai đoạn tiếp theo trong bất kỳ đường ống nào bạn có thể pha chế sẽ không có quyền truy cập theo thứ tự một cách xác định vào hai luồng (chúng là những điều về mặt khái niệm xảy ra song song và bạn luôn phải tuân theo lịch trình - nếu đến lúc người đọc của bạn nhận được một lát được viết cho cả hai ống, bạn không thể biết cái nào đến trước).

"[T] anh ấy ra lệnh cho chúng xảy ra" chỉ thực sự được biết đến với ứng dụng. Thứ tự đầu ra trên thiết bị xuất chuẩn / thiết bị xuất chuẩn là một vấn đề nổi tiếng - cổ điển, có lẽ -.


7

Khi bạn sử dụng cấu trúc: 1>stdout.log 2>&1cả stderrstdout đều được chuyển hướng đến tệp vì chuyển hướng xuất chuẩn được thiết lập trước khi chuyển hướng stderr .

Nếu bạn đảo ngược thứ tự, bạn có thể nhận được thiết bị xuất chuẩn chuyển hướng đến một tệp và sau đó sao chép tiêu chuẩn vào thiết bị xuất chuẩn để bạn có thể chuyển nó sang tee.

$ cat test
#!/bin/sh
echo OUT! >&1
echo ERR! >&2

$ ./test 2>&1 1>stdout.log | tee stderr.log
ERR!

$ cat stdout.log
OUT!

$ cat stderr.log
ERR!

Điều đó không cho phép bạn đăng nhập hai luồng vào cùng một tệp.
artistoex

1
@artistoex: Bạn chỉ có thể sử dụng tee -avới cùng một tệp được sử dụng 1>để tổng hợp cả hai đầu ra trong cùng một tệp. Một cái gì đó như:./test 2>&1 1>out+err.log | tee -a out+err.log
mmoya

Tôi không biết tại sao, nhưng điều này không làm việc với wget -O - www.google.detôi. (so sánh với giải pháp bên dưới)
artistoex

4
Việc sử dụng tee -asẽ không hoạt động đáng tin cậy để có cùng một đầu ra trên bảng điều khiển nếu không có gì được chuyển hướng. Đầu ra đi qua teecó thể bị trễ một chút so với đầu ra trực tiếp từ lệnh và do đó có thể xuất hiện sau trong nhật ký.
Gilles 'SO- ngừng trở nên xấu xa'

Điều này không làm việc cho tôi, out + err.log trống. Và vâng, tôi muốn stderror và tiêu chuẩn trong cùng một tệp
Andreas

7

Tôi đã có thể thực hiện điều này bằng cách đặt dòng sau ở đầu tập lệnh bash:

exec 1>>log 2> >(tee -a log >&2)

Điều này sẽ chuyển hướng stdout sang tệp log( 1>>log), sau đó tee stderr đến tệp log( 2> >(tee -a log) và chuyển nó trở lại stderr ( >&2). Bằng cách này, tôi nhận được một tệp duy nhất log, hiển thị cả stdout và stderr theo thứ tự, và stderr cũng hiển thị trên màn hình như bình thường.

Điều hấp dẫn là nó dường như chỉ hoạt động khi tôi thêm vào tập tin. Nếu tôi không nối thêm, có vẻ như hai lần chuyển hướng bịt kín lẫn nhau và tôi chỉ nhận được bất kỳ kết quả đầu ra nào.


Bất kỳ cách nào để có được điều này để "sửa đổi" các dòng STDERR để bao gồm một chuỗi tùy chỉnh để bạn có thể dễ dàng grep trên nó?
IMTheNachoMan

1

Bạn muốn nhân đôi luồng lỗi để nó xuất hiện cả trên bàn điều khiển và trong tệp nhật ký. Công cụ cho điều đó là tee, và tất cả những gì bạn cần làm là áp dụng nó vào luồng lỗi. Thật không may, không có cấu trúc shell tiêu chuẩn để chuyển luồng lỗi của lệnh sang một lệnh khác, do đó cần phải sắp xếp lại mô tả tệp nhỏ.

{ { echo out; echo err 1>&2; } 2>&1 >&3 | tee /dev/tty; } >log 3>&1
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^   ^^^^^^^^^^^^    ^^^^^^^^^
  command produces output      stdout3                    log
  command produces error       stderr1   dup to terminal  log

Điều này cũng không hoàn toàn phù hợp với tôi, trước tiên tôi nhận được các dòng stdout và sau đó là tất cả các dòng stderr trong tệp 'log'. ví dụ: {{echo out; echo err 1> & 2; echo out2; echo err2 1> & 2; } 2> & 1> & 3 | tee / dev / tty; }> đăng nhập 3> & 1
Andreas

1
@Andreas: Oh, duh, cũng teegiới thiệu một sự chậm trễ ở đây. Tôi không nghĩ có một giải pháp vỏ nguyên chất. Trong thực tế, tôi tự hỏi liệu có một giải pháp mà không sửa đổi ứng dụng theo một cách nào đó.
Gilles 'SO- ngừng trở nên xấu xa'

1

Để viết stderr để sàng lọc và ghi BÓNG stderr và stdout vào một tệp - VÀ có các dòng cho stderr và stdout xuất hiện theo cùng một trình tự nếu cả hai được ghi vào màn hình:

Hóa ra đó là một vấn đề khó khăn, đặc biệt là phần có "trình tự tương tự" mà bạn mong đợi nếu bạn chỉ đơn giản viết chúng lên màn hình. Nói một cách đơn giản: Viết từng tệp vào tệp riêng của mình, thực hiện một số phép thuật xử lý nền để đánh dấu từng dòng (trong mỗi tệp) với thời gian chính xác mà dòng được tạo ra, sau đó: "tail --follow" tệp stderr vào màn hình , nhưng để xem cả hai "stderr" và "stdout" cùng nhau - theo thứ tự - sắp xếp hai tệp (với các dấu thời gian chính xác trên mỗi dòng) với nhau.

Mã số:

# Set the location of output and the "first name" of the log file(s)
pth=$HOME
ffn=my_log_filename_with_no_extension
date >>$pth/$ffn.out
date >>$pth/$ffn.err
# Start background processes to handle 2 files, by rewriting each one line-by-line as each line is added, putting a label at front of line
tail -f $pth/$ffn.out | perl -nle 'use Time::HiRes qw(time);print substr(time."0000",0,16)."|1|".$_' >>$pth/$ffn.out.txt &
tail -f $pth/$ffn.err | perl -nle 'use Time::HiRes qw(time);print substr(time."0000",0,16)."|2|".$_' >>$pth/$ffn.err.txt &
sleep 1
# Remember the process id of each of 2 background processes
export idout=`ps -ef | grep "tail -f $pth/$ffn.out" | grep -v 'grep' | perl -pe 's/\s+/\t/g' | cut -f2`
export iderr=`ps -ef | grep "tail -f $pth/$ffn.err" | grep -v 'grep' | perl -pe 's/\s+/\t/g' | cut -f2`
# Run the command, sending stdout to one file, and stderr to a 2nd file
bash mycommand.sh 1>>$pth/$ffn.out 2>>$pth/$ffn.err
# Remember the exit code of the command
myexit=$?
# Kill the two background processes
ps -ef | perl -lne 'print if m/^\S+\s+$ENV{"idout"}/'
echo kill $idout
kill $idout
ps -ef | perl -lne 'print if m/^\S+\s+$ENV{"iderr"}/'
echo kill $iderr
kill $iderr
date
echo "Exit code: $myexit for '$listname', list item# '$ix', bookcode '$bookcode'"

Vâng, điều này có vẻ phức tạp, và nó dẫn đến 4 tệp đầu ra (2 trong số đó bạn có thể xóa). Có vẻ như đây là một vấn đề khó giải quyết, vì vậy phải mất một số cơ chế.

Cuối cùng, để xem kết quả từ Bout stdout và stderr trong chuỗi bạn mong đợi, hãy chạy nó:

cat $pth/$ffn.out.txt $pth/$ffn.err.txt | sort

Lý do duy nhất mà chuỗi ít nhất rất gần với những gì nó đã có cả thiết bị xuất chuẩn và thiết bị xuất chuẩn chỉ đơn giản là đi đến màn hình là: Mỗi dòng được đánh dấu bằng dấu thời gian xuống đến mili giây.

Để xem stderr trên màn hình khi quá trình diễn ra, hãy sử dụng:

tail -f $pth/$ffn.out

Hy vọng rằng sẽ giúp được ai đó, người đã đến đây rất lâu sau khi câu hỏi ban đầu được hỏi.


+1 cho nỗ lực tuyệt đối, tôi đã có một ý tưởng tương tự (dấu thời gian cả hai luồng qua perl) nhưng đang vật lộn để thực hiện nó. Tôi sẽ điều tra xem nó có hiệu quả với tôi không (ví dụ, nếu nó thực sự giữ trật tự đầu ra, vì các giải pháp khác ở đây thay đổi mọi thứ một chút do sự không đồng bộ giữa việc gắn thẻ stderr và stdout)
Olivier Dulac

0

Hãy flà lệnh bạn muốn được thực thi, sau đó

( exec 3>/tmp/log; f 2>&1 1>&3 |tee >(cat)>&3 )

sẽ cung cấp cho bạn những gì bạn muốn. Ví dụ wget -O - www.google.desẽ trông như thế này:

( exec 3>/tmp/googlelog; wget -O - www.google.de 2>&1 1>&3 |tee >(cat)>&3 )

1
Điều này gần như làm việc với tôi, nhưng trong tệp nhật ký, trước tiên tôi nhận được tất cả các dòng đầu ra, và sau đó là tất cả các dòng stderror. Tôi muốn chúng được xen kẽ theo thứ tự tự nhiên khi chúng xảy ra.
Andreas

0

Đưa ra những gì tôi đã đọc ở đây, giải pháp duy nhất tôi có thể thấy là trả trước mỗi dòng cho dù là STERE hay STERR với dấu thời gian / dấu thời gian. ngày +% s sẽ chuyển sang giây, nhưng đó là chậm để duy trì trật tự. Ngoài ra nhật ký bằng cách nào đó sẽ phải được sắp xếp. Nhiều việc hơn tôi có khả năng.


0

Tôi đã vật lộn với yêu cầu chính xác này, và cuối cùng không thể tìm thấy một giải pháp đơn giản. Thay vào đó, cuối cùng tôi đã làm điều này:

TMPFILE=/tmp/$$.log
myCommand > $TMPFILE 2>&1
[ $? != 0 ] && cat $TMPFILE
date >> myCommand.log
cat $TMPFILE >> myCommand.log
rm -f $TMPFILE

Nó nối stdout và stderr, theo thứ tự xen kẽ thích hợp vào tệp nhật ký. Và nếu có bất kỳ lỗi nào xảy ra (như được xác định bởi trạng thái thoát của lệnh), nó sẽ gửi toàn bộ đầu ra (stdout stderr) đến stdout, một lần nữa theo thứ tự xen kẽ thích hợp. Tôi thấy đây là một sự thỏa hiệp hợp lý cho nhu cầu của tôi. Nếu bạn muốn một tệp nhật ký chạy một lần thay vì tệp đa chạy đang phát triển, thì nó thậm chí còn đơn giản hơn:

myCommand > myCommand.log 2>&1
[ $? != 0 ] && cat myCommand.log

0

stderr để thay thế lệnh tee -avà stdout nối vào cùng một tệp:

./script.sh 2> >(tee -a outputfile) >>outputfile

và lưu ý: quá đảm bảo đúng thứ tự (nhưng không có stderr-show), có lệnh 'unbuffer' từ các công cụ 'mong đợi', mô phỏng tty và giữ stdout / err theo thứ tự như nó sẽ hiển thị trên thiết bị đầu cuối:

unbuffer ./script.sh > outputfile
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.