ฉันจะเปลี่ยนทิศทางเอาต์พุตของเชลล์สคริปต์ทั้งหมดภายในสคริปต์ได้อย่างไร?


205

เป็นไปได้หรือไม่ที่จะเปลี่ยนเส้นทางเอาต์พุตทั้งหมดของเชลล์สคริปต์ Bourne ไปยังที่อื่น แต่ด้วยคำสั่งเชลล์ภายในสคริปต์เอง?

การเปลี่ยนเส้นทางเอาต์พุตของคำสั่งเดียวนั้นง่าย แต่ฉันต้องการอะไรมากกว่านี้:

#!/bin/sh
if [ ! -t 0 ]; then
    # redirect all of my output to a file here
fi

# rest of script...

ความหมาย: หากสคริปต์รันแบบไม่โต้ตอบ (เช่น cron) ให้บันทึกเอาต์พุตของทุกสิ่งลงในไฟล์ หากเรียกใช้แบบโต้ตอบจากเชลล์ให้เอาต์พุตไปที่ stdout ตามปกติ

ฉันต้องการทำสิ่งนี้สำหรับสคริปต์ที่รันตามปกติโดยยูทิลิตี้ FreeBSD เป็นระยะ ๆ เป็นส่วนหนึ่งของการทำงานประจำวันซึ่งปกติฉันไม่สนใจที่จะเห็นทุกวันทางอีเมลดังนั้นฉันจึงไม่ได้ส่ง อย่างไรก็ตามหากบางสิ่งบางอย่างในสคริปต์นี้ล้มเหลวสิ่งสำคัญสำหรับฉันและฉันต้องการที่จะสามารถจับภาพและส่งอีเมลผลลัพธ์ของส่วนหนึ่งของงานประจำวันนี้

ปรับปรุง: คำตอบของโจชัวเป็นจุดบน แต่ฉันยังต้องการบันทึกและคืนค่า stdout และ stderr รอบสคริปต์ทั้งหมดซึ่งจะทำเช่นนี้:

# save stdout and stderr to file descriptors 3 and 4, then redirect them to "foo"
exec 3>&1 4>&2 >foo 2>&1

# ...

# restore stdout and stderr
exec 1>&3 2>&4

2
การทดสอบ $ TERM ไม่ใช่วิธีที่ดีที่สุดในการทดสอบโหมดโต้ตอบ ให้ทดสอบว่า stdin เป็น tty หรือไม่ (ทดสอบ -t 0)
Chris Jester-Young

2
ในคำอื่น ๆ : ถ้า [! -t 0]; จากนั้น exec> somefile 2> & 1; fi
Chris Jester-Young

1
ดูที่นี่สำหรับความดีทั้งหมด: http://tldp.org/LDP/abs/html/io-redirection.htmlโดยทั่วไปสิ่งที่โจชัวพูด exec> file เปลี่ยนเส้นทาง stdout ไปยังไฟล์เฉพาะ, exec <file แทนที่ stdin โดย file, ฯลฯ มันเหมือนปกติ แต่ใช้ exec (ดู man exec สำหรับรายละเอียดเพิ่มเติม)
Loki

ในส่วนอัปเดตของคุณคุณควรปิด FDs 3 และ 4 เช่น: exec 1>&3 2>&4 3>&- 4>&-
Gurjeet Singh

คำตอบ:


173

ตอบคำถามตามที่ได้รับการปรับปรุง

#...part of script without redirection...

{
    #...part of script with redirection...
} > file1 2>file2 # ...and others as appropriate...

#...residue of script without redirection...

เครื่องมือจัดฟัน '{... }' ให้หน่วยของการเปลี่ยนเส้นทาง I / O เครื่องมือจัดฟันต้องปรากฏในตำแหน่งที่คำสั่งสามารถปรากฏได้อย่างง่าย ๆ ในตอนเริ่มต้นของบรรทัดหรือหลังเซมิโคลอน ( ใช่ว่าสามารถทำให้แม่นยำยิ่งขึ้นถ้าคุณต้องการเล่นลิ้นแจ้งให้เราทราบ )

คุณถูกต้องที่คุณสามารถรักษา stdout ดั้งเดิมและ stderr ด้วยการเปลี่ยนเส้นทางที่คุณแสดง แต่โดยทั่วไปจะง่ายกว่าสำหรับผู้ที่ต้องบำรุงรักษาสคริปต์ในภายหลังเพื่อทำความเข้าใจว่าเกิดอะไรขึ้นถ้าคุณกำหนดขอบเขตของโค้ดที่เปลี่ยนเส้นทางดังที่แสดงไว้ด้านบน

