Làm cách nào để tôi viết stderr vào một tệp trong khi sử dụng hệ thống tee tee với một đường ống?


542

Tôi biết làm thế nào để sử dụng teeđể viết ra ( STDOUT) của aaa.shđể bbb.out, trong khi vẫn hiển thị nó trong các thiết bị đầu cuối:

./aaa.sh | tee bbb.out

Làm thế nào bây giờ tôi cũng sẽ ghi STDERRvào một tệp có tên ccc.out, trong khi vẫn hiển thị nó?


2
Để làm rõ - bạn có muốn stderr đi đến màn hình cũng như tập tin không?
Charles Duffy

Tôi đã làm, tôi sẽ chỉnh sửa bài viết của tôi để làm rõ điều đó. Tôi tin rằng giải pháp của lhunath sẽ đủ. Cảm ơn sự giúp đỡ của tất cả!
jparanich

Câu trả lời:


785

Tôi giả sử bạn vẫn muốn thấy STDERR và STDOUT trên thiết bị đầu cuối. Bạn có thể tìm câu trả lời của Josh Kelley, nhưng tôi thấy việc giữ tailxung quanh trong nền tạo ra tệp nhật ký của bạn rất hack và lộn xộn. Lưu ý cách bạn cần giữ exra FD và dọn dẹp sau đó bằng cách giết nó và về mặt kỹ thuật nên thực hiện điều đó trong một trap '...' EXIT.

Có một cách tốt hơn để làm điều này và bạn đã phát hiện ra nó : tee.

Chỉ, thay vì chỉ sử dụng nó cho thiết bị xuất chuẩn của bạn, hãy có một tee cho thiết bị xuất chuẩn và một cho thiết bị xuất chuẩn. Làm thế nào bạn sẽ thực hiện điều này? Thay thế quá trình và chuyển hướng tập tin:

command > >(tee -a stdout.log) 2> >(tee -a stderr.log >&2)

Hãy chia nó ra và giải thích:

> >(..)

>(...)(thay thế quá trình) tạo ra một bộ xếp hình và cho phép teelắng nghe nó. Sau đó, nó sử dụng >(chuyển hướng tập tin) để chuyển hướng STDOUT của commandtới FIFO mà lần đầu tiên bạn teeđang nghe.

Điều tương tự cho lần thứ hai:

2> >(tee -a stderr.log >&2)

Chúng tôi sử dụng thay thế quy trình một lần nữa để tạo ra một teequy trình đọc từ STDIN và đưa nó vào stderr.log. teexuất lại đầu vào của nó trên STDOUT, nhưng vì đầu vào của nó là STDERR của chúng tôi, chúng tôi muốn chuyển hướng teeSTDOUT sang STDERR của chúng tôi một lần nữa. Sau đó, chúng tôi sử dụng chuyển hướng tệp để chuyển hướng commandSTDERR sang đầu vào của FIFO ( teeSTDIN).

Xem http://mywiki.wooledge.org/BashGuide/InputAndOutput

Thay thế quy trình là một trong những điều thực sự đáng yêu mà bạn nhận được như một phần thưởng khi chọn bashlàm vỏ của mình chứ không phải là sh(POSIX hoặc Bourne).


Trong sh, bạn phải làm mọi thứ bằng tay:

out="${TMPDIR:-/tmp}/out.$$" err="${TMPDIR:-/tmp}/err.$$"
mkfifo "$out" "$err"
trap 'rm "$out" "$err"' EXIT
tee -a stdout.log < "$out" &
tee -a stderr.log < "$err" >&2 &
command >"$out" 2>"$err"

5
Tôi đã thử điều này: $ echo "HANG" > >(tee stdout.log) 2> >(tee stderr.log >&2)hoạt động, nhưng chờ đầu vào. Có một lý do đơn giản tại sao điều này xảy ra?
Justin

1
@SillyFreak Tôi không hiểu bạn muốn làm gì hoặc bạn đang gặp vấn đề gì. kiểm tra tiếng vang; exit không tạo ra bất kỳ đầu ra nào trên thiết bị xuất chuẩn, vì vậy err sẽ vẫn trống.
lhunath

