chuyển hướng COPY của thiết bị xuất chuẩn để đăng nhập tệp từ bên trong tập lệnh bash


235

Tôi biết cách chuyển hướng thiết bị xuất chuẩn sang tệp:

exec > foo.log
echo test

điều này sẽ đặt 'test' vào tệp foo.log.

Bây giờ tôi muốn chuyển hướng đầu ra vào tệp nhật ký VÀ giữ nó trên thiết bị xuất chuẩn

tức là nó có thể được thực hiện một cách tầm thường từ bên ngoài tập lệnh:

script | tee foo.log

nhưng tôi muốn khai báo nó trong chính kịch bản

Tôi đã thử

exec | tee foo.log

nhưng nó không hoạt động.


3
Câu hỏi của bạn rất kém. Khi bạn gọi 'exec> foo.log', thiết bị xuất chuẩn của tập lệnh tệp foo.log. Tôi nghĩ rằng bạn có nghĩa là bạn muốn đầu ra để đi đến foo.log và đến tty, vì sẽ foo.log được sẽ stdout.
William Pursell

những gì tôi muốn làm là sử dụng | trên 'exec'. đó sẽ là điều hoàn hảo đối với tôi, tức là "exec | tee foo.log", thật không may, bạn không thể sử dụng chuyển hướng đường ống trong cuộc gọi exec
Vitaly Kushner

Câu trả lời:


297
#!/usr/bin/env bash

# Redirect stdout ( > ) into a named pipe ( >() ) running "tee"
exec > >(tee -i logfile.txt)

# Without this, only stdout would be captured - i.e. your
# log file would not contain any error messages.
# SEE (and upvote) the answer by Adam Spiers, which keeps STDERR
# as a separate stream - I did not want to steal from him by simply
# adding his answer to mine.
exec 2>&1

echo "foo"
echo "bar" >&2

Lưu ý rằng đây là bash, không sh. Nếu bạn gọi tập lệnh với sh myscript.sh, bạn sẽ gặp lỗi dọc theo dòng syntax error near unexpected token '>'.

Nếu bạn đang làm việc với bẫy tín hiệu, bạn có thể muốn sử dụng tee -itùy chọn để tránh gián đoạn đầu ra nếu tín hiệu xảy ra. (Cảm ơn JamesThomasMoon1979 vì đã bình luận.)


Các công cụ thay đổi đầu ra của chúng tùy thuộc vào việc chúng ghi vào một đường ống hoặc một thiết bị đầu cuối ( lsví dụ sử dụng màu sắc và đầu ra được cột) sẽ phát hiện cấu trúc trên có nghĩa là chúng xuất ra một đường ống.

Có các tùy chọn để thực thi tô màu / cột (ví dụ ls -C --color=always). Lưu ý rằng điều này cũng sẽ dẫn đến các mã màu được ghi vào logfile, làm cho nó ít đọc hơn .


5
Tee trên hầu hết các hệ thống được đệm, do đó đầu ra có thể không đến cho đến khi tập lệnh kết thúc. Ngoài ra, vì tee này đang chạy trong một subshell, không phải là một tiến trình con, nên không thể chờ đợi để đồng bộ hóa đầu ra với quá trình gọi. Những gì bạn muốn là một phiên bản tee không có bộ đệm

14
@Barry: POSIX chỉ định rằng teekhông nên đệm đầu ra của nó. Nếu nó đệm trên hầu hết các hệ thống, nó bị hỏng trên hầu hết các hệ thống. Đó là một vấn đề của việc teethực hiện, không phải là giải pháp của tôi.
DevSolar

3
@Sebastian: execrất mạnh, nhưng cũng rất tham gia. Bạn có thể "sao lưu" thiết bị xuất chuẩn hiện tại sang một bộ lọc dữ liệu khác, sau đó khôi phục nó sau này. Google "bash exec guide", có rất nhiều thứ tiên tiến ngoài kia.
DevSolar

2
@AdamSpiers: Tôi cũng không chắc Barry là gì. Bash's execđược ghi nhận là không bắt đầu các quy trình mới, >(tee ...)là một sự thay thế quy trình / ống tiêu chuẩn có tên, và &trong quá trình chuyển hướng tất nhiên không liên quan gì đến việc tạo nền ...? :-)
DevSolar

