วิธี grep สตรีมข้อผิดพลาดมาตรฐาน (stderr)?


76

ฉันใช้ ffmpeg เพื่อรับข้อมูลเมตาของคลิปเสียง แต่ฉันไม่สามารถ grep มัน

    $ ffmpeg -i 01-Daemon.mp3  |grep -i Duration
    FFmpeg version SVN-r15261, Copyright (c) 2000-2008 Fabrice Bellard, et al.
      configuration: --prefix=/usr --bindir=/usr/bin 
      --datadir=/usr/share/ffmpeg --incdir=/usr/include/ffmpeg --libdir=/usr/lib
      --mandir=/usr/share/man --arch=i386 --extra-cflags=-O2 
      ...

ฉันตรวจสอบเอาต์พุต ffmpeg นี้โดยตรงไปที่ stderr

$ ffmpeg -i 01-Daemon.mp3 2> /dev/null

ดังนั้นฉันคิดว่า grep ไม่สามารถอ่านสตรีมข้อผิดพลาดเพื่อตรวจจับเส้นที่ตรงกันได้ เราจะเปิดใช้งาน grep เพื่ออ่านสตรีมข้อผิดพลาดได้อย่างไร?

การใช้ลิงก์nixCraftฉันเปลี่ยนเส้นทางสตรีมข้อผิดพลาดมาตรฐานไปยังสตรีมเอาต์พุตมาตรฐานจากนั้น grep ทำงาน

$ ffmpeg -i 01-Daemon.mp3 2>&1 | grep -i Duration
  Duration: 01:15:12.33, start: 0.000000, bitrate: 64 kb/s

แต่ถ้าเราไม่ต้องการเปลี่ยนเส้นทาง stderr ไปเป็น stdout ล่ะ?


1
ฉันเชื่อว่าgrepสามารถใช้งานได้บน stdout เท่านั้น (แม้ว่าฉันจะไม่สามารถหาแหล่งที่มาแบบบัญญัติเพื่อสำรองข้อมูลได้) ซึ่งหมายความว่าสตรีมใด ๆ จำเป็นต้องแปลงเป็น stdout ก่อน
Stefan Lasiewski

9
@Stefan: grepสามารถทำงานได้บน stdin เท่านั้น เป็นไพพ์ที่สร้างขึ้นโดยเชลล์ที่เชื่อมต่อ stdin ของ grep กับ stdout ของคำสั่งอื่น และเชลล์สามารถเชื่อมต่อ stdout กับ stdin เท่านั้น
Gilles

อ๊ะคุณพูดถูก ฉันคิดว่านั่นคือสิ่งที่ฉันตั้งใจจะพูดจริงๆฉันไม่ได้คิดอย่างนั้น ขอบคุณ @Giles
Stefan Lasiewski

คุณต้องการที่จะยังคงพิมพ์ stdout หรือไม่?
มิเคล

คำตอบ:


52

หากคุณกำลังใช้bashทำไมไม่ใช้ท่อที่ไม่ระบุชื่อในสาระสำคัญชวเลขสำหรับสิ่งที่ phunehehe พูดว่า:

ffmpeg -i 01-Daemon.mp3 2> >(grep -i Duration)


3
+1 ดี! ทุบตีเท่านั้น แต่สะอาดกว่าทางเลือก
มิเคล

1
+1 ฉันใช้สิ่งนั้นเพื่อตรวจสอบcpผลลัพธ์cp -r dir* to 2> >(grep -v "svn")
Betlista

5
หากคุณต้องการผลลัพธ์ที่เปลี่ยนเส้นทางกรองบน ​​stderr อีกครั้งเพิ่ม>&2เช่นcommand 2> >(grep something >&2)
tlo

2
โปรดอธิบายวิธีการใช้งาน 2>เปลี่ยนเส้นทาง stderr ไปที่ไฟล์ฉันเข้าใจแล้ว >(grep -i Duration)นี้อ่านจากไฟล์ แต่ไฟล์จะไม่ถูกจัดเก็บ? เทคนิคนี้เรียกว่าอะไรดังนั้นฉันสามารถอ่านเพิ่มเติมเกี่ยวกับมันได้
Marko Avlijaš

1
มันเป็น "น้ำตาล syntactic" เพื่อสร้างและท่อ (ไม่ใช่ไฟล์) และเมื่อสมบูรณ์ลบท่อนั้น พวกมันไม่ระบุชื่ออย่างมีประสิทธิภาพเพราะพวกเขาไม่ได้รับชื่อในระบบไฟล์ Bash เรียกการทดแทนกระบวนการนี้
Jé Queue

48

