ฉันต้องการเปลี่ยนเส้นทางทั้ง stdout และ stderr ของกระบวนการเป็นไฟล์เดียว ฉันจะทำเช่นนั้นใน Bash ได้อย่างไร
ฉันต้องการเปลี่ยนเส้นทางทั้ง stdout และ stderr ของกระบวนการเป็นไฟล์เดียว ฉันจะทำเช่นนั้นใน Bash ได้อย่างไร
คำตอบ:
#!/bin/bash
แทนที่จะ#!/bin/sh
ต้องใช้ทุบตี
do_something 2>&1 | tee -a some_file
นี่จะเปลี่ยนเส้นทาง stderr ไปยัง stdout และ stdout ไปที่some_file
และพิมพ์ไปยัง stdout
do_something &>filename
ไม่ได้ +1
Ambiguous output redirect.
คิดว่าทำไม
$?
ไม่ได้หมายถึงการออกจากสถานะของแต่ออกจากสถานะของdo_something
tee
คุณสามารถเปลี่ยนเส้นทางstderrไปยังstdoutและstdoutเป็นไฟล์:
some_command >file.log 2>&1
ดูที่http://tldp.org/LDP/abs/html/io-redirection.html
รูปแบบนี้เป็นที่ต้องการมากกว่ารูปแบบที่นิยมมากที่สุดและใช้งานได้เฉพาะในทุบตีเท่านั้น ในเชลล์เป้าหมายสามารถตีความได้ว่าเป็นการรันคำสั่งในพื้นหลัง นอกจากนี้รูปแบบสามารถอ่านได้มากขึ้น 2 (คือ STDERR) เปลี่ยนเส้นทางไปที่ 1 (STDOUT)
แก้ไข: เปลี่ยนลำดับตามที่ระบุไว้ในความคิดเห็น
# 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'"
ตอนนี้ก้องง่ายจะเขียนถึง $ LOG_FILE มีประโยชน์สำหรับ daemonizing
ถึงผู้เขียนโพสต์ต้นฉบับ
มันขึ้นอยู่กับสิ่งที่คุณต้องการเพื่อให้บรรลุ หากคุณต้องการเปลี่ยนเส้นทางเข้า / ออกคำสั่งที่คุณโทรจากสคริปต์คำตอบจะได้รับแล้ว Mine เป็นเรื่องเกี่ยวกับการเปลี่ยนเส้นทางภายในสคริปต์ปัจจุบันซึ่งมีผลกับคำสั่ง / บิวด์อินทั้งหมด (รวมถึงส้อม) หลังจากตัวอย่างโค้ดที่กล่าวถึง
โซลูชันที่ยอดเยี่ยมอีกประการหนึ่งคือการเปลี่ยนเส้นทางไปยัง std-err / out AND ไปยัง logger หรือไฟล์บันทึกพร้อมกันซึ่งเกี่ยวข้องกับการแยก "สตรีม" ออกเป็นสองส่วน ฟังก์ชั่นนี้จัดทำโดยคำสั่ง 'tee' ซึ่งสามารถเขียน / ต่อท้ายไฟล์อธิบายหลายไฟล์ (ไฟล์ซ็อกเก็ตไพพ์ ฯลฯ ) ในครั้งเดียว: 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
}
ดังนั้นตั้งแต่ต้น สมมติว่าเรามีเทอร์มินัลเชื่อมต่อกับ / dev / stdout (FD # 1) และ / dev / stderr (FD # 2) ในทางปฏิบัติมันอาจเป็นท่อซ็อกเก็ตหรืออะไรก็ตาม
ผลลัพธ์ของการรันสคริปต์ที่มีบรรทัดด้านบนและเพิ่มสคริปต์นี้:
echo "Will end up in STDOUT(terminal) and /var/log/messages"
... เป็นดังนี้:
$ ./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
หากคุณต้องการเห็นภาพที่ชัดเจนยิ่งขึ้นให้เพิ่ม 2 บรรทัดเหล่านี้ลงในสคริปต์:
ls -l /proc/self/fd/
ps xf
bash your_script.sh 1>file.log 2>&1
1>file.log
สั่งให้เชลล์ส่ง STDOUT ไปที่ไฟล์file.log
และ2>&1
บอกให้เปลี่ยนเส้นทาง STDERR (file descriptor 2) ไปที่ STDOUT (file descriptor 1)
หมายเหตุ:ลำดับสำคัญตามที่ liw.fi ชี้ให้เห็นว่า2>&1 1>file.log
ไม่ทำงาน
อยากรู้อยากเห็นงานนี้:
yourcommand &> filename
แต่นี่จะทำให้เกิดข้อผิดพลาดทางไวยากรณ์:
yourcommand &>> filename
syntax error near unexpected token `>'
คุณต้องใช้:
yourcommand 1>> filename 2>&1
&>>
ดูเหมือนว่าจะทำงานบน BASH 4:$ echo $BASH_VERSION 4.1.5(1)-release $ (echo to stdout; echo to stderr > /dev/stderr) &>> /dev/null
คำตอบสั้น ๆ : Command >filename 2>&1
หรือCommand &>filename
คำอธิบาย:
พิจารณารหัสต่อไปนี้ซึ่งพิมพ์คำว่า "stdout" เป็น stdout และคำว่า "stderror" เป็น stderror
$ (echo "stdout"; echo "stderror" >&2)
stdout
stderror
โปรดทราบว่าโอเปอเรเตอร์ '&' บอกว่าทุบตีว่า 2 เป็นตัวอธิบายไฟล์ (ซึ่งชี้ไปที่ stderr) ไม่ใช่ชื่อไฟล์ หากเราออกจาก '&' คำสั่งนี้จะพิมพ์stdout
ไปยัง stdout และสร้างไฟล์ชื่อ "2" และเขียนstderror
นั่น
ด้วยการทดลองกับโค้ดด้านบนคุณจะเห็นได้ว่าตัวดำเนินการเปลี่ยนเส้นทางทำงานอย่างไร ตัวอย่างเช่นโดยการเปลี่ยนไฟล์ที่ตัวอธิบายสองตัว1,2
ใดถูกเปลี่ยนเส้นทางไปยัง/dev/null
โค้ดสองบรรทัดต่อไปนี้ลบทุกอย่างออกจาก stdout และทุกอย่างจาก stderror ตามลำดับ (การพิมพ์สิ่งที่เหลืออยู่)
$ (echo "stdout"; echo "stderror" >&2) 1>/dev/null
stderror
$ (echo "stdout"; echo "stderror" >&2) 2>/dev/null
stdout
ตอนนี้เราสามารถอธิบายได้ว่าทำไมการแก้ปัญหาทำไมโค้ดต่อไปนี้จึงไม่สร้างเอาต์พุต
(echo "stdout"; echo "stderror" >&2) >/dev/null 2>&1
เพื่อให้เข้าใจอย่างแท้จริงนี้ผมขอแนะนำให้คุณอ่านบทความนี้หน้าเว็บบนโต๊ะอธิบายไฟล์ สมมติว่าคุณอ่านเสร็จแล้วเราก็สามารถดำเนินการต่อไปได้ โปรดทราบว่ากระบวนการ Bash จากซ้ายไปขวา ดังนั้น Bash จะเห็นสิ่ง>/dev/null
แรก (ซึ่งเหมือนกับ1>/dev/null
), และตั้งค่า file descriptor 1 ให้ชี้ไปที่ / dev / null แทน stdout 2>&1
มีการกระทำนี้ทุบตีแล้วย้ายชี้ไปทางขวาและเห็น ชุดนี้อธิบายไฟล์ 2 ให้ชี้ไปที่ไฟล์เดียวกันกับ file descriptor 1 (และไม่ใช่ file descriptor 1 เอง !!!! (ดูแหล่งข้อมูลนี้บนตัวชี้สำหรับข้อมูลเพิ่มเติม)) เนื่องจาก file descriptor 1 คะแนนไปที่ / dev / null และ file descriptor 2 ชี้ไปที่ไฟล์เดียวกับ file descriptor 1 ตอนนี้ file descriptor 2 ก็ชี้ไปที่ / dev / null ดังนั้นตัวให้คำอธิบายไฟล์ทั้งสองชี้ไปที่ / dev / null และนี่คือสาเหตุที่ไม่มีการแสดงผลเอาต์พุต
ในการทดสอบว่าคุณเข้าใจแนวคิดจริง ๆ หรือไม่ให้ลองเดาผลลัพธ์เมื่อเราสลับลำดับการเปลี่ยนเส้นทาง:
(echo "stdout"; echo "stderror" >&2) 2>&1 >/dev/null
stderror
เหตุผลที่นี่คือการประเมินจากซ้ายไปขวา Bash เห็น 2> & 1 ดังนั้นจึงกำหนด file descriptor 2 ให้ชี้ไปที่ตำแหน่งเดียวกับ file descriptor 1 คือ stdout จากนั้นตั้งค่าตัวอธิบายไฟล์ 1 (จำไว้ว่า> / dev / null = 1> / dev / null) ให้ชี้ไปที่> / dev / null ดังนั้นการลบทุกอย่างที่มักจะส่งไปยังมาตรฐานออก ดังนั้นสิ่งที่เราเหลือไว้คือสิ่งที่ไม่ได้ถูกส่งไปยัง stdout ใน subshell (รหัสในวงเล็บ) - เช่น "stderror" สิ่งที่น่าสนใจที่จะต้องทราบก็คือแม้ว่า 1 เป็นเพียงตัวชี้ไปยัง stdout แต่การเปลี่ยนเส้นทางตัวชี้ 2 ถึง 1 ผ่าน2>&1
ไม่ได้ก่อให้เกิดห่วงโซ่ของตัวชี้ 2 -> 1 -> stdout ถ้าเป็นเช่นนั้นผลของการเปลี่ยนเส้นทาง 1 ถึง / dev / null รหัส2>&1 >/dev/null
จะทำให้เชนตัวชี้ 2 -> 1 -> / dev / null ตรงกันข้ามกับสิ่งที่เราเห็นด้านบน
ในที่สุดฉันก็ทราบว่ามีวิธีที่ง่ายกว่าในการทำสิ่งนี้:
จากส่วน 3.6.4 ที่นี่เราเห็นว่าเราสามารถใช้โอเปอเรเตอร์&>
เพื่อเปลี่ยนเส้นทางทั้ง stdout และ stderr ดังนั้นในการเปลี่ยนเส้นทางทั้งเอาต์พุต stderr และ stdout ของคำสั่งใด ๆ ไปยัง\dev\null
(ซึ่งลบเอาต์พุต) เราเพียงพิมพ์
$ command &> /dev/null
หรือในกรณีตัวอย่างของฉัน:
$ (echo "stdout"; echo "stderror" >&2) &>/dev/null
ประเด็นที่สำคัญ:
2>&1 >/dev/null
เป็น! >/dev/null 2>&1
= หนึ่งสร้างเอาท์พุทและอื่น ๆ ไม่ได้!ในที่สุดก็ดูทรัพยากรที่ดีเหล่านี้:
Bash Documentation on Redirection , คำอธิบายของ File Descriptor Tables , คำแนะนำ Pointers
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" )
มีความเกี่ยวข้อง: การเขียน stdOut & stderr ไปยัง syslog
มันเกือบจะได้ผล แต่ไม่ใช่จาก xinted (
ฉันต้องการทางออกให้ stdout และ stderr เขียนลงในไฟล์บันทึกและ stderr ยังคงอยู่บนคอนโซล ดังนั้นฉันจึงต้องทำซ้ำเอาต์พุต stderr ผ่านที
นี่คือทางออกที่ฉันพบ:
command 3>&1 1>&2 2>&3 1>>logfile | tee -a logfile
สำหรับสถานการณ์เมื่อจำเป็นต้องมี "piping" คุณสามารถใช้:
| &
ตัวอย่างเช่น:
echo -ne "15\n100\n"|sort -c |& tee >sort_result.txt
หรือ
TIMEFORMAT=%R;for i in `seq 1 20` ; do time kubectl get pods |grep node >>js.log ; done |& sort -h
โซลูชันที่ใช้ bash นี้สามารถวางท่อ STDOUT และ STDERR แยกกัน (จาก STDERR ของ "sort -c" หรือจาก STDERR ถึง "sort -h")
"ง่าย" วิธี (bash4 ls * 2>&- 1>&-
เท่านั้น):
ฟังก์ชั่นต่อไปนี้สามารถใช้ในการทำให้กระบวนการสลับเป็นผลลัพธ์โดยอัตโนมัติ stdout / stderr และไฟล์บันทึกการทำงาน
#!/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"
}
ตัวอย่างการใช้งานภายในสคริปต์:
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"
@ เฟอร์นันโด-fabreti
การเพิ่มสิ่งที่คุณทำฉันเปลี่ยนฟังก์ชั่นเล็กน้อยและลบ & ปิดและมันทำงานได้สำหรับฉัน
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"
ในสถานการณ์เมื่อคุณพิจารณาใช้สิ่งต่าง ๆ เช่นexec 2>&1
ฉันพบว่าง่ายต่อการอ่านถ้าเป็นไปได้การเขียนรหัสใหม่โดยใช้ฟังก์ชัน bash เช่นนี้:
function myfunc(){
[...]
}
myfunc &>mylog.log
สำหรับ tcsh ฉันต้องใช้คำสั่งต่อไปนี้:
command >& file
หากใช้command &> file
จะทำให้เกิดข้อผิดพลาด "คำสั่ง null ไม่ถูกต้อง"