11
Tôi đề nghị đi -itới tee. Nếu không, ngắt tín hiệu (bẫy) sẽ phá vỡ thiết bị xuất chuẩn trong tập lệnh chính. Ví dụ: nếu bạn có một trap 'echo foo' EXITvà sau đó nhấn ctrl+c, bạn sẽ không thấy " foo ". Vì vậy, tôi sẽ sửa đổi câu trả lời exec &> >(tee -ia file).
JamesThomasMoon1979

173

Câu trả lời được chấp nhận không bảo tồn STDERR như một mô tả tệp riêng biệt. Điều đó có nghĩa là

./script.sh >/dev/null

sẽ không xuất ra barterminal, chỉ tới logfile và

./script.sh 2>/dev/null

sẽ xuất cả hai foobarđến thiết bị đầu cuối. Rõ ràng đó không phải là hành vi mà một người dùng bình thường có thể mong đợi. Điều này có thể được khắc phục bằng cách sử dụng hai quy trình tee riêng biệt cả hai nối vào cùng một tệp nhật ký:

#!/bin/bash

# See (and upvote) the comment by JamesThomasMoon1979 
# explaining the use of the -i option to tee.
exec >  >(tee -ia foo.log)
exec 2> >(tee -ia foo.log >&2)

echo "foo"
echo "bar" >&2

(Lưu ý rằng ban đầu không cắt bớt tệp nhật ký - nếu bạn muốn hành vi đó bạn nên thêm

>foo.log

lên đầu tập lệnh.)

Đặc tả POSIX.1-2008tee(1) yêu cầu đầu ra không có bộ đệm, tức là không được đệm dòng, do đó, trong trường hợp này, có thể STDOUT và STDERR có thể kết thúc trên cùng một dòng foo.log; tuy nhiên điều đó cũng có thể xảy ra trên thiết bị đầu cuối, vì vậy tệp nhật ký sẽ là một sự phản ánh trung thực của những gì thể nhìn thấy trên thiết bị đầu cuối, nếu không phải là một tấm gương chính xác của nó. Nếu bạn muốn các dòng STDOUT được phân tách rõ ràng khỏi các dòng STDERR, hãy xem xét sử dụng hai tệp nhật ký, có thể có tiền tố tem ngày trên mỗi dòng để cho phép sắp xếp lại theo thời gian sau này.


Vì một số lý do, trong trường hợp của tôi, khi tập lệnh được thực thi từ lệnh gọi hệ thống c (chương trình c), hai quy trình phụ tee tiếp tục tồn tại ngay cả khi tập lệnh chính thoát. Vì vậy, tôi đã phải thêm các bẫy như thế này:exec > >(tee -a $LOG) trap "kill -9 $! 2>/dev/null" EXIT exec 2> >(tee -a $LOG >&2) trap "kill -9 $! 2>/dev/null" EXIT
alveko

15
Tôi đề nghị đi -itới tee. Nếu không, ngắt tín hiệu (bẫy) sẽ phá vỡ thiết bị xuất chuẩn trong tập lệnh. Ví dụ: nếu bạn trap 'echo foo' EXITvà sau đó nhấn ctrl+c, bạn sẽ không thấy " foo ". Vì vậy, tôi sẽ sửa đổi câu trả lời exec > >(tee -ia foo.log).
JamesThomasMoon1979

Tôi đã thực hiện một số kịch bản "có nguồn gốc" nhỏ dựa trên điều này. Có thể sử dụng chúng trong một tập lệnh như . loghoặc . log foo.log: sam.nipl.net/sh/log sam.nipl.net/sh/log-a
Sam Watkins

1
Vấn đề với phương pháp này là các tin nhắn sẽ STDOUTxuất hiện đầu tiên dưới dạng một đợt và sau đó các tin nhắn sẽ STDERRxuất hiện. Chúng không được xen kẽ như thường thấy.
CMCDragonkai

28

Giải pháp cho busybox, macOS bash và shell không bash

Câu trả lời được chấp nhận chắc chắn là sự lựa chọn tốt nhất cho bash. Tôi đang làm việc trong môi trường Busybox mà không có quyền truy cập vào bash và nó không hiểu exec > >(tee log.txt)cú pháp. Nó cũng không làmexec >$PIPE đúng, cố gắng tạo một tệp thông thường có cùng tên với đường ống được đặt tên, bị lỗi và bị treo.

Hy vọng rằng điều này sẽ hữu ích cho những người khác không có bash.