ไม่มีกระสุนธรรมดา (แม้แต่ zsh) ที่อนุญาตให้มีท่ออื่นนอกเหนือจาก stdout ไปยัง stdin แต่เชลล์สไตล์ Bourne ทั้งหมดสนับสนุนการกำหนดไฟล์ descriptor อีกครั้ง (เหมือนใน1>&2) ดังนั้นคุณสามารถเบี่ยงเบน stdout ชั่วคราวไปยัง fd 3 และ stderr เป็น stdout ได้ชั่วคราวและต่อมาใส่ fd 3 กลับไปสู่ ​​stdout หากstuffผลิตออกบางอย่างเกี่ยวกับ stdout และผลผลิตบางอย่างเกี่ยวกับ stderr และคุณต้องการนำไปใช้ในการออกข้อผิดพลาดออกจากออกมาตรฐานแตะต้องคุณสามารถใช้filter{ stuff 2>&1 1>&3 | filter 1>&2; } 3>&1

$ stuff () {
  echo standard output
  echo more output
  echo standard error 1>&2
  echo more error 1>&2
}
$ filter () {
  grep a
}
$ { stuff 2>&1 1>&3 | filter 1>&2; } 3>&1
standard output
more output
standard error

วิธีนี้ใช้ได้ผล โชคไม่ดีที่ในกรณีของฉันหากคืนค่าที่ไม่เป็นศูนย์กลับมาก็จะหายไป - ค่าที่ส่งคืนเป็น 0 สำหรับฉัน สิ่งนี้อาจไม่เกิดขึ้นเสมอไป แต่เกิดขึ้นในกรณีที่ฉันกำลังดูอยู่ มีวิธีการบันทึกหรือไม่
Faheem Mitha

2
@FaheemMitha ไม่แน่ใจว่าคุณกำลังทำอะไร แต่อาจpipestatusจะช่วยได้
Gilles

1
@FaheemMitha set -o pipefailอาจมีประโยชน์ที่นี่ขึ้นอยู่กับสิ่งที่คุณต้องการทำกับสถานะข้อผิดพลาด (ตัวอย่างเช่นหากคุณset -eเปิดเพื่อล้มเหลวในข้อผิดพลาดใด ๆ คุณอาจต้องการset -o pipefailด้วย)
Wildcard

@ Wildcard ใช่ฉันset -eเปิดเพื่อล้มเหลวในข้อผิดพลาดใด ๆ
Faheem Mitha

rcเปลือกช่วยให้ stderr ท่อ ดูคำตอบของฉันด้านล่าง
Rolf

20

สิ่งนี้คล้ายกับ "temp file trick" ของ phunehehe แต่ใช้ไพพ์ที่มีชื่อแทนช่วยให้คุณได้ผลลัพธ์ที่ใกล้เคียงขึ้นเล็กน้อยเมื่อพวกมันถูกส่งออกซึ่งจะเป็นประโยชน์สำหรับคำสั่งที่ใช้เวลานาน:

$ mkfifo mypipe
$ command 2> mypipe | grep "pattern" mypipe

ในการก่อสร้างนี้ stderr จะถูกนำไปยังท่อที่ชื่อว่า "mypipe" เนื่องจากgrepถูกเรียกด้วยอาร์กิวเมนต์ไฟล์มันจะไม่มองไปที่ STDIN สำหรับอินพุต น่าเสียดายที่คุณยังจะต้องทำความสะอาดท่อที่มีชื่อเมื่อคุณทำเสร็จแล้ว

หากคุณกำลังใช้ทุบตี 4 มีไวยากรณ์ทางลัดซึ่งเป็นcommand1 2>&1 | command2 command1 |& command2อย่างไรก็ตามฉันเชื่อว่านี่เป็นทางลัดไวยากรณ์อย่างแท้จริงคุณยังคงเปลี่ยนเส้นทาง STDERR ไปยัง STDOUT


ที่เกี่ยวข้อง: stackoverflow.com/questions/2871233/…
Stefan Lasiewski

1
ว่า|&ไวยากรณ์เป็นที่สมบูรณ์แบบในการทำความสะอาดป่องทับทิมร่องรอย stderr สแต็ค ในที่สุดฉันก็สามารถ grep เหล่านั้นโดยไม่ต้องยุ่งยากมากเกินไป
pgr

8

คำตอบของ Gilles และ Stefan Lasiewski นั้นทั้งดี แต่วิธีนี้ง่ายกว่า:

ffmpeg -i 01-Daemon.mp3 2>&1 >/dev/null | grep "pattern"

ฉันสมมติว่าคุณไม่ต้องการffmpeg'sพิมพ์ stdout