ในส่วนที่เกี่ยวข้องของคู่มือการทุบตีมีการจัดกลุ่มคำสั่งและI / O เปลี่ยนเส้นทาง ในส่วนที่เกี่ยวข้องของสเป POSIX เปลือกผสมคำสั่งและI / O เปลี่ยนเส้นทาง Bash มีเครื่องหมายพิเศษบางอย่าง แต่มีลักษณะคล้ายกับข้อกำหนดเชลล์ POSIX


3
นี่ชัดเจนกว่าการบันทึก descriptor ดั้งเดิมและกู้คืนในภายหลัง
Steve Madsen

23
ฉันต้องทำ googling เพื่อที่จะเข้าใจว่าสิ่งนี้กำลังทำอะไรอยู่ดังนั้นฉันจึงต้องการแบ่งปัน วงเล็บปีกกากลายเป็น"บล็อกของรหัส"ซึ่งในผลสร้างฟังก์ชั่นที่ไม่ระบุชื่อ จากนั้นทุกอย่างในบล็อกโค้ดสามารถเปลี่ยนเส้นทางได้ (ดูตัวอย่างที่ 3-2จากลิงค์นั้น) โปรดทราบด้วยว่าการจัดฟันแบบโค้งไม่ได้เรียกใช้subshellแต่การเปลี่ยนทิศทาง I / O ที่ คล้ายกันสามารถทำได้ด้วย subshell โดยใช้วงเล็บ
chris

3
ฉันชอบวิธีนี้ดีกว่าวิธีอื่น ๆ แม้แต่คนที่มีเพียงความเข้าใจพื้นฐานที่สุดของการเปลี่ยนเส้นทาง I / O สามารถเข้าใจสิ่งที่เกิดขึ้น ยิ่งกว่านั้น verbose มากขึ้น และในฐานะ Pythoner ฉันชอบ verbose
John Red

>>ดีกว่าทำ >บางคนมีนิสัยของ การต่อท้ายนั้นปลอดภัยกว่าและแนะนำมากกว่าการใช้มากเกินไป บางคนเขียนแอปพลิเคชันซึ่งใช้คำสั่งคัดลอกมาตรฐานเพื่อส่งออกข้อมูลบางส่วนไปยังปลายทางเดียวกัน
neverMind9

แอพนั้นคือ ccc.bmw71 (.pro) (เครื่องมือตรวจสอบแบตเตอรี่ 3C เดิมชื่อ“ เครื่องมือตรวจสอบแบตเตอรี่”) ส่งออกประวัติแบตเตอรี่ไปที่ /sdcard/bmw_history.txt เสมอ หากมีอยู่แล้วลองเดาดูสิ OVERWRITE! ทำให้ฉันสูญเสียประวัติแบตเตอรี่ไปโดยไม่ได้ตั้งใจ ฉันตั้งค่าขีด จำกัด ใน 30 วันเป็นจำนวนที่สูงมากซึ่งทำให้ใช้ไม่ได้และทำให้กลับเป็น 30 ฉันต้องการส่งออกประวัติแบตเตอรี่ปัจจุบันก่อนที่จะนำเข้าข้อมูลสำรองที่มีอยู่ จากนั้นเกิดขึ้น
neverMind9

175

โดยทั่วไปแล้วเราจะวางหนึ่งในเหล่านี้ที่หรือใกล้ด้านบนของสคริปต์ สคริปต์ที่แยกวิเคราะห์บรรทัดคำสั่งของพวกเขาจะทำการเปลี่ยนเส้นทางหลังจากการแยกวิเคราะห์

ส่ง stdout ไปที่ไฟล์

exec > file

กับ stderr

exec > file                                                                      
exec 2>&1

ผนวกทั้ง stdout และ stderr ไปยังไฟล์

exec >> file
exec 2>&1

ดังที่Jonathan Leffler พูดถึงในความคิดเห็นของเขา :

execมีสองงานแยกกัน คนแรกคือการแทนที่เชลล์ดำเนินการในปัจจุบัน (สคริปต์) ด้วยโปรแกรมใหม่ อีกอันกำลังเปลี่ยนทิศทาง I / O ในเชลล์ปัจจุบัน execนี้มีความโดดเด่นโดยมีอาร์กิวเมนต์ไม่มี


7
ฉันว่ายังเพิ่ม 2> & 1 ไปยังจุดสิ้นสุดดังนั้น stderr ก็จะถูกจับเช่นกัน :-)
Chris Jester-Young

7
คุณใส่สิ่งเหล่านี้ที่ไหน ที่ด้านบนของสคริปต์?
colan

4
ด้วยวิธีนี้เราต้องรีเซ็ตการเปลี่ยนเส้นทางออกจากสคริปต์ คำตอบถัดไปของ Jonathan Leffler คือ "การพิสูจน์ความล้มเหลว" มากกว่าในแง่นี้
Chuim

6
@JohnRed: execมีสองงานแยกกัน หนึ่งคือการแทนที่สคริปต์ปัจจุบันด้วยคำสั่งอื่นโดยใช้กระบวนการเดียวกัน - คุณระบุคำสั่งอื่นเป็นข้อโต้แย้งไปที่exec(และคุณสามารถปรับเปลี่ยน I / O การเปลี่ยนเส้นทางในขณะที่คุณทำ) งานอื่นกำลังเปลี่ยนการเปลี่ยนทิศทาง I / O ในเชลล์สคริปต์ปัจจุบันโดยไม่ต้องเปลี่ยนมัน execสัญกรณ์นี้มีความโดดเด่นโดยไม่ต้องสั่งเป็นอาร์กิวเมนต์ไปยัง เครื่องหมายในคำตอบนี้เป็นของตัวแปร "I / O เท่านั้น" ซึ่งจะเปลี่ยนเฉพาะการเปลี่ยนเส้นทางและไม่ได้แทนที่สคริปต์ที่ทำงานอยู่ ( setคำสั่งนั้นคล้ายกันในหลายจุดประสงค์)
Jonathan Leffler

18
exec > >(tee -a "logs/logdata.log") 2>&1พิมพ์บันทึกบนหน้าจอเช่นเดียวกับการเขียนลงในไฟล์
shriyog

32

คุณสามารถทำให้ทั้งฟังก์ชันเป็นแบบนี้:

main_function() {
  do_things_here
}

ดังนั้นในตอนท้ายของสคริปต์นี้:

if [ -z $TERM ]; then
  # if not run via terminal, log everything into a log file
  main_function 2>&1 >> /var/log/my_uber_script.log
else
  # run via terminal, only output to screen
  main_function
fi

หรือคุณอาจบันทึกทุกอย่างลงใน logfile แต่ละการเรียกใช้และยังส่งออกไปยัง stdout โดยทำดังนี้

# log everything, but also output to stdout
main_function 2>&1 | tee -a /var/log/my_uber_script.log

คุณหมายถึง main_function >> /var/log/my_uber_script.log 2> & 1
Felipe Alvarez

ฉันชอบใช้ main_function ในไพพ์ดังกล่าว แต่ในกรณีนี้สคริปต์ของคุณจะไม่ส่งคืนค่าส่งคืนดั้งเดิม ในกรณีทุบตีคุณควรออกแล้วใช้ 'exit $ {PIPESTATUS [0]}'
rudimeier

7

สำหรับการบันทึก stdout และ stderr ดั้งเดิมคุณสามารถใช้:

exec [fd number]<&1 
exec [fd number]<&2

ตัวอย่างเช่นรหัสต่อไปนี้จะพิมพ์ "walla1" และ "walla2" ไปยังไฟล์บันทึก ( a.txt), "walla3" ถึง stdout, "walla4" ถึง stderr

#!/bin/bash

exec 5<&1
exec 6<&2

exec 1> ~/a.txt 2>&1

echo "walla1"
echo "walla2" >&2
echo "walla3" >&5
echo "walla4" >&6

1
โดยปกติจะเป็นการดีกว่าที่จะใช้exec 5>&1และการexec 6>&2ใช้สัญกรณ์เปลี่ยนเส้นทางเอาท์พุทมากกว่าสัญกรณ์เปลี่ยนเส้นทางอินพุตสำหรับผลลัพธ์ คุณได้รับไปกับมันเพราะเมื่อสคริปต์ถูกเรียกใช้จากเทอร์มินัลอินพุตมาตรฐานก็สามารถเขียนได้และเอาต์พุตมาตรฐานและข้อผิดพลาดมาตรฐานสามารถอ่านได้โดยอาศัยคุณธรรม (หรือเป็น 'รอง'?) ของโวหารประวัติศาสตร์: เทอร์มินัล การอ่านและการเขียนและคำอธิบายไฟล์แบบเปิดเดียวกันใช้สำหรับตัวอธิบายไฟล์ I / O มาตรฐานทั้งสามตัว
Jonathan Leffler

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.