Ngoài ra, đối với bất kỳ ai sử dụng một đường ống có tên, nó an toàn rm $PIPE, bởi vì điều đó hủy liên kết đường ống khỏi VFS, nhưng các quy trình sử dụng nó vẫn duy trì số tham chiếu trên nó cho đến khi hoàn thành.

Lưu ý việc sử dụng $ * không nhất thiết phải an toàn.

#!/bin/sh

if [ "$SELF_LOGGING" != "1" ]
then
    # The parent process will enter this branch and set up logging

    # Create a named piped for logging the child's output
    PIPE=tmp.fifo
    mkfifo $PIPE

    # Launch the child process with stdout redirected to the named pipe
    SELF_LOGGING=1 sh $0 $* >$PIPE &

    # Save PID of child process
    PID=$!

    # Launch tee in a separate process
    tee logfile <$PIPE &

    # Unlink $PIPE because the parent process no longer needs it
    rm $PIPE    

    # Wait for child process, which is running the rest of this script
    wait $PID

    # Return the error code from the child process
    exit $?
fi

# The rest of the script goes here

Đây là giải pháp duy nhất tôi thấy cho đến nay hoạt động trên mac
Mike Baglio Jr.

19

Trong tệp tập lệnh của bạn, đặt tất cả các lệnh trong ngoặc đơn, như thế này:

(
echo start
ls -l
echo end
) | tee foo.log

5
về mặt giáo dục, cũng có thể sử dụng niềng răng ( {})
glenn jackman

vâng, tôi đã xem xét điều đó, nhưng đây không phải là chuyển hướng của thiết bị đầu ra shell hiện tại, nó là một mánh gian lận, bạn thực sự chạy một subshell và thực hiện chuyển hướng piper thường xuyên trên nó. công trình nghĩ. Tôi chia tay với điều này và giải pháp "tail -f foo.log &". sẽ chờ một chút để xem nếu có thể là một bề mặt tốt hơn. nếu không có lẽ sẽ ổn định;)
Vitaly Kushner

8
{} thực thi một danh sách trong môi trường shell hiện tại. () thực thi một danh sách trong môi trường subshell.