1
cảm ơn vì nhận xét đó; Tôi đã tìm ra lỗi logic của tôi là gì sau đó: khi được gọi là một vỏ tương tác, bash in một dấu nhắc lệnh và lặp lại lối ra đến stderr. Tuy nhiên, nếu stderr được chuyển hướng, bash bắt đầu là không tương tác theo mặc định; so sánh /bin/bash 2> err/bin/bash -i 2> err
Silly Freak

15
Và đối với những người "nhìn thấy là tin tưởng", một bài kiểm tra nhanh:(echo "Test Out";>&2 echo "Test Err") > >(tee stdout.log) 2> >(tee stderr.log >&2)
Matthew Wilcoxson

2
Wow . Câu trả lời hay: "Hãy chia nhỏ và giải thích" +1
jalanb

672

tại sao không đơn giản:

./aaa.sh 2>&1 | tee -a log

Điều này chỉ đơn giản là chuyển hướng stderrđến stdout, vì vậy tee vang cả để đăng nhập và sàng lọc. Có lẽ tôi đang thiếu một cái gì đó, bởi vì một số giải pháp khác có vẻ thực sự phức tạp.

Lưu ý: Vì bash phiên bản 4, bạn có thể sử dụng |&làm tên viết tắt cho 2>&1 |:

./aaa.sh |& tee -a log

85
Điều đó hoạt động tốt nếu bạn muốn cả stdout (kênh 1) và stderr (kênh 2) được ghi vào cùng một tệp (một tệp duy nhất chứa hỗn hợp của cả stdout và sterr). Giải pháp khác, phức tạp hơn cho phép bạn tách stdout và stderr thành 2 tệp khác nhau (stdout.log và stderr.log, tương ứng). Đôi khi điều đó quan trọng, đôi khi không.
Tyler Rick

19
Các giải pháp khác phức tạp hơn nhiều so với cần thiết trong nhiều trường hợp. Điều này làm việc hoàn hảo cho tôi.
dk vitamin

13
Vấn đề với phương pháp này là bạn mất mã thoát / mã trạng thái từ quy trình aaa.sh, điều này có thể quan trọng (ví dụ: khi sử dụng trong tệp tạo tệp). Bạn không có vấn đề này với câu trả lời được chấp nhận.
Stefaan

9
nếu bạn không nhớ stdout / stderr đã hợp nhất thì ./aaa.sh |& tee aaa.loghoạt động (trong bash).
jfs

5
@Stefaan Tôi tin rằng bạn có thể giữ trạng thái thoát nếu bạn thêm chuỗi lệnh set -o pipefailtheo sau ;hoặc &&nếu tôi không nhầm.
David

59

Điều này có thể hữu ích cho những người tìm thấy điều này thông qua google. Đơn giản chỉ cần bỏ qua ví dụ bạn muốn thử. Tất nhiên, hãy đổi tên các tập tin đầu ra.

#!/bin/bash

STATUSFILE=x.out
LOGFILE=x.log

### All output to screen
### Do nothing, this is the default


### All Output to one file, nothing to the screen
#exec > ${LOGFILE} 2>&1


### All output to one file and all output to the screen
#exec > >(tee ${LOGFILE}) 2>&1


### All output to one file, STDOUT to the screen
#exec > >(tee -a ${LOGFILE}) 2> >(tee -a ${LOGFILE} >/dev/null)


### All output to one file, STDERR to the screen
### Note you need both of these lines for this to work
#exec 3>&1
#exec > >(tee -a ${LOGFILE} >/dev/null) 2> >(tee -a ${LOGFILE} >&3)


### STDOUT to STATUSFILE, stderr to LOGFILE, nothing to the screen
#exec > ${STATUSFILE} 2>${LOGFILE}


### STDOUT to STATUSFILE, stderr to LOGFILE and all output to the screen
#exec > >(tee ${STATUSFILE}) 2> >(tee ${LOGFILE} >&2)


### STDOUT to STATUSFILE and screen, STDERR to LOGFILE
#exec > >(tee ${STATUSFILE}) 2>${LOGFILE}


