ฉันจะให้ทั้ง STDOUT และ STDERR ไปที่เทอร์มินัลและล็อกไฟล์ได้อย่างไร


113

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

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

ฉันเคยเห็นวิธีแก้ปัญหามากมายบนเน็ต บางอย่างไม่ได้ผลและบางอย่างก็ซับซ้อนอย่างน่ากลัว ฉันได้พัฒนาโซลูชันที่ใช้งานได้แล้ว (ซึ่งฉันจะป้อนเป็นคำตอบ) แต่มันเป็นสิ่งที่น่ารังเกียจ

โซลูชันที่สมบูรณ์แบบคือโค้ดบรรทัดเดียวที่สามารถรวมไว้ในส่วนเริ่มต้นของสคริปต์ใด ๆ ที่ส่งสตรีมทั้งสองไปยังเทอร์มินัลและล็อกไฟล์

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


สำหรับผู้อ่านอื่น ๆ : คำถามที่คล้ายกัน: stackoverflow.com/questions/692000/…
pevik

1
ฉันรำคาญทุกคน (รวมถึงฉันด้วย!) ยกเว้น @JasonSydes ตกรางและตอบคำถามอื่น และคำตอบของ Jason นั้นไม่น่าเชื่อถือตามที่ฉันแสดงความคิดเห็น ฉันชอบที่จะเห็นคำตอบที่เชื่อถือได้จริงสำหรับคำถามที่คุณถาม (และเน้นย้ำใน EDIT ของคุณ)
Don Hatch

เดี๋ยวก่อนฉันจะเอาคืน คำตอบที่ยอมรับของ @PaulTromblin ไม่ตอบโจทย์ ฉันไม่ได้อ่านมันมากพอ
ดอนฟัก

คำตอบ:


179

ใช้ "ที" เพื่อเปลี่ยนเส้นทางไปยังไฟล์และหน้าจอ ขึ้นอยู่กับเชลล์ที่คุณใช้ก่อนอื่นคุณต้องเปลี่ยนเส้นทาง stderr ไปที่ stdout โดยใช้

./a.out 2>&1 | tee output

หรือ

./a.out |& tee output

ใน csh มีคำสั่งในตัวที่เรียกว่า "script" ที่จะจับภาพทุกอย่างที่ไปที่หน้าจอไปยังไฟล์ คุณเริ่มต้นด้วยการพิมพ์ "script" จากนั้นทำสิ่งที่คุณต้องการจับภาพจากนั้นกด control-D เพื่อปิดไฟล์สคริปต์ ฉันไม่รู้ว่าเทียบเท่ากับ sh / bash / ksh

นอกจากนี้เนื่องจากคุณได้ระบุว่าสิ่งเหล่านี้เป็นสคริปต์ sh ของคุณเองที่คุณสามารถแก้ไขได้คุณสามารถทำการเปลี่ยนเส้นทางภายในโดยล้อมรอบสคริปต์ทั้งหมดด้วยเครื่องหมายวงเล็บหรือวงเล็บเช่น

  #!/bin/sh
  {
    ... whatever you had in your script before
  } 2>&1 | tee output.file

5
ฉันไม่รู้ว่าคุณสามารถวงเล็บคำสั่งในเชลล์สคริปต์ได้ น่าสนใจ.
Jamie

1
ฉันขอขอบคุณทางลัด Bracket ด้วย! ด้วยเหตุผลบางอย่าง2>&1 | tee -a filenameไม่ได้บันทึก stderr ลงในไฟล์จากสคริปต์ของฉัน แต่มันใช้งานได้ดีเมื่อฉันคัดลอกคำสั่งและวางลงในเทอร์มินัล! เคล็ดลับวงเล็บใช้งานได้ดี
Ed Brannin

8
โปรดทราบว่าความแตกต่างระหว่าง stdout และ stderr จะหายไปเนื่องจาก tee จะพิมพ์ทุกอย่างเป็น stdout
Flimm

2
FYI: คำสั่ง 'script' มีอยู่ในการแจกแจงส่วนใหญ่ (เป็นส่วนหนึ่งของแพ็คเกจ util-linux)
SamWN

2
@Flimm มีวิธี (วิธีอื่น) ในการรักษาความแตกต่างระหว่าง stdout และ stderr หรือไม่?
Gabriel