Chỉ trích. Cảm ơn bạn. Câu trả lời được chấp nhận ở đó không có tác dụng với tôi, cố gắng lên lịch cho một kịch bản chạy dưới MingW trên hệ thống Windows. Nó phàn nàn, tôi tin, về sự thay thế quá trình chưa được thực hiện. Câu trả lời này hoạt động tốt, sau khi thay đổi sau đây, để nắm bắt cả stderr và stdout: `` `-) | tee foo.log +) 2> & 1 | tee foo.log
Jon Carter

14

Cách dễ dàng để tạo một bản ghi bash script vào syslog. Đầu ra tập lệnh có sẵn cả thông qua /var/log/syslogvà thông qua thiết bị lỗi chuẩn. syslog sẽ thêm siêu dữ liệu hữu ích, bao gồm cả dấu thời gian.

Thêm dòng này ở trên cùng:

exec &> >(logger -t myscript -s)

Hoặc, gửi nhật ký đến một tệp riêng biệt:

exec &> >(ts |tee -a /tmp/myscript.output >&2 )

Điều này đòi hỏi moreutils(đối với tslệnh, có thêm dấu thời gian).


10

Sử dụng câu trả lời được chấp nhận, tập lệnh của tôi tiếp tục trở lại đặc biệt sớm (ngay sau 'exec >> (tee ...)') để phần còn lại của tập lệnh của tôi chạy trong nền. Khi tôi không thể làm cho giải pháp đó hoạt động theo cách của mình, tôi đã tìm một giải pháp khác / giải quyết vấn đề:

# Logging setup
logfile=mylogfile
mkfifo ${logfile}.pipe
tee < ${logfile}.pipe $logfile &
exec &> ${logfile}.pipe
rm ${logfile}.pipe

# Rest of my script

Điều này làm cho đầu ra từ tập lệnh đi từ quy trình, qua đường ống vào quy trình nền phụ của 'tee' để ghi lại mọi thứ vào đĩa và đến bản gốc của tập lệnh.

Lưu ý rằng 'exec &>' chuyển hướng cả stdout và stderr, chúng ta có thể chuyển hướng chúng một cách riêng biệt nếu chúng ta thích hoặc thay đổi thành 'exec>' nếu chúng ta chỉ muốn stdout.

Ngay cả khi đường ống được gỡ bỏ khỏi hệ thống tệp trong phần đầu của tập lệnh, nó sẽ tiếp tục hoạt động cho đến khi các quy trình kết thúc. Chúng tôi không thể tham chiếu nó bằng tên tệp sau rm-line.


Câu trả lời tương tự như ý tưởng thứ hai từ David Z . Có một cái nhìn vào ý kiến ​​của nó. +1 ;-)
olibre

Hoạt động tốt. Tôi không hiểu $logfilemột phần của tee < ${logfile}.pipe $logfile &. Cụ thể, tôi đã cố gắng thay đổi điều này để ghi lại các dòng nhật ký lệnh mở rộng đầy đủ (từ set -x) thành tệp trong khi chỉ hiển thị các dòng mà không dẫn '+' trong thiết bị xuất chuẩn bằng cách thay đổi thành (tee | grep -v '^+.*$') < ${logfile}.pipe $logfile &nhưng nhận được thông báo lỗi liên quan $logfile. Bạn có thể giải thích các teedòng chi tiết hơn một chút?
Chris Johnson

Tôi đã thử nghiệm điều này và có vẻ như câu trả lời này không bảo tồn STDERR (nó được hợp nhất với STDOUT), vì vậy nếu bạn dựa vào các luồng riêng biệt để phát hiện lỗi hoặc chuyển hướng khác, bạn nên xem câu trả lời của Adam.
HeroCC


1

Không thể nói rằng tôi cảm thấy thoải mái với bất kỳ giải pháp nào dựa trên exec. Tôi thích sử dụng tee trực tiếp, vì vậy tôi thực hiện lệnh gọi chính nó bằng tee khi được yêu cầu:

# my script: 

check_tee_output()
{
    # copy (append) stdout and stderr to log file if TEE is unset or true
    if [[ -z $TEE || "$TEE" == true ]]; then 
        echo '-------------------------------------------' >> log.txt
        echo '***' $(date) $0 $@ >> log.txt
        TEE=false $0 $@ 2>&1 | tee --append log.txt
        exit $?
    fi 
}

check_tee_output $@

rest of my script

Điều này cho phép bạn làm điều này:

your_script.sh args           # tee 
TEE=true your_script.sh args  # tee 
TEE=false your_script.sh args # don't tee
export TEE=false
your_script.sh args           # tee

Thay vào đó, bạn có thể tùy chỉnh điều này, ví dụ như đặt tee = false, thay vào đó, làm cho TEE giữ tệp nhật ký, v.v. Tôi đoán giải pháp này tương tự như jbarlow, nhưng đơn giản hơn, có thể tôi có những hạn chế mà tôi chưa gặp phải.


-1

Đây không phải là một giải pháp hoàn hảo, nhưng đây là một vài điều bạn có thể thử:

exec >foo.log
tail -f foo.log &
# rest of your script

hoặc là

PIPE=tmp.fifo
mkfifo $PIPE
exec >$PIPE
tee foo.log <$PIPE &
# rest of your script
rm $PIPE

Cái thứ hai sẽ để lại một tập tin đường ống ngồi xung quanh nếu có vấn đề xảy ra với tập lệnh của bạn, điều này có thể hoặc không thể là một vấn đề (nghĩa là có thể bạn có thể gửi rmnó trong shell cha sau đó).


1
Đuôi sẽ để lại một quá trình chạy phía sau trong tee script thứ 2 sẽ chặn hoặc bạn sẽ cần chạy nó với & trong trường hợp đó nó sẽ để lại tiến trình như trong phần 1.
Vitaly Kushner

@Vitaly: Rất tiếc, quên nền tee- Tôi đã chỉnh sửa. Như tôi đã nói, không phải là một giải pháp hoàn hảo, nhưng các quá trình nền sẽ bị giết khi lớp vỏ cha mẹ của chúng chấm dứt, vì vậy bạn không phải lo lắng về việc chúng sẽ ăn cắp tài nguyên mãi mãi.
David Z

1
Yike: chúng trông hấp dẫn, nhưng đầu ra của đuôi -f cũng sẽ đến foo.log. Bạn có thể khắc phục điều đó bằng cách chạy đuôi -f trước khi thực thi, nhưng đuôi vẫn còn chạy sau khi cha mẹ kết thúc. Bạn cần phải giết nó một cách rõ ràng, có thể trong một cái bẫy 0.
William Pursell

Yeap. Nếu tập lệnh được nền, nó để lại các tiến trình trên tất cả.
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.