### STDOUT to STATUSFILE, STDERR to LOGFILE and screen
#exec > ${STATUSFILE} 2> >(tee ${LOGFILE} >&2)


echo "This is a test"
ls -l sdgshgswogswghthb_this_file_will_not_exist_so_we_get_output_to_stderr_aronkjegralhfaff
ls -l ${0}

6
Không, và tôi đoán exec có thể sử dụng một số giải thích. exec >có nghĩa là, di chuyển mục tiêu của một mô tả tập tin đến một đích nhất định. Mặc định là 1, do đó, exec > /dev/nullchuyển đầu ra của thiết bị xuất chuẩn thành / dev / null từ bây giờ trong phiên này. Các mô tả tập tin hiện tại cho phiên này có thể được nhìn thấy bằng cách làm ls -l /dev/fd/. Thử nó! Sau đó, xem điều gì xảy ra khi bạn phát hành exec 2>/tmp/stderr.log.Ngoài ra, exec 3>&1có nghĩa là, tạo một bộ mô tả tệp mới với số 3 và chuyển hướng nó đến mục tiêu của bộ mô tả tệp 1. Trong ví dụ, mục tiêu là màn hình khi lệnh được ban hành.
trống

Ví dụ để hiển thị stdout và stderr cả trong màn hình và các tệp riêng biệt là tuyệt vời !!! Cảm ơn rất nhiều!
Marcello de Sales

21

Để chuyển hướng thiết bị xuất chuẩn sang tệp, hiển thị thiết bị xuất chuẩn ra màn hình và cũng lưu thiết bị xuất chuẩn vào tệp:

./aaa.sh 2> ccc.out | tee ./bbb.out

EDIT : Để hiển thị cả stderr và stdout trên màn hình và cũng lưu cả hai vào một tệp, bạn có thể sử dụng chuyển hướng I / O của bash :

#!/bin/bash

# Create a new file descriptor 4, pointed at the file
# which will receive stderr.
exec 4<>ccc.out

# Also print the contents of this file to screen.
tail -f ccc.out &

# Run the command; tee stdout as normal, and send stderr
# to our file descriptor 4.
./aaa.sh 2>&4 | tee bbb.out

# Clean up: Close file descriptor 4 and kill tail -f.
exec 4>&-
kill %1

1
Tôi hy vọng rằng người dùng muốn stderr đi đến bảng điều khiển của họ ngoài tệp, mặc dù điều đó không được chỉ định rõ ràng.
Charles Duffy

2
Tôi nên rõ ràng hơn, tôi cũng muốn stderr lên màn hình. Tôi vẫn thích giải pháp của Josh Kelley nhưng tìm thấy lhunath phù hợp với nhu cầu của tôi hơn. Cảm ơn các bạn!
jparanich

18

Nói cách khác, bạn muốn đặt stdout vào một bộ lọc ( tee bbb.out) và thiết bị xuất chuẩn vào một bộ lọc khác ( tee ccc.out). Không có cách tiêu chuẩn nào để chuyển bất cứ thứ gì ngoài stdout sang một lệnh khác, nhưng bạn có thể giải quyết vấn đề đó bằng cách đưa ra các mô tả tệp.

{ { ./aaa.sh | tee bbb.out; } 2>&1 1>&3 | tee ccc.out; } 3>&1 1>&2

Xem thêm Làm thế nào để grep luồng lỗi tiêu chuẩn (stderr)? Khi nào bạn sẽ sử dụng một mô tả tập tin bổ sung?

Trong bash (và ksh và zsh), nhưng không phải trong các vỏ POSIX khác như dấu gạch ngang, bạn có thể sử dụng thay thế quy trình :

./aaa.sh > >(tee bbb.out) 2> >(tee ccc.out)

Coi chừng rằng trong bash, lệnh này trả về ngay khi ./aaa.shkết thúc, ngay cả khi các teelệnh vẫn được thực thi (ksh và zsh chờ các quy trình con). Đây có thể là một vấn đề nếu bạn làm một cái gì đó như ./aaa.sh > >(tee bbb.out) 2> >(tee ccc.out); process_logs bbb.out ccc.out. Trong trường hợp đó, sử dụng mô tả tập tin tung hứng hoặc ksh / zsh thay thế.