20

ใกล้จะครึ่งทศวรรษต่อมา ...

ฉันเชื่อว่านี่คือ "โซลูชั่นที่สมบูรณ์แบบ" ที่ OP ต้องการ

นี่คือซับหนึ่งที่คุณสามารถเพิ่มลงในสคริปต์ Bash ของคุณ:

exec > >(tee -a $HOME/logfile) 2>&1

นี่คือสคริปต์ขนาดเล็กที่สาธิตการใช้งาน:

#!/usr/bin/env bash

exec > >(tee -a $HOME/logfile) 2>&1

# Test redirection of STDOUT
echo test_stdout

# Test redirection of STDERR
ls test_stderr___this_file_does_not_exist

(หมายเหตุ: ใช้ได้กับ Bash เท่านั้น แต่จะไม่ทำงานร่วมกับ bin / sh /.)

ดัดแปลงจากที่นี่ ; ต้นฉบับไม่ได้จากสิ่งที่ฉันบอกได้จับ STDERR ในไฟล์บันทึก คงมีข้อความจากที่นี่


3
โปรดทราบว่าความแตกต่างระหว่าง stdout และ stderr จะหายไปเนื่องจาก tee จะพิมพ์ทุกอย่างเป็น stdout
Flimm

@Flimm stderr สามารถเปลี่ยนเส้นทางไปยังกระบวนการทีอื่นซึ่งสามารถเปลี่ยนเส้นทางไปยัง stderr ได้อีกครั้ง
jarno

@Flimm ฉันเขียนคำแนะนำของ jarno ไว้ที่นี่: stackoverflow.com/a/53051506/1054322
MatrixManAtYrService

1
โซลูชันนี้เช่นเดียวกับโซลูชันอื่น ๆ ส่วนใหญ่ที่เสนอจนถึงขณะนี้มีแนวโน้มที่จะแข่งขันได้ นั่นคือเมื่อสคริปต์ปัจจุบันเสร็จสมบูรณ์และส่งกลับไปยังพรอมต์ของผู้ใช้หรือสคริปต์การโทรระดับที่สูงขึ้นทีออฟซึ่งทำงานในพื้นหลังจะยังคงทำงานอยู่และอาจปล่อยสองสามบรรทัดสุดท้ายไปที่หน้าจอและ ไฟล์บันทึกล่าช้า (นั่นคือไปยังหน้าจอหลังจากพร้อมต์และไปยังไฟล์บันทึกหลังจากที่คาดว่าไฟล์บันทึกจะเสร็จสมบูรณ์)
Don Hatch

1
อย่างไรก็ตามนี่เป็นคำตอบเดียวที่เสนอเพื่อตอบคำถามได้จริง!
Don Hatch

11

รูปแบบ

the_cmd 1> >(tee stdout.txt ) 2> >(tee stderr.txt >&2 )

ซึ่งจะเปลี่ยนเส้นทางทั้ง stdout และ stderr แยกกันและจะส่งสำเนาของ stdout และ stderr แยกกันไปยังผู้โทร (ซึ่งอาจเป็นเทอร์มินัลของคุณ)

  • ใน zsh จะไม่ดำเนินการในคำสั่งถัดไปจนกว่าtees จะเสร็จสิ้น

  • ในการทุบตีคุณอาจพบว่าผลลัพธ์สองสามบรรทัดสุดท้ายปรากฏขึ้นหลังจากคำสั่งใด ๆ ก็ตามมา

ไม่ว่าในกรณีใดบิตที่ถูกต้องจะไปถูกที่


คำอธิบาย

นี่คือสคริปต์ (เก็บไว้ใน. / ตัวอย่าง):

#! /usr/bin/env bash
the_cmd()
{
    echo out;
    1>&2 echo err;
}

the_cmd 1> >(tee stdout.txt ) 2> >(tee stderr.txt >&2 )

นี่คือเซสชัน:

$ foo=$(./example)
    err

$ echo $foo
    out

$ cat stdout.txt
    out

$ cat stderr.txt
    err