มันทำงานอย่างไร:

  • ท่อก่อน
    • ffmpeg และ grep เริ่มต้นด้วย stdout ของ ffmpeg จะไปที่ stdin ของ grep
  • เปลี่ยนเส้นทางถัดไปจากซ้ายไปขวา
    • ffmpeg's stderr ถูกตั้งค่าเป็น stdout อะไรก็ตาม (ปัจจุบันเป็นไพพ์)
    • stdout ของ ffmpeg ถูกตั้งค่าเป็น / dev / null

คำอธิบายนี้ทำให้ฉันสับสน กระสุนสองนัดสุดท้ายทำให้ฉันคิดว่า "เปลี่ยนเส้นทาง stderr ไปยัง stdout" จากนั้น "เปลี่ยนเส้นทาง stdout (ด้วย stderr ตอนนี้) เป็น / dev / null" อย่างไรก็ตามนี่ไม่ใช่สิ่งที่ทำจริงๆ ข้อความเหล่านั้นดูเหมือนจะกลับรายการ
Steve

1
@ Steve ไม่มี "with stderr" ในกระสุนนัดที่สอง คุณเคยเห็นunix.stackexchange.com/questions/37660/order-of-redirectionsหรือไม่
มิเคล

ไม่ฉันหมายถึงนั่นคือการตีความของฉันที่คุณอธิบายเป็นภาษาอังกฤษ อย่างไรก็ตามลิงค์ที่คุณให้นั้นมีประโยชน์มาก
Steve

เหตุใดจึงต้องเปลี่ยนเส้นทางไปที่ / dev / null
Kanwaljeet Singh

7

ดูด้านล่างสำหรับสคริปต์ที่ใช้ในการทดสอบเหล่านี้

Grep สามารถทำงานกับ stdin ได้เท่านั้นดังนั้นคุณต้องแปลง stderr stream ในรูปแบบที่ Grep สามารถแยกวิเคราะห์ได้

โดยปกติ stdout และ stderr ถูกพิมพ์บนหน้าจอของคุณ:

$ ./stdout-stderr.sh
./stdout-stderr.sh: Printing to stdout
./stdout-stderr.sh: Printing to stderr

หากต้องการซ่อน stdout แต่ยังคงพิมพ์ stderr ทำสิ่งนี้:

$ ./stdout-stderr.sh >/dev/null
./stdout-stderr.sh: Printing to stderr

แต่ grep จะไม่ทำงานบน stderr! คุณคาดหวังว่าคำสั่งต่อไปนี้เพื่อระงับบรรทัดที่มี 'err' แต่ไม่มี

$ ./stdout-stderr.sh >/dev/null |grep --invert-match err
./stdout-stderr.sh: Printing to stderr

นี่คือทางออก

ไวยากรณ์ Bash ต่อไปนี้จะซ่อนเอาต์พุตไปยัง stdout แต่จะยังคงแสดง stderr ก่อนอื่นเราทำการ stdout เป็น / dev / null จากนั้นเราแปลง stderr เป็น stdout เพราะ Unix ไพพ์จะทำงานบน stdout เท่านั้น คุณยังสามารถพิมพ์ข้อความได้

$ ./stdout-stderr.sh 2>&1 >/dev/null | grep err
./stdout-stderr.sh: Printing to stderr

(โปรดทราบว่าคำสั่งด้านบนจะแตกต่างกันไป./command >/dev/null 2>&1ซึ่งเป็นคำสั่งทั่วไป)

นี่คือสคริปต์ที่ใช้สำหรับการทดสอบ พิมพ์หนึ่งบรรทัดไปยัง stdout และหนึ่งบรรทัดไปยัง stderr:

#!/bin/sh

# Print a message to stdout
echo "$0: Printing to stdout"
# Print a message to stderr
echo "$0: Printing to stderr" >&2

exit 0

หากคุณสลับการเปลี่ยนเส้นทางคุณไม่จำเป็นต้องใช้เครื่องมือจัดฟันทั้งหมด ./stdout-stderr.sh 2>&1 >/dev/null | grep errเพียงแค่ทำ
มิเคล

3

เมื่อคุณไพพ์เอาต์พุตของคำสั่งหนึ่งไปยังอีกคำสั่ง (โดยใช้|) คุณจะเปลี่ยนทิศทางเอาต์พุตมาตรฐานเท่านั้น ดังนั้นควรอธิบายว่าทำไม

ffmpeg -i 01-Daemon.mp3 | grep -i Duration

ไม่ส่งออกสิ่งที่คุณต้องการ (ใช้งานได้แม้ว่า)

หากคุณไม่ต้องการเปลี่ยนเส้นทางข้อผิดพลาดไปยังเอาต์พุตมาตรฐานคุณสามารถเปลี่ยนเส้นทางข้อผิดพลาดไปยังไฟล์ได้แล้วจึง grep ในภายหลัง

ffmpeg -i 01-Daemon.mp3 2> /tmp/ffmpeg-error
grep -i Duration /tmp/ffmpeg-error

ขอบคุณ it does work, thoughคุณหมายถึงมันทำงานบนเครื่องของคุณหรือไม่? ประการที่สองเมื่อคุณชี้ให้เห็นโดยใช้ไพพ์เราสามารถเปลี่ยนเส้นทาง stdout เท่านั้น ฉันสนใจคำสั่งหรือคุณสมบัติทุบตีที่จะให้ฉันเปลี่ยนเส้นทาง stderr (แต่ไม่ใช่เคล็ดลับไฟล์ชั่วคราว)
Andrew-Dufresne

@ แอนดรูว์ฉันหมายถึงคำสั่งทำงานตามที่ได้รับการออกแบบมาให้ใช้งานได้ มันก็ไม่ได้ทำงานในแบบที่คุณต้องการให้ :)
phunehehe

ฉันไม่รู้วิธีใดที่สามารถเปลี่ยนเส้นทางข้อผิดพลาดของคำสั่งไปยังอินพุตมาตรฐานของอีกอันหนึ่งได้ คงจะน่าสนใจถ้ามีคนชี้ให้เห็น
phunehehe

2

คุณสามารถสลับลำธารได้ สิ่งนี้จะช่วยให้คุณสามารถgrepสตรีมข้อผิดพลาดมาตรฐานดั้งเดิมในขณะที่ยังได้รับเอาต์พุตที่ไปยังเอาต์พุตมาตรฐานในเทอร์มินัลเดิม:

somecommand 3>&2 2>&1 1>&3- | grep 'pattern'

สิ่งนี้ทำงานได้โดยการสร้างไฟล์ descriptor ใหม่ (3) เปิดขึ้นมาเพื่อเอาท์พุทและตั้งค่าเป็นสตรีมข้อผิดพลาดมาตรฐาน ( 3>&2) จากนั้นเราเปลี่ยนเส้นทางข้อผิดพลาดมาตรฐานไปยังเอาต์พุตมาตรฐาน ( 2>&1) ในที่สุดเอาท์พุทมาตรฐานจะถูกเปลี่ยนเส้นทางไปยังข้อผิดพลาดมาตรฐานดั้งเดิมและตัวบ่งชี้ไฟล์ใหม่ถูกปิด ( 1>&3-)

ในกรณีของคุณ:

ffmpeg -i 01-Daemon.mp3 3>&2 2>&1 1>&3- | grep -i Duration

ทดสอบมัน:

$ ( echo "error" >&2; echo "output" ) 3>&2 2>&1 1>&3- | grep "error"
output
error

$ ( echo "error" >&2; echo "output" ) 3>&2 2>&1 1>&3- | grep -v "error"
output

1

ฉันชอบใช้rcเชลล์ในกรณีนี้

ก่อนติดตั้งแพคเกจ (มันน้อยกว่า 1MB)

นี่คือตัวอย่างของวิธีที่คุณจะทิ้งstdoutและstderrไปยัง grep ในrc:

find /proc/ >[1] /dev/null |[2] grep task

คุณสามารถทำได้โดยไม่ทิ้ง Bash:

rc -c 'find /proc/ >[1] /dev/null |[2] grep task'

อย่างที่คุณอาจสังเกตเห็นว่าซินแท็กซ์ตรงไปตรงมาซึ่งทำให้นี่เป็นทางออกที่ฉันต้องการ

คุณสามารถระบุไฟล์ descriptor ที่คุณต้องการไพพ์ภายในเครื่องหมายวงเล็บหลังตัวอักษรไพพ์

ตัวอธิบายไฟล์มาตรฐานถูกกำหนดหมายเลขไว้เช่น:

  • 0: อินพุตมาตรฐาน
  • 1: ouptut มาตรฐาน
  • 2: ข้อผิดพลาดมาตรฐาน

0

การเปลี่ยนแปลงในตัวอย่างกระบวนการย่อย bash:

echo สองบรรทัดไปที่ stderr และที stderr ไปที่ไฟล์, grep the tee และ pipe back out to stdout

(>&2 echo -e 'asdf\nfff\n') 2> >(tee some.load.errors | grep 'fff' >&1)

stdout:

fff

some.load.errors (เช่น stderr):

asdf
fff

-1

ลองคำสั่งนี้

  • สร้างไฟล์ด้วยชื่อสุ่ม
  • ส่งออกไปยังไฟล์
  • ส่งเนื้อหาของไฟล์ไปที่ไพพ์
  • grep

ceva -> ชื่อตัวแปร (อังกฤษ = บางอย่าง)

ceva=$RANDOM$RANDOM$RANDOM; ffmpeg -i qwerty_112_0_0_record.flv 2>$ceva; cat $ceva | grep Duration; rm $ceva;

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