4
Đây có vẻ như là câu trả lời duy nhất cho phép giữ các luồng stdout / stderr nguyên trạng (ví dụ: không hợp nhất chúng). Ngọt!
user1338062

Cách tiếp cận đơn giản nhất sh, hữu ích cho các công việc định kỳ, nơi không có sự thay thế quá trình.
Roger Dueck

13

Nếu sử dụng bash:

# Redirect standard out and standard error separately
% cmd >stdout-redirect 2>stderr-redirect

# Redirect standard error and out together
% cmd >stdout-redirect 2>&1

# Merge standard error with standard out and pipe
% cmd 2>&1 |cmd2

Tín dụng (không trả lời từ đỉnh đầu của tôi) ở đây: http://www.cygwin.com/ml/cygwin/2003-06/msg00772.html


5

Trong trường hợp của tôi, một tập lệnh đã chạy lệnh trong khi chuyển hướng cả thiết bị xuất chuẩn và thiết bị xuất chuẩn sang tệp, đại loại như:

cmd > log 2>&1

Tôi cần cập nhật nó để khi có lỗi, thực hiện một số hành động dựa trên các thông báo lỗi. Tất nhiên tôi có thể xóa bản sao 2>&1và chụp stderr khỏi tập lệnh, nhưng sau đó các thông báo lỗi sẽ không đi vào tệp nhật ký để tham khảo. Mặc dù câu trả lời được chấp nhận từ @lhunath được cho là giống nhau, nhưng nó lại chuyển hướng stdoutstderrđến các tệp khác nhau, đó không phải là điều tôi muốn, nhưng nó đã giúp tôi đưa ra giải pháp chính xác mà tôi cần:

(cmd 2> >(tee /dev/stderr)) > log

Với trên, đăng nhập sẽ có một bản sao của cả hai stdoutstderrvà tôi có thể chụp stderrtừ kịch bản của tôi mà không cần phải lo lắng về stdout.


4

Sau đây sẽ hoạt động cho KornShell (ksh) khi không có sự thay thế quá trình,

# create a combined(stdin and stdout) collector
exec 3 <> combined.log

# stream stderr instead of stdout to tee, while draining all stdout to the collector
./aaa.sh 2>&1 1>&3 | tee -a stderr.log 1>&3

# cleanup collector
exec 3>&-

Thủ thuật thực sự ở đây, là chuỗi trong 2>&1 1>&3đó trong trường hợp của chúng tôi chuyển hướng stderrđến stdoutvà chuyển hướng stdoutđến mô tả 3. Tại thời điểm này stderrstdoutchưa được kết hợp.

Trong thực tế, stderr(như stdin) được chuyển đến teenơi nó đăng nhập stderr.logvà cũng chuyển hướng đến mô tả 3.

Và mô tả 3đang đăng nhập nó combined.logmọi lúc. Vì vậy, combined.logchứa cả stdoutstderr.


Tiện lợi! Đáng tiếc đó là đánh giá thấp bởi vì đó là một lựa chọn tốt. Tôi có KSH, btw :)
runlevel0

2

Nếu bạn đang sử dụng zsh , bạn có thể sử dụng nhiều chuyển hướng, do đó bạn thậm chí không cần tee:

./cmd 1>&1 2>&2 1>out_file 2>err_file

Ở đây bạn chỉ cần chuyển hướng từng luồng đến chính nó tệp đích.


Ví dụ đầy đủ

% (echo "out"; echo "err">/dev/stderr) 1>&1 2>&2 1>/tmp/out_file 2>/tmp/err_file
out
err
% cat /tmp/out_file
out
% cat /tmp/err_file
err

Lưu ý rằng điều này yêu cầu MULTIOStùy chọn được đặt (là mặc định).

MULTIOS

Thực hiện tees hoặc s ẩn catkhi nhiều lần chuyển hướng được thử (xem Chuyển hướng ).

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.