Tôi muốn chuyển hướng cả stdout và stderr của một tiến trình sang một tệp duy nhất. Làm thế nào để tôi làm điều đó trong Bash?
Tôi muốn chuyển hướng cả stdout và stderr của một tiến trình sang một tệp duy nhất. Làm thế nào để tôi làm điều đó trong Bash?
Câu trả lời:
#!/bin/bash
chứ không phải #!/bin/sh
, vì trong yêu cầu bash.
do_something 2>&1 | tee -a some_file
Điều này sẽ chuyển hướng stderr sang thiết bị xuất chuẩn và thiết bị xuất chuẩn some_file
và in nó ra thiết bị xuất chuẩn.
do_something &>filename
không. +1.
Ambiguous output redirect.
ý tưởng nào tại sao không?
$?
không còn đề cập đến trạng thái thoát của do_something
, mà là trạng thái thoát của tee
.
Bạn có thể chuyển hướng stderr để stdout và stdout vào một tập tin:
some_command >file.log 2>&1
Xem http://tldp.org/LDP/abs/html/io-redirection.html
Định dạng này được ưa thích hơn định dạng &> phổ biến nhất chỉ hoạt động trong bash. Trong Bourne shell, nó có thể được hiểu là chạy lệnh trong nền. Ngoài ra định dạng dễ đọc hơn 2 (là STDERR) được chuyển hướng đến 1 (STDOUT).
EDIT: đã thay đổi thứ tự như được chỉ ra trong các ý kiến
# Close STDOUT file descriptor
exec 1<&-
# Close STDERR FD
exec 2<&-
# Open STDOUT as $LOG_FILE file for read and write.
exec 1<>$LOG_FILE
# Redirect STDERR to STDOUT
exec 2>&1
echo "This line will appear in $LOG_FILE, not 'on screen'"
Bây giờ, tiếng vang đơn giản sẽ ghi vào $ LOG_FILE. Hữu ích cho daemonizing.
Để tác giả của bài viết gốc,
Nó phụ thuộc vào những gì bạn cần phải đạt được. Nếu bạn chỉ cần chuyển hướng vào / ra một lệnh bạn gọi từ tập lệnh của mình, câu trả lời đã được đưa ra. Của tôi là về việc chuyển hướng trong tập lệnh hiện tại, điều này ảnh hưởng đến tất cả các lệnh / tích hợp (bao gồm các nhánh) sau đoạn mã được đề cập.
Một giải pháp tuyệt vời khác là về việc chuyển hướng đến cả std-err / out AND sang logger hoặc tệp nhật ký cùng một lúc liên quan đến việc chia "một luồng" thành hai. Chức năng này được cung cấp bởi lệnh 'tee' có thể ghi / nối vào một số mô tả tệp (tệp, ổ cắm, đường ống, v.v.) cùng một lúc: tee FILE1 FILE2 ...> (cmd1)> (cmd2) ...
exec 3>&1 4>&2 1> >(tee >(logger -i -t 'my_script_tag') >&3) 2> >(tee >(logger -i -t 'my_script_tag') >&4)
trap 'cleanup' INT QUIT TERM EXIT
get_pids_of_ppid() {
local ppid="$1"
RETVAL=''
local pids=`ps x -o pid,ppid | awk "\\$2 == \\"$ppid\\" { print \\$1 }"`
RETVAL="$pids"
}
# Needed to kill processes running in background
cleanup() {
local current_pid element
local pids=( "$$" )
running_pids=("${pids[@]}")
while :; do
current_pid="${running_pids[0]}"
[ -z "$current_pid" ] && break
running_pids=("${running_pids[@]:1}")
get_pids_of_ppid $current_pid
local new_pids="$RETVAL"
[ -z "$new_pids" ] && continue
for element in $new_pids; do
running_pids+=("$element")
pids=("$element" "${pids[@]}")
done
done
kill ${pids[@]} 2>/dev/null
}
Vì vậy, ngay từ đầu. Giả sử chúng ta có thiết bị đầu cuối được kết nối với / dev / stdout (FD # 1) và / dev / stderr (FD # 2). Trong thực tế, nó có thể là một đường ống, ổ cắm hoặc bất cứ điều gì.
Kết quả của việc chạy tập lệnh có dòng trên và thêm đoạn này:
echo "Will end up in STDOUT(terminal) and /var/log/messages"
...là như sau:
$ ./my_script
Will end up in STDOUT(terminal) and /var/log/messages
$ tail -n1 /var/log/messages
Sep 23 15:54:03 wks056 my_script_tag[11644]: Will end up in STDOUT(terminal) and /var/log/messages
Nếu bạn muốn xem hình ảnh rõ hơn, hãy thêm 2 dòng này vào tập lệnh:
ls -l /proc/self/fd/
ps xf
bash your_script.sh 1>file.log 2>&1
1>file.log
hướng dẫn shell gửi STDOUT đến tệp file.log
và 2>&1
yêu cầu nó chuyển hướng STDERR (mô tả tệp 2) sang STDOUT (mô tả tệp 1).
Lưu ý: Thứ tự quan trọng như liw.fi đã chỉ ra, 2>&1 1>file.log
không hoạt động.
Thật kỳ lạ, điều này hoạt động:
yourcommand &> filename
Nhưng điều này đưa ra một lỗi cú pháp:
yourcommand &>> filename
syntax error near unexpected token `>'
Bạn phải sử dụng:
yourcommand 1>> filename 2>&1
&>>
dường như hoạt động trên BASH 4:$ echo $BASH_VERSION 4.1.5(1)-release $ (echo to stdout; echo to stderr > /dev/stderr) &>> /dev/null
Câu trả lời ngắn: Command >filename 2>&1
hoặcCommand &>filename
Giải trình:
Hãy xem xét đoạn mã sau in từ "stdout" thành stdout và từ "stderror" thành stderror.
$ (echo "stdout"; echo "stderror" >&2)
stdout
stderror
Lưu ý rằng toán tử '&' cho bash biết rằng 2 là một mô tả tệp (trỏ đến stderr) chứ không phải tên tệp. Nếu chúng ta bỏ qua '&', lệnh này sẽ in stdout
ra thiết bị xuất chuẩn và tạo một tệp có tên "2" và ghi vào stderror
đó.
Bằng cách thử nghiệm mã ở trên, bạn có thể tự mình thấy chính xác cách thức các toán tử chuyển hướng hoạt động. Chẳng hạn, bằng cách thay đổi tập tin nào trong hai mô tả 1,2
, được chuyển hướng đến /dev/null
hai dòng mã sau sẽ xóa mọi thứ khỏi thiết bị xuất chuẩn và mọi thứ từ stderror (in những gì còn lại).
$ (echo "stdout"; echo "stderror" >&2) 1>/dev/null
stderror
$ (echo "stdout"; echo "stderror" >&2) 2>/dev/null
stdout
Bây giờ, chúng ta có thể giải thích tại sao giải pháp tại sao đoạn mã sau không tạo ra đầu ra:
(echo "stdout"; echo "stderror" >&2) >/dev/null 2>&1
Để thực sự hiểu điều này, tôi khuyên bạn nên đọc trang web này trên các bảng mô tả tệp . Giả sử bạn đã đọc xong, chúng ta có thể tiến hành. Lưu ý rằng Bash xử lý từ trái sang phải; do đó Bash nhìn thấy >/dev/null
đầu tiên (giống như 1>/dev/null
) và đặt bộ mô tả tệp 1 để trỏ đến / dev / null thay vì thiết bị xuất chuẩn. Làm xong việc này, Bash sau đó di chuyển sang phải và thấy 2>&1
. Cái này đặt bộ mô tả tệp 2 để trỏ đến cùng một tệp với bộ mô tả tệp 1 (và không phải chính bộ mô tả tệp 1 !!!! (xem tài nguyên này trên con trỏđể biết thêm thông tin)). Vì bộ mô tả tệp 1 điểm đến / dev / null và bộ mô tả tệp 2 điểm vào cùng một tệp với bộ mô tả tệp 1, bộ mô tả tệp 2 bây giờ cũng trỏ đến / dev / null. Do đó, cả hai bộ mô tả tệp đều trỏ đến / dev / null và đây là lý do tại sao không có đầu ra nào được hiển thị.
Để kiểm tra xem bạn có thực sự hiểu khái niệm này hay không, hãy thử đoán đầu ra khi chúng tôi chuyển đổi thứ tự chuyển hướng:
(echo "stdout"; echo "stderror" >&2) 2>&1 >/dev/null
stderror
Lý do ở đây là việc đánh giá từ trái sang phải, Bash thấy 2> & 1, và do đó đặt bộ mô tả tệp 2 để trỏ đến cùng một vị trí với bộ mô tả tệp 1, tức là thiết bị xuất chuẩn. Sau đó, nó đặt bộ mô tả tệp 1 (hãy nhớ rằng> / dev / null = 1> / dev / null) để trỏ đến> / dev / null, do đó xóa mọi thứ thường được gửi đến tiêu chuẩn. Do đó, tất cả những gì chúng ta còn lại là cái không được gửi đến thiết bị xuất chuẩn trong lớp con (mã trong ngoặc đơn) - tức là "stderror". Điều thú vị cần lưu ý là mặc dù 1 chỉ là một con trỏ đến thiết bị xuất chuẩn, việc chuyển hướng con trỏ 2 đến 1 thông qua 2>&1
KHÔNG tạo thành một chuỗi các con trỏ 2 -> 1 -> thiết bị xuất chuẩn. Nếu có, do chuyển hướng 1 đến / dev / null, mã2>&1 >/dev/null
sẽ cung cấp cho chuỗi con trỏ 2 -> 1 -> / dev / null và do đó mã sẽ không tạo ra gì, trái ngược với những gì chúng ta thấy ở trên.
Cuối cùng, tôi lưu ý rằng có một cách đơn giản hơn để làm điều này:
Từ mục 3.6.4 ở đây , chúng ta thấy rằng chúng ta có thể sử dụng toán tử &>
để chuyển hướng cả thiết bị xuất chuẩn và thiết bị xuất chuẩn. Do đó, để chuyển hướng cả đầu ra stderr và stdout của bất kỳ lệnh nào sang \dev\null
(mà xóa đầu ra), chúng ta chỉ cần gõ
$ command &> /dev/null
hoặc trong trường hợp ví dụ của tôi:
$ (echo "stdout"; echo "stderror" >&2) &>/dev/null
Những điểm chính:
2>&1 >/dev/null
là! = >/dev/null 2>&1
. Một cái tạo ra đầu ra và cái kia thì không!Cuối cùng hãy xem những tài nguyên tuyệt vời này:
Tài liệu Bash về chuyển hướng , giải thích các bảng mô tả tệp , giới thiệu về con trỏ
LOG_FACILITY="local7.notice"
LOG_TOPIC="my-prog-name"
LOG_TOPIC_OUT="$LOG_TOPIC-out[$$]"
LOG_TOPIC_ERR="$LOG_TOPIC-err[$$]"
exec 3>&1 > >(tee -a /dev/fd/3 | logger -p "$LOG_FACILITY" -t "$LOG_TOPIC_OUT" )
exec 2> >(logger -p "$LOG_FACILITY" -t "$LOG_TOPIC_ERR" )
Nó có liên quan: Viết stdOut & stderr vào syslog.
Nó gần như hoạt động, nhưng không phải từ xinted; (
Tôi muốn một giải pháp để có đầu ra từ thiết bị xuất chuẩn cộng với thiết bị xuất chuẩn được ghi vào tệp nhật ký và thiết bị xuất chuẩn vẫn còn trên bàn điều khiển. Vì vậy, tôi cần phải nhân đôi đầu ra stderr qua tee.
Đây là giải pháp tôi tìm thấy:
command 3>&1 1>&2 2>&3 1>>logfile | tee -a logfile
Đối với tình huống, khi "đường ống" là cần thiết, bạn có thể sử dụng:
&
Ví dụ:
echo -ne "15\n100\n"|sort -c |& tee >sort_result.txt
hoặc là
TIMEFORMAT=%R;for i in `seq 1 20` ; do time kubectl get pods |grep node >>js.log ; done |& sort -h
Các giải pháp dựa trên bash này có thể tách riêng STDOUT và STDERR (từ STDERR của "sort -c" hoặc từ STDERR sang "sort -h").
Các chức năng sau đây có thể được sử dụng để tự động hóa quá trình chuyển đổi đầu ra beetwen stdout / stderr và logfile.
#!/bin/bash
#set -x
# global vars
OUTPUTS_REDIRECTED="false"
LOGFILE=/dev/stdout
# "private" function used by redirect_outputs_to_logfile()
function save_standard_outputs {
if [ "$OUTPUTS_REDIRECTED" == "true" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: Cannot save standard outputs because they have been redirected before"
exit 1;
fi
exec 3>&1
exec 4>&2
trap restore_standard_outputs EXIT
}
# Params: $1 => logfile to write to
function redirect_outputs_to_logfile {
if [ "$OUTPUTS_REDIRECTED" == "true" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: Cannot redirect standard outputs because they have been redirected before"
exit 1;
fi
LOGFILE=$1
if [ -z "$LOGFILE" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: logfile empty [$LOGFILE]"
fi
if [ ! -f $LOGFILE ]; then
touch $LOGFILE
fi
if [ ! -f $LOGFILE ]; then
echo "[ERROR]: ${FUNCNAME[0]}: creating logfile [$LOGFILE]"
exit 1
fi
save_standard_outputs
exec 1>>${LOGFILE%.log}.log
exec 2>&1
OUTPUTS_REDIRECTED="true"
}
# "private" function used by save_standard_outputs()
function restore_standard_outputs {
if [ "$OUTPUTS_REDIRECTED" == "false" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: Cannot restore standard outputs because they have NOT been redirected"
exit 1;
fi
exec 1>&- #closes FD 1 (logfile)
exec 2>&- #closes FD 2 (logfile)
exec 2>&4 #restore stderr
exec 1>&3 #restore stdout
OUTPUTS_REDIRECTED="false"
}
Ví dụ về cách sử dụng bên trong tập lệnh:
echo "this goes to stdout"
redirect_outputs_to_logfile /tmp/one.log
echo "this goes to logfile"
restore_standard_outputs
echo "this goes to stdout"
@ fernando-fabreti
Thêm vào những gì bạn đã làm tôi đã thay đổi các chức năng một chút và loại bỏ & - đóng và nó hoạt động với tôi.
function saveStandardOutputs {
if [ "$OUTPUTS_REDIRECTED" == "false" ]; then
exec 3>&1
exec 4>&2
trap restoreStandardOutputs EXIT
else
echo "[ERROR]: ${FUNCNAME[0]}: Cannot save standard outputs because they have been redirected before"
exit 1;
fi
}
# Params: $1 => logfile to write to
function redirectOutputsToLogfile {
if [ "$OUTPUTS_REDIRECTED" == "false" ]; then
LOGFILE=$1
if [ -z "$LOGFILE" ]; then
echo "[ERROR]: ${FUNCNAME[0]}: logfile empty [$LOGFILE]"
fi
if [ ! -f $LOGFILE ]; then
touch $LOGFILE
fi
if [ ! -f $LOGFILE ]; then
echo "[ERROR]: ${FUNCNAME[0]}: creating logfile [$LOGFILE]"
exit 1
fi
saveStandardOutputs
exec 1>>${LOGFILE}
exec 2>&1
OUTPUTS_REDIRECTED="true"
else
echo "[ERROR]: ${FUNCNAME[0]}: Cannot redirect standard outputs because they have been redirected before"
exit 1;
fi
}
function restoreStandardOutputs {
if [ "$OUTPUTS_REDIRECTED" == "true" ]; then
exec 1>&3 #restore stdout
exec 2>&4 #restore stderr
OUTPUTS_REDIRECTED="false"
fi
}
LOGFILE_NAME="tmp/one.log"
OUTPUTS_REDIRECTED="false"
echo "this goes to stdout"
redirectOutputsToLogfile $LOGFILE_NAME
echo "this goes to logfile"
echo "${LOGFILE_NAME}"
restoreStandardOutputs
echo "After restore this goes to stdout"
Trong các tình huống khi bạn cân nhắc sử dụng những thứ như exec 2>&1
tôi thấy dễ đọc hơn nếu có thể viết lại mã bằng các hàm bash như thế này:
function myfunc(){
[...]
}
myfunc &>mylog.log