นี่คือวิธีการทำงาน:

  1. teeกระบวนการทั้งสองเริ่มต้นแล้ว stdins ของพวกเขาจะถูกกำหนดให้กับ file descriptors เนื่องจากถูกล้อมรอบในการแทนที่กระบวนการพาธ ไปยังตัวอธิบายไฟล์เหล่านั้นจึงถูกแทนที่ในคำสั่งเรียกดังนั้นตอนนี้จึงมีลักษณะดังนี้:

the_cmd 1> /proc/self/fd/13 2> /proc/self/fd/14

  1. the_cmd รันเขียน stdout ไปยัง file descriptor ตัวแรกและ stderr ไปยังไฟล์ที่สอง

  2. ในกรณีทุบตีเมื่อthe_cmdเสร็จสิ้นคำสั่งต่อไปนี้จะเกิดขึ้นทันที (หากเทอร์มินัลของคุณเป็นผู้โทรคุณจะเห็นข้อความแจ้งของคุณปรากฏขึ้น)

  3. ในกรณี zsh เมื่อthe_cmdเสร็จสิ้นเชลล์จะรอให้ทั้งสองteeกระบวนการเสร็จสิ้นก่อนที่จะดำเนินการต่อ เพิ่มเติมเกี่ยวกับเรื่องนี้ที่นี่

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

  5. teeกระบวนการที่สองได้stdoutเปลี่ยนเส้นทางไปยังผู้โทรstderr(ซึ่งเป็นสิ่งที่ดีเพราะ stdin กำลังอ่านจากthe_cmdstderr ของ) ดังนั้นเมื่อเขียนถึง stdout บิตเหล่านั้นจะไปที่ stderr ของผู้โทร

สิ่งนี้ช่วยให้ stderr แยกจาก stdout ทั้งในไฟล์และในเอาต์พุตของคำสั่ง

หากทีแรกเขียนข้อผิดพลาดใด ๆ จะปรากฏขึ้นทั้งในไฟล์ stderr และใน stderr ของคำสั่งหากทีที่สองเขียนข้อผิดพลาดใด ๆ สิ่งเหล่านี้จะปรากฏเฉพาะใน stderr ของเทอร์มินัลเท่านั้น


สิ่งนี้ดูมีประโยชน์จริงๆและสิ่งที่ฉันต้องการ ฉันไม่แน่ใจว่าจะจำลองการใช้วงเล็บ (ดังที่แสดงในบรรทัดแรก) ใน Windows Batch Script ได้อย่างไร ( teeมีอยู่ในระบบที่เป็นปัญหา) ข้อผิดพลาดที่ฉันได้รับคือ "กระบวนการไม่สามารถเข้าถึงไฟล์ได้เนื่องจากถูกใช้โดยกระบวนการอื่น"
Agi Hammerthief

โซลูชันนี้เช่นเดียวกับโซลูชันอื่น ๆ ส่วนใหญ่ที่เสนอจนถึงขณะนี้มีแนวโน้มที่จะแข่งขันได้ นั่นคือเมื่อสคริปต์ปัจจุบันเสร็จสิ้นและส่งกลับไปยังพรอมต์ของผู้ใช้หรือสคริปต์การโทรระดับที่สูงขึ้นทีออฟซึ่งทำงานในพื้นหลังจะยังคงทำงานอยู่และอาจส่งข้อความสองสามบรรทัดสุดท้ายไปที่หน้าจอและ ไฟล์บันทึกล่าช้า (นั่นคือไปยังหน้าจอหลังจากพร้อมต์และไปยังไฟล์บันทึกหลังจากที่คาดว่าไฟล์บันทึกจะเสร็จสมบูรณ์)
Don Hatch

2
@ DonHatch คุณสามารถเสนอทางออกที่แก้ไขปัญหานี้ได้หรือไม่?
pylipp

ฉันยังสนใจกรณีทดสอบที่ทำให้การแข่งขันเป็นที่ประจักษ์ ไม่ใช่ว่าฉันเป็นหนี้สงสัยจะสูญ แต่ก็ยากที่จะพยายามหลีกเลี่ยงเพราะฉันไม่เคยเห็นมันเกิดขึ้น
MatrixManAtYrService

@pylipp ฉันไม่มีทางออก ฉันจะสนใจมาก
ดอนฟัก

4

เพื่อเปลี่ยนเส้นทาง stderr ไปยัง stdout ต่อท้ายสิ่งนี้ที่คำสั่งของคุณ: 2>&1 สำหรับการส่งออกไปยังเทอร์มินัลและเข้าสู่ระบบคุณควรใช้tee

