จะทำให้ไพพ์ไลน์รอการสิ้นสุดไฟล์หรือหยุดหลังจากเกิดข้อผิดพลาดได้อย่างไร?


12

ฉันลองคำสั่งต่อไปนี้หลังจากดูวิดีโอนี้ใน shenanigans ไปป์

man -k . | dmenu -l 20 | awk '{print $1}' | xargs -r man -Tpdf | zathura -

โดยทั่วไปจะพิมพ์รายการ manpages ไปที่ dmenu เพื่อให้ผู้ใช้เลือกหนึ่งรายการจากนั้นจะใช้ xargs เพื่อเรียกใช้man -Tpdf %(พิมพ์ไปยัง stdout PDF ของ manpage git จากอินพุต xargs) และส่ง pdf ไปยังโปรแกรมอ่าน pdf (zathura )

ปัญหาคือ (ตามที่คุณเห็นในวิดีโอ) ตัวอ่าน pdf เริ่มต้นก่อนที่ฉันจะเลือก manpage หนึ่งใน dmenu และถ้าฉันคลิก Esc และเลือกไม่มีตัวอ่าน pdf จะยังคงเปิดอยู่โดยไม่แสดงเอกสารเลย

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


1
คุณใช้เปลือกอะไร ทุบตีนี้หรือไม่
terdon

ฉันได้ลองใช้ bash, zsh และ sh พวกเขาทั้งหมดมีพฤติกรรมเดียวกัน
Seninha

2
ใช่พฤติกรรมนั้นเป็นมาตรฐานฉันถามว่าเชลล์pipefailตัวไหนเพราะตัวเลือกของ bash ที่กล่าวถึงในคำตอบของ Kusalandanda
terdon

คำตอบ:


12

ฉันจะทำให้โปรแกรมอ่าน pdf (และคำสั่งอื่น ๆ ในห่วงโซ่ท่อ) ทำงานเฉพาะเมื่ออินพุตของไฟล์ถึงจุดสิ้นสุดไฟล์หรือเมื่อได้รับอินพุตเลย?

มีifne(ใน Debian มันอยู่ในmoreutilsแพ็คเกจ):

ifne รันคำสั่งต่อไปนี้หากว่าอินพุตมาตรฐานไม่ว่างเปล่า

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

 | ifne zathura -

ขอบคุณสำหรับคำตอบฉันไม่ทราบคำสั่งนี้! คำสั่งนี้ (และอื่น ๆ ในmoreutils) ควรอยู่ใน Unix ดั้งเดิมและระบุโดย posix ... มันช่างเป็นเครื่องมือพื้นฐานและ Unix-ish ...
27719

@Seninha ความเรียบง่ายของifneมันเป็นการหลอกลวงเล็กน้อย Unix ไม่มีการดำเนินการ "pipe peek" ดังนั้นifneต้องอ่านอย่างน้อยหนึ่งไบต์ก่อนตัดสินใจใช้คำสั่ง dependent นั่นหมายความว่ามันไม่สามารถทำแบบทดสอบและคำสั่ง exec แต่ต้องสร้างอีกท่อแยกกระบวนการที่จะเรียกใช้คำสั่งขึ้นอีกและคัดลอกกระแสทั้งจากท่อ stdin ไปยังท่อปลายน้ำ หากกรณี "การป้อนข้อมูลว่างเปล่า" ไม่ใช่เรื่องปกติifneอาจช่วยประหยัดค่าใช้จ่ายได้มากกว่าทรัพยากรโดยเฉลี่ย

@ Wumpus.Q.Wumbley เป็นตำนาน - คุณไม่จำเป็นต้องอ่านไบต์ใด ๆ เพื่อตรวจสอบว่ามีข้อมูลอยู่ในไพพ์หรือไม่ ดูที่นี่ และบน Linux คุณสามารถดูข้อมูลจากไพพ์ (เช่นอ่านข้อมูลโดยไม่ต้องลบออก) ฉันได้พูดถึงสิ่งนี้และอีกมากมายในความคิดเห็นต่อคำตอบ "บัญญัติ" ที่นี่ แต่พวกเขาถูกลบออกโดย mods เพราะพวกเขาอาจรู้สึกว่าข้อเท็จจริงเหล่านั้นเป็นเหมือนการเบี่ยงเบนจากความสุดยอดของคำตอบ
mosvy

6

ไฟล์ Pdf ควรจะหาได้; โปรแกรมดู pdf ใด ๆ จะต้องดูตัวอย่างก่อนและข้ามไปที่ออฟเซ็ตจากตาราง xref

เนื่องจากไม่สามารถค้นหาไพพ์ได้zathuraจึงใช้เคล็ดลับที่ทำให้สับสนซึ่งเป็นการคัดลอกอินพุตทั้งหมดไปยังไฟล์ชั่วคราวจากนั้นใช้ไฟล์ชั่วคราวดังกล่าวตามปกติ เคล็ดลับ "ฉลาด" แบบนี้กำลังสร้างความหวังที่ผิดพลาดและกำลังนำผู้คนให้คิดว่าไฟล์ pdf นั้นสามารถสตรีมได้

แต่ anyways, zathuraจริงๆไม่รอ EOF ก่อนแสดงเอกสารที่คุณไม่ต้องทำอะไรเพื่อที่จะ hapen:

(sleep 10; cat file.pdf) | zathura -
# will really show the content of file.pdf after 10 seconds

ปัญหาคือzathuraไม่มีตัวเลือกให้เปิดเฉพาะหน้าต่างถ้าไฟล์ตกลงและออกโดยมีข้อผิดพลาดหากไม่ใช่ในกรณี - มันจะอยู่ที่นั่นราวกับว่าทุกอย่างตกลง:

$ dd if=file.pdf bs=50000 count=1 status=none | zathura -
error: could not open document  # its window still hanging around showing nothing

$ echo $?
0  # really?

ดังนั้นแม้ว่าคุณจะเปลี่ยนเส้นทางการส่งออกไปยังไฟล์ชั่วคราวด้วยตัวเองและจะทำงานต่อzathuraเมื่อทุกอย่างเรียบร้อยไม่มีการรับประกันว่าผู้ใช้จะไม่ได้รับบริการด้วยหน้าต่างสีดำหากzathuraไม่ชอบผลลัพธ์ด้วยเหตุผลใดก็ตาม .


Btw,

man -X man

จะแสดง manpage ในหน้าต่าง X11 ด้วยgxditviewแม้ว่ามันจะดูตรงจาก '70 ;-)

และแน่นอนคุณสามารถใช้:

... | xargs xterm -e man

ซึ่งนอกเหนือจากการปรับปรุงอื่น ๆ อีกมากมายจะช่วยให้คุณใช้นิพจน์ทั่วไปในการค้นหาและการเลือกข้อความที่เหมาะสม


6

คำสั่งทั้งหมดในไปป์ไลน์เริ่มต้นสวยมากในเวลาเดียวกัน เป็นเพียง I / O ผ่านไปป์ที่ซิงโครไนซ์มัน นอกจากนี้ไพพ์สามารถเก็บข้อมูลได้มากเท่าที่บัฟเฟอร์ของไพพ์อนุญาต

คุณจึงไม่สามารถหลีกเลี่ยงการใช้ขั้นตอนเดียวของไปป์ไลน์เนื่องจาก

  1. คำสั่งในสเตจนั้นเริ่มทันทีที่สเตจอื่นเริ่มทำงานแล้วและ
  2. หากคำสั่งไม่ใช้อินพุตที่เข้ามาในไพพ์มันจะบล็อกขั้นตอนก่อนหน้าของไพพ์ไลน์

ให้เขียนเอาต์พุตไปที่ไฟล์แทนขณะที่ให้ไพพ์ไลน์เสร็จ จากนั้นใช้ไฟล์นั้น

ตัวอย่าง (เป็นฟังก์ชันรับอาร์กิวเมนต์หนึ่งตัว):

myman () {
    tmpfile=$( mktemp )

    if man -k "$1" | dmenu -l 20 | awk '{print $1}' | xargs -r man -Tpdf >"$tmpfile" && [ -s  "$tmpfile" ]
    then
        zathura "$tmpfile"
    fi

    rm -f "$tmpfile"
}

นอกจากนี้จะไม่เรียกใช้zathuraโปรแกรมหากไพพ์ไลน์ล้มเหลว ( xargsส่วนที่ส่งคืนไม่ใช่ศูนย์) หรือไฟล์ที่สร้างขึ้นนั้นว่างเปล่า

ในbashเชลล์คุณอาจต้องการตั้งค่าpipefailตัวเลือกเชลล์ด้วยset -o pipefailเพื่อให้ไพพ์ไลน์ส่งคืนสถานะการออกของคำสั่งแรกในไพพ์ไลน์ที่ล้มเหลว และคุณต้องการสร้างtmpfileตัวแปรlocal:

myman () {
    local tmpfile=$( mktemp )

    if [ -o pipefail ]; then
        set -o pipefail
        trap 'set +o pipefail' RETURN
    fi

    if man -k "$1" | dmenu -l 20 | awk '{print $1}' | xargs -r man -Tpdf >"$tmpfile"
    then
        zathura "$tmpfile"
    fi

    rm -f "$tmpfile"
}

นี่เป็นการตั้งค่าpipefailตัวเลือกสำหรับช่วงเวลาของฟังก์ชั่นหากยังไม่ได้ตั้งค่าและยกเลิกการเลือกถ้าจำเป็น มันจะกำจัดการ-sทดสอบไฟล์เอาต์พุต


1
ทำไมrm -f? คุณคิดถึงกรณีที่ไพพ์เปลี่ยนการอนุญาตของ tmpfile หรือไม่?
terdon

2
@terdon ฉันกำลังนึกถึงกรณีที่มีการลบไฟล์ชั่วคราวก่อนกำหนด rm -fจะไม่เกิดข้อผิดพลาดหากไฟล์ถูกลบไปแล้ว (อาจเป็นไปได้โดยzathuraฉันไม่รู้)
Kusalananda

ฟังก์ชั่นแรกใช้งานไม่ได้ตามที่คาดไว้: มันจะทำให้ zathura แสดงหน้าต่างสีดำ แต่ตอนนี้ zathura ทำงานหลังจากเสร็จไปป์ไลน์ นี่เป็นเพราะไปป์ไลน์ส่งคืนสถานะทางออกของ xargs ซึ่งเป็น 0 คำสั่งที่ล้มเหลวในไปป์ไลน์คือ dmenu (ซึ่งจะคืนค่า 1 เมื่อฉันเลือกอะไรเลย) ฟังก์ชั่นทุบตีกับpipefailตัวเลือกใช้งานได้ตามที่คาดไว้ (และใน zsh ด้วยซึ่งมีตัวเลือกเดียวกัน)
Seninha

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