เปลี่ยนเส้นทาง stderr และ stdout ใน Bash


678

ฉันต้องการเปลี่ยนเส้นทางทั้ง stdout และ stderr ของกระบวนการเป็นไฟล์เดียว ฉันจะทำเช่นนั้นใน Bash ได้อย่างไร


2
ฉันอยากจะบอกว่านี่เป็นคำถามที่มีประโยชน์อย่างน่าประหลาดใจ หลายคนไม่ทราบวิธีการทำเช่นนี้เพราะพวกเขาไม่ต้องทำบ่อย ๆ และไม่ใช่พฤติกรรมที่ดีที่สุดของ Bash
Robert Wm Ruedisueli

2
บางครั้งมันมีประโยชน์ที่จะเห็นผลลัพธ์ (ตามปกติ) และเพื่อเปลี่ยนเส้นทางไปยังไฟล์ ดูคำตอบโดย Marko ด้านล่าง (ฉันพูดแบบนี้ที่นี่เพราะมันง่ายที่จะเพียงแค่ดูคำตอบแรกที่ยอมรับถ้ามันเพียงพอที่จะแก้ปัญหา แต่คำตอบอื่น ๆ มักจะให้ข้อมูลที่เป็นประโยชน์)
jvriesem

คำตอบ:


762

ลองดูที่นี่ ควรจะเป็น:

yourcommand &>filename

(เปลี่ยนเส้นทางทั้งสองstdoutและstderrชื่อไฟล์)


29
ไวยากรณ์นี้จะเลิกตามทุบตีแฮกเกอร์วิกิพีเดีย ใช่ไหม?
Salman von Abbas

20
ตามwiki.bash-hackers.org/scripting/obsoleteดูเหมือนว่าล้าสมัยในแง่ที่ว่ามันไม่ได้เป็นส่วนหนึ่งของ POSIX แต่หน้า bash man ทำให้ไม่ถูกลบออกจากการทุบตีในอนาคตอันใกล้ หน้าคนไม่ระบุการตั้งค่าสำหรับ '&>' มากกว่า '> &' ซึ่งเทียบเท่ากัน
chepner

13
ฉันเดาว่าเราไม่ควรใช้ &> เพราะมันไม่ได้อยู่ใน POSIX และเชลล์ทั่วไปเช่น "เส้นประ" ไม่รองรับ
Sam Watkins

27
คำแนะนำพิเศษ: หากคุณใช้สิ่งนี้ในสคริปต์ตรวจสอบให้แน่ใจว่ามันเริ่มต้นด้วย#!/bin/bashแทนที่จะ#!/bin/shต้องใช้ทุบตี
Tor Klingberg

8
หรือ & >> เพื่อผนวกแทนการเขียนทับ
Alexander Gonchiy

449
do_something 2>&1 | tee -a some_file

นี่จะเปลี่ยนเส้นทาง stderr ไปยัง stdout และ stdout ไปที่some_file และพิมพ์ไปยัง stdout


16
บน AIX (ksh) โซลูชันของคุณใช้งานได้ คำตอบที่ยอมรับdo_something &>filenameไม่ได้ +1
ระงับ

11
@Daniel แต่คำถามนี้เกี่ยวกับการทุบตีโดยเฉพาะ
John La Rooy

3
ฉันAmbiguous output redirect.คิดว่าทำไม
Alexandre Holden Daly

1
ฉันมีสคริปต์ทับทิม (ซึ่งฉันไม่ต้องการแก้ไข แต่อย่างใด) ที่พิมพ์ข้อความแสดงข้อผิดพลาดเป็นสีแดงเข้ม สคริปต์ทับทิมนี้ถูกเรียกใช้จากสคริปต์ทุบตีของฉัน (ซึ่งฉันสามารถแก้ไขได้) เมื่อฉันใช้ข้างต้นมันจะพิมพ์ข้อความแสดงข้อผิดพลาดเป็นข้อความธรรมดาลบการจัดรูปแบบ มีวิธีใดบ้างที่จะคงรูปแบบบนหน้าจอและรับเอาท์พุท (ทั้ง stdout และ stderr) ในไฟล์ด้วยหรือไม่?
atlantis

8
โปรดทราบว่า (โดยค่าเริ่มต้น) นี้มีผลข้างเคียงที่$?ไม่ได้หมายถึงการออกจากสถานะของแต่ออกจากสถานะของdo_something tee
Flimm

255

คุณสามารถเปลี่ยนเส้นทางstderrไปยังstdoutและstdoutเป็นไฟล์:

some_command >file.log 2>&1 

ดูที่http://tldp.org/LDP/abs/html/io-redirection.html

รูปแบบนี้เป็นที่ต้องการมากกว่ารูปแบบที่นิยมมากที่สุดและใช้งานได้เฉพาะในทุบตีเท่านั้น ในเชลล์เป้าหมายสามารถตีความได้ว่าเป็นการรันคำสั่งในพื้นหลัง นอกจากนี้รูปแบบสามารถอ่านได้มากขึ้น 2 (คือ STDERR) เปลี่ยนเส้นทางไปที่ 1 (STDOUT)

แก้ไข: เปลี่ยนลำดับตามที่ระบุไว้ในความคิดเห็น


47
สิ่งนี้จะเปลี่ยนเส้นทาง stderr ไปยัง stdout ดั้งเดิมไม่ใช่ไปยังไฟล์ที่ stdout กำลังดำเนินการอยู่ ใส่ '2> & 1' หลังจาก '> file.log' และใช้งานได้

1
ข้อดีของวิธีการนี้คือ some_command &> file.log คืออะไร
ubermonkey

6
หากคุณต้องการผนวกไฟล์คุณต้องทำอย่างนี้ echo "foo" 2> & 1 1 >> bar.txt AFAIK ไม่มีวิธีต่อท้ายการใช้ &>
SlappyTheFish

9
อาร์เกขออภัยเสียงก้อง "foo" 1 >> bar.txt 2> & 1
SlappyTheFish

11
ฉันคิดว่าการตีความที่ 2> & 1 เปลี่ยนเส้นทาง stderr ไปยัง stdout นั้นผิด ฉันเชื่อว่ามีความแม่นยำมากกว่าที่จะบอกว่าจะส่ง stderr ไปยังสถานที่เดียวกันที่ stdout กำลังดำเนินอยู่ในขณะนี้ ดังนั้นการวาง 2> & 1 หลังจากการเปลี่ยนเส้นทางครั้งแรกจึงเป็นสิ่งจำเป็น
jdg

201
# 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) ในทางปฏิบัติมันอาจเป็นท่อซ็อกเก็ตหรืออะไรก็ตาม

  • สร้าง FDs # 3 และ # 4 และชี้ไปที่ "ตำแหน่ง" เดียวกันกับ # 1 และ # 2 ตามลำดับ การเปลี่ยน FD # 1 ไม่ส่งผลกระทบต่อ FD # 3 นับจากนี้เป็นต้นไป ตอนนี้ FDs # 3 และ # 4 ชี้ไปที่ STDOUT และ STDERR ตามลำดับ สิ่งเหล่านี้จะใช้เป็นเทอร์มินัลจริง STDOUT และ STDERR
  • 1>> (... ) เปลี่ยนเส้นทาง STDOUT ไปที่คำสั่งเป็น parens
  • parens (sub-shell) ประมวลผลการอ่าน 'tee' จาก STDOUT (ไปป์) ของ exec และเปลี่ยนเส้นทางไปยังคำสั่ง 'logger' ผ่านไพพ์อื่นไปยัง sub-shell ใน parens ในเวลาเดียวกันมันก็อปปี้อินพุตเดียวกันกับ FD # 3 (เทอร์มินัล)
  • ส่วนที่สองคล้ายกันมากคือการทำเคล็ดลับเดียวกันสำหรับ STDERR และ FDs # 2 และ # 4

ผลลัพธ์ของการรันสคริปต์ที่มีบรรทัดด้านบนและเพิ่มสคริปต์นี้:

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

1
ข้อยกเว้นเพียงข้อเดียวเท่านั้น ในตัวอย่างแรกที่คุณเขียน: exec 1 <> $ LOG_FILE มันทำให้ logfile ดั้งเดิมเป็น allways owerwritten สำหรับวิธีการบันทึกจริงที่ดีกว่าคือ: exec 1 >> $ LOG_FILE มันก่อให้เกิดการบันทึกเป็นต่อท้าย
Znik

4
มันเป็นเรื่องจริงแม้ว่ามันจะขึ้นอยู่กับความตั้งใจ แนวทางของฉันคือการสร้างไฟล์บันทึกที่ไม่ซ้ำใครและประทับเวลาเสมอ อื่น ๆ คือการผนวก ทั้งสองวิธีนี้เป็น 'logrotateable' ฉันชอบไฟล์แยกที่ต้องแยกวิเคราะห์น้อย แต่อย่างที่บอกว่าอะไรก็ตามที่ทำให้เรือของคุณลอย :)
Quizac

1
โซลูชันที่สองของคุณมีข้อมูล แต่มีรหัสการล้างข้อมูลทั้งหมดหรือไม่ ดูเหมือนจะไม่เกี่ยวข้องกันและหากเป็นเช่นนั้นจะทำให้เป็นตัวอย่างที่ดีเท่านั้น ฉันยังต้องการที่จะเห็นมันทำใหม่เล็กน้อยเพื่อให้ FDs 1 และ 2 ไม่ได้ถูกเปลี่ยนเส้นทางไปยังคนตัดไม้ แต่ 3 และ 4 เป็นสิ่งที่เรียกว่าสคริปต์นี้อาจจัดการ 1 และ 2 เพิ่มเติมภายใต้สมมติฐานทั่วไป stdout == 1 และ stderr == 2 แต่การทดลองสั้น ๆ ของฉันแสดงว่ามีความซับซ้อนมากขึ้น
JFlo

1
ฉันชอบดีกว่าด้วยรหัสการล้างข้อมูล อาจเป็นสิ่งที่ทำให้ไขว้เขวจากตัวอย่างหลัก แต่การลอกออกจะทำให้ตัวอย่างไม่สมบูรณ์ เน็ตนั้นเต็มไปด้วยตัวอย่างที่ไม่มีข้อผิดพลาดในการจัดการหรืออย่างน้อยโน้ตที่เป็นมิตรก็ยังคงต้องการโค้ดประมาณร้อยบรรทัดเพื่อให้ปลอดภัยในการใช้
Zoltan K.

1
ฉันต้องการที่จะอธิบายอย่างละเอียดเกี่ยวกับรหัสทำความสะอาด เป็นส่วนหนึ่งของสคริปต์ที่ daemonizes ergo กลายเป็นภูมิคุ้มกันต่อสัญญาณ HANG-UP 'tee' และ 'logger' เป็นโปรเซสที่เกิดจาก PPID เดียวกันและพวกมันสืบทอด HUP Trap จากสคริปต์ทุบตีหลัก ดังนั้นเมื่อกระบวนการหลักตายไปพวกเขาจะได้รับการสืบทอดโดย init [1] พวกเขาจะไม่กลายเป็นซอมบี้ (ลบล้าง) รหัสการล้างข้อมูลทำให้แน่ใจว่างานเบื้องหลังทั้งหมดถูกฆ่าตายหากสคริปต์หลักตาย นอกจากนี้ยังใช้กับกระบวนการอื่น ๆ ที่อาจถูกสร้างขึ้นและทำงานในพื้นหลัง
quizac

41
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ไม่ทำงาน


21

อยากรู้อยากเห็นงานนี้:

yourcommand &> filename

แต่นี่จะทำให้เกิดข้อผิดพลาดทางไวยากรณ์:

yourcommand &>> filename
syntax error near unexpected token `>'

คุณต้องใช้:

yourcommand 1>> filename 2>&1

10
&>>ดูเหมือนว่าจะทำงานบน BASH 4:$ echo $BASH_VERSION 4.1.5(1)-release $ (echo to stdout; echo to stderr > /dev/stderr) &>> /dev/null
272735

15

คำตอบสั้น ๆ : 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

ประเด็นที่สำคัญ:

  • ตัวอธิบายไฟล์ทำตัวเหมือนพอยน์เตอร์ (แม้ว่าตัวอธิบายไฟล์จะไม่เหมือนกับพอยน์เตอร์ของไฟล์)
  • การเปลี่ยนเส้นทาง file descriptor "a" เป็น file descriptor "b" ซึ่งชี้ไปที่ไฟล์ "f" ทำให้ file descriptor "a" ชี้ไปที่ตำแหน่งเดียวกับ file descriptor b - file "f" มันไม่ได้ก่อให้เกิดห่วงโซ่ของตัวชี้ a -> b -> f
  • เพราะที่กล่าวมาเรื่องการสั่งซื้อ2>&1 >/dev/nullเป็น! >/dev/null 2>&1= หนึ่งสร้างเอาท์พุทและอื่น ๆ ไม่ได้!

ในที่สุดก็ดูทรัพยากรที่ดีเหล่านี้:

Bash Documentation on Redirection , คำอธิบายของ File Descriptor Tables , คำแนะนำ Pointers


ตัวอธิบายไฟล์ (0, 1, 2) เป็นเพียงออฟเซ็ตลงในตาราง เมื่อใช้ 2> & 1 เอฟเฟกต์คือ slot FD [2] = dup (1) ดังนั้นเมื่อใดก็ตามที่ FD [1] ชี้ FD [2] จะชี้ไปที่ เมื่อคุณเปลี่ยน FD [1] ให้ชี้ไปที่ / dev / null ระบบจะเปลี่ยน FD [1] แต่จะไม่เปลี่ยนสล็อต FD [2] (ซึ่งชี้ไปที่ stdout) ฉันใช้คำว่า dup () เพราะนั่นคือการเรียกระบบที่ใช้ในการทำซ้ำไฟล์ descriptor
PatS

11
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 (


ฉันเดาว่ามันใช้งานไม่ได้เพราะ "/ dev / fd / 3 การอนุญาตถูกปฏิเสธ" การเปลี่ยนเป็น> & 3 อาจช่วยได้
Quizac

6

ฉันต้องการทางออกให้ stdout และ stderr เขียนลงในไฟล์บันทึกและ stderr ยังคงอยู่บนคอนโซล ดังนั้นฉันจึงต้องทำซ้ำเอาต์พุต stderr ผ่านที

นี่คือทางออกที่ฉันพบ:

command 3>&1 1>&2 2>&3 1>>logfile | tee -a logfile
  • สลับแรก stderr และ stdout
  • จากนั้นผนวก stdout ต่อท้ายไฟล์บันทึก
  • pipe stderr เพื่อทีและผนวกเข้ากับไฟล์บันทึกการทำงาน

BTW สิ่งนี้ไม่ได้ผลสำหรับฉัน (logfile ว่างเปล่า) | tee ไม่มีผลกระทบ แทนฉันทำให้มันทำงานโดยใช้stackoverflow.com/questions/692000/…
Yaroslav Bulatov

4

สำหรับสถานการณ์เมื่อจำเป็นต้องมี "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")



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"

เมื่อฉันใช้ฟังก์ชั่นของคุณและพยายามที่จะกู้คืนเอาต์พุตมาตรฐานฉันได้รับ echo: ข้อผิดพลาดในการเขียน: หมายเลขไฟล์ไม่ถูกต้องการเปลี่ยนเส้นทางทำงานได้อย่างสมบูรณ์แบบ ... การคืนค่าดูเหมือนจะไม่
thom schumacher

เพื่อให้สคริปต์ของคุณทำงานได้ฉันต้องแสดงความคิดเห็นในบรรทัดเหล่านี้และฉันเปลี่ยนลำดับ: #exec 1> & - #closes FD 1 (logfile) #exec 2> & - #closes FD 2 (logfile); exec 1> & 3 #restore stdout exec 2> & 4 #restore stderr
thom schumacher

เสียใจเป็นอย่างยิ่ง. ฉันไม่ได้รับข้อผิดพลาดเมื่อทำงานใน CentOS 7 ทุบตี 4.2.46 ฉันได้ใส่คำอธิบายประกอบไว้แล้วซึ่งฉันได้รับคำสั่งเหล่านั้น มันคือ: Ref: logan.tw/posts/2016/02/20/open-and-close-files-in-bash
Fernando Fabreti

ฉันรันคำสั่งเหล่านี้บน AIX ซึ่งอาจเป็นสาเหตุ ฉันเพิ่มโพสต์สำหรับการแก้ไขที่ฉันทำ
ทุมชูมัคเกอร์

1

@ เฟอร์นันโด-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"

1

ในสถานการณ์เมื่อคุณพิจารณาใช้สิ่งต่าง ๆ เช่นexec 2>&1ฉันพบว่าง่ายต่อการอ่านถ้าเป็นไปได้การเขียนรหัสใหม่โดยใช้ฟังก์ชัน bash เช่นนี้:

function myfunc(){
  [...]
}

myfunc &>mylog.log

0

สำหรับ tcsh ฉันต้องใช้คำสั่งต่อไปนี้:

command >& file

หากใช้command &> fileจะทำให้เกิดข้อผิดพลาด "คำสั่ง null ไม่ถูกต้อง"

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