ทั้งสองอย่างรวมกันจะมีลักษณะดังนี้:

 mycommand 2>&1 | tee mylogfile.log

แก้ไข: สำหรับการฝังลงในสคริปต์ของคุณคุณจะต้องทำเช่นเดียวกัน ดังนั้นสคริปต์ของคุณ

#!/bin/sh
whatever1
whatever2
...
whatever3

จะลงเอยด้วย

#!/bin/sh
( whatever1
whatever2
...
whatever3 ) 2>&1 | tee mylogfile.log

2
โปรดทราบว่าความแตกต่างระหว่าง stdout และ stderr จะหายไปเนื่องจาก tee จะพิมพ์ทุกอย่างเป็น stdout
Flimm

4

แก้ไข: ฉันเห็นว่าฉันตกรางและจบลงด้วยการตอบคำถามที่แตกต่างจากที่ถาม คำตอบสำหรับคำถามที่แท้จริงอยู่ที่ด้านล่างของคำตอบของ Paul Tomblin (หากคุณต้องการปรับปรุงโซลูชันดังกล่าวเพื่อเปลี่ยนเส้นทาง stdout และ stderr แยกกันด้วยเหตุผลบางประการคุณสามารถใช้เทคนิคที่ฉันอธิบายไว้ที่นี่)


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

ฉันคิดว่าในที่สุดฉันก็พบคำตอบที่รักษาความแตกต่างไม่ได้มีแนวโน้มที่จะแข่งขันและก็ไม่ได้เล่นซอด้วยเช่นกัน

Building Block แรก: เพื่อสลับ stdout และ stderr:

my_command 3>&1 1>&2 2>&3-

หน่วยการสร้างที่สอง: หากเราต้องการกรอง (เช่นที) เฉพาะ stderr เราสามารถทำได้โดยการสลับ stdout & stderr กรองแล้วสลับกลับ:

{ my_command 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3-

ตอนนี้ส่วนที่เหลือเป็นเรื่องง่าย: เราสามารถเพิ่มตัวกรอง stdout ได้ในตอนเริ่มต้น:

{ { my_command | stdout_filter;} 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3-

หรือตอนท้าย:

{ my_command 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3- | stdout_filter

เพื่อให้มั่นใจว่าทั้งสองคำสั่งข้างต้นใช้ได้ผลฉันใช้สิ่งต่อไปนี้:

alias my_command='{ echo "to stdout"; echo "to stderr" >&2;}'
alias stdout_filter='{ sleep 1; sed -u "s/^/teed stdout: /" | tee stdout.txt;}'
alias stderr_filter='{ sleep 2; sed -u "s/^/teed stderr: /" | tee stderr.txt;}'

ผลลัพธ์คือ:

...(1 second pause)...
teed stdout: to stdout
...(another 1 second pause)...
teed stderr: to stderr

และข้อความแจ้งของฉันจะกลับมาทันทีหลังจาก " teed stderr: to stderr" ตามที่คาดไว้

เชิงอรรถเกี่ยวกับ zsh :

วิธีแก้ปัญหาข้างต้นใช้งานได้ใน bash (และอาจจะเป็นเชลล์อื่น ๆ ฉันไม่แน่ใจ) แต่มันใช้ไม่ได้ใน zsh มีสองสาเหตุที่ล้มเหลวใน zsh:

  1. 2>&3-zsh ไม่เข้าใจไวยากรณ์ ที่จะต้องเขียนใหม่เป็น2>&3 3>&-
  2. ใน zsh (ไม่เหมือนเชลล์อื่น ๆ ) หากคุณเปลี่ยนเส้นทางตัวอธิบายไฟล์ที่เปิดอยู่แล้วในบางกรณี (ฉันไม่เข้าใจอย่างถ่องแท้ว่ามันตัดสินใจอย่างไร) มันจะมีพฤติกรรมเหมือนทีออฟในตัวแทน เพื่อหลีกเลี่ยงปัญหานี้คุณต้องปิดแต่ละ fd ก่อนที่จะเปลี่ยนเส้นทาง

ตัวอย่างเช่นวิธีแก้ปัญหาที่สองของฉันต้องเขียนใหม่สำหรับ zsh เป็น{my_command 3>&1 1>&- 1>&2 2>&- 2>&3 3>&- | stderr_filter;} 3>&1 1>&- 1>&2 2>&- 2>&3 3>&- | stdout_filter(ซึ่งใช้งานได้ใน bash ด้วย แต่เป็นคำที่ละเอียดมาก)

ในทางกลับกันคุณสามารถใช้ประโยชน์จาก teeing โดยปริยายลึกลับของ zsh เพื่อหาวิธีแก้ปัญหาที่สั้นกว่ามากสำหรับ zsh ซึ่งไม่ได้วิ่งทีเลย:

my_command >&1 >stdout.txt 2>&2 2>stderr.txt

(ฉันไม่สามารถเดาได้จากเอกสารฉันพบว่า>&1และ2>&2เป็นสิ่งที่กระตุ้นการ teeing โดยปริยายของ zsh ฉันพบว่าเกิดจากการลองผิดลองถูก)


ฉันเล่นกับสิ่งนี้ด้วยการทุบตีและทำงานได้ดี คำเตือนสำหรับผู้ใช้ zsh ที่มีนิสัยชอบใช้ความเข้ากันได้ (เช่นตัวฉันเอง) มันทำงานแตกต่างกันที่นั่น: gist.github.com/MatrixManAtYrService/…
MatrixManAtYrService

@MatrixManAtYrService ฉันเชื่อว่าฉันสามารถรับมือกับสถานการณ์ zsh ได้และปรากฎว่ามีวิธีแก้ปัญหาที่ดีกว่ามากใน zsh ดู "เชิงอรรถเกี่ยวกับ zsh" ของฉัน
ดอนฟัก

ขอขอบคุณที่อธิบายวิธีแก้ปัญหาโดยละเอียด คุณรู้วิธีดึงโค้ดส่งคืนเมื่อใช้ฟังก์ชัน ( my_function) ในการกรอง stdout / stderr ที่ซ้อนกันหรือไม่ ฉันทำแล้ว{ { my_function || touch failed;} 3>&1 1>&2 2>&3- | stderr_filter;} 3>&1 1>&2 2>&3- | stdout_filterแต่รู้สึกแปลกที่สร้างไฟล์เป็นตัวบ่งชี้ความล้มเหลว ...
pylipp

@pylipp ฉันไม่ถนัด คุณอาจถามเป็นคำถามแยกกัน (อาจใช้ไปป์ไลน์ที่ง่ายกว่า)
ดอนฟัก

2

ใช้ scriptคำสั่งในสคริปต์ของคุณ (man 1 script)

สร้าง wrapper shellscript (2 บรรทัด) ที่ตั้งค่า script () จากนั้นเรียก exit

ส่วนที่ 1: wrap.sh

#!/bin/sh
script -c './realscript.sh'
exit

ส่วนที่ 2: realscript.sh

#!/bin/sh
echo 'Output'

ผลลัพธ์:

~: sh wrap.sh 
Script started, file is typescript
Output
Script done, file is typescript
~: cat typescript 
Script started on fr. 12. des. 2008 kl. 18.07 +0100
Output

Script done on fr. 12. des. 2008 kl. 18.07 +0100
~:


1

ฉันสร้างสคริปต์ชื่อ "RunScript.sh" เนื้อหาของสคริปต์นี้คือ:

${APP_HOME}/${1}.sh ${2} ${3} ${4} ${5} ${6} 2>&1 | tee -a ${APP_HOME}/${1}.log

ฉันเรียกแบบนี้ว่า

./RunScript.sh ScriptToRun Param1 Param2 Param3 ...

วิธีนี้ใช้งานได้ แต่ต้องใช้สคริปต์ของแอปพลิเคชันเพื่อเรียกใช้ผ่านสคริปต์ภายนอก มันค่อนข้างแย่


9
คุณจะสูญเสียการจัดกลุ่มอาร์กิวเมนต์ที่มีช่องว่างด้วย$ 1 $ 2 $ 3 ...คุณควรใช้ (w / quotes): "$ @"
NVRAM

1

หนึ่งปีต่อมานี่คือสคริปต์ทุบตีแบบเก่าสำหรับบันทึกอะไรก็ได้ ตัวอย่างเช่น
teelog make ...บันทึกไปยังชื่อบันทึกที่สร้างขึ้น (และดูเคล็ดลับในการบันทึกmakes ที่ซ้อนกันด้วย)

#!/bin/bash
me=teelog
Version="2008-10-9 oct denis-bz"

Help() {
cat <<!

    $me anycommand args ...

logs the output of "anycommand ..." as well as displaying it on the screen,
by running
    anycommand args ... 2>&1 | tee `day`-command-args.log

That is, stdout and stderr go to both the screen, and to a log file.
(The Unix "tee" command is named after "T" pipe fittings, 1 in -> 2 out;
see http://en.wikipedia.org/wiki/Tee_(command) ).

The default log file name is made up from "command" and all the "args":
    $me cmd -opt dir/file  logs to `day`-cmd--opt-file.log .
To log to xx.log instead, either export log=xx.log or
    $me log=xx.log cmd ...
If "logdir" is set, logs are put in that directory, which must exist.
An old xx.log is moved to /tmp/\$USER-xx.log .

The log file has a header like
    # from: command args ...
    # run: date pwd etc.
to show what was run; see "From" in this file.

Called as "Log" (ln -s $me Log), Log anycommand ... logs to a file:
    command args ... > `day`-command-args.log
and tees stderr to both the log file and the terminal -- bash only.

Some commands that prompt for input from the console, such as a password,
don't prompt if they "| tee"; you can only type ahead, carefully.

To log all "make" s, including nested ones like
    cd dir1; \$(MAKE)
    cd dir2; \$(MAKE)
    ...
export MAKE="$me make"

!
  # See also: output logging in screen(1).
    exit 1
}


#-------------------------------------------------------------------------------
# bzutil.sh  denisbz may2008 --

day() {  # 30mar, 3mar
    /bin/date +%e%h  |  tr '[A-Z]' '[a-z]'  |  tr -d ' '
}

edate() {  # 19 May 2008 15:56
    echo `/bin/date "+%e %h %Y %H:%M"`
}

From() {  # header  # from: $*  # run: date pwd ...
    case `uname` in Darwin )
        mac=" mac `sw_vers -productVersion`"
    esac
    cut -c -200 <<!
${comment-#} from: $@
${comment-#} run: `edate`  in $PWD `uname -n` $mac `arch` 

!
    # mac $PWD is pwd -L not -P real
}

    # log name: day-args*.log, change this if you like --
logfilename() {
    log=`day`
    [[ $1 == "sudo" ]]  &&  shift
    for arg
    do
        log="$log-${arg##*/}"  # basename
        (( ${#log} >= 100 ))  &&  break  # max len 100
    done
            # no blanks etc in logfilename please, tr them to "-"
    echo $logdir/` echo "$log".log  |  tr -C '.:+=[:alnum:]_\n' - `
}

#-------------------------------------------------------------------------------
case "$1" in
-v* | --v* )
    echo "$0 version: $Version"
    exit 1 ;;
"" | -* )
    Help
esac

    # scan log= etc --
while [[ $1 == [a-zA-Z_]*=* ]]; do
    export "$1"
    shift
done

: ${logdir=.}
[[ -w $logdir ]] || {
    echo >&2 "error: $me: can't write in logdir $logdir"
    exit 1
    }
: ${log=` logfilename "$@" `}
[[ -f $log ]]  &&
    /bin/mv "$log" "/tmp/$USER-${log##*/}"


case ${0##*/} in  # basename
log | Log )  # both to log, stderr to caller's stderr too --
{
    From "$@"
    "$@"
} > $log  2> >(tee /dev/stderr)  # bash only
    # see http://wooledge.org:8000/BashFAQ 47, stderr to a pipe
;;

* )
#-------------------------------------------------------------------------------
{
    From "$@"  # header: from ... date pwd etc.

    "$@"  2>&1  # run the cmd with stderr and stdout both to the log

} | tee $log
    # mac tee buffers stdout ?

esac

ฉันรู้ว่านี่เป็นวิธีที่ล่าช้าในการเพิ่มความคิดเห็น แต่ฉันต้องขอบคุณสำหรับสคริปต์นี้ มีประโยชน์มากและได้รับการบันทึกไว้เป็นอย่างดี!
stephenmm

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