การใช้ jq ภายใน chain pipe จะไม่สร้างเอาต์พุต


12

ปัญหาของการjqต้องการตัวกรองที่ชัดเจนเมื่อมีการกล่าวถึงผลลัพธ์ของการเปลี่ยนเส้นทางไปทั่วเว็บ แต่ฉันไม่สามารถเปลี่ยนเส้นทางผลลัพธ์หากjqเป็นส่วนหนึ่งของไพพ์เชนแม้ว่าจะใช้ตัวกรองอย่างชัดเจนก็ตาม

พิจารณา:

touch in.txt
tail -f in.txt | jq '.f1'
# in a different terminal:
echo '{"f1":1,"f2":2}' >> in.txt
echo '{"f1":3,"f2":2}' >> in.txt

ตามที่คาดไว้เอาต์พุตในเทอร์มินัลดั้งเดิมจากjqคำสั่งคือ:

1
3

แต่ถ้าฉันเพิ่มการเปลี่ยนเส้นทางหรือการวางท่อใด ๆ ที่ส่วนท้ายของjqคำสั่งเอาต์พุตจะเงียบ

rm in.txt
touch in.txt
tail -f in.txt | jq '.f1' | tee out.txt
# in a different terminal:
echo '{"f1":1,"f2":2}' >> in.txt
echo '{"f1":3,"f2":2}' >> in.txt

ไม่มีผลลัพธ์ปรากฏในเทอร์มินัลแรกและ out.txt ว่างเปล่า

ฉันลองหลายร้อยรูปแบบแล้ว แต่มันเป็นปัญหาที่เข้าใจยาก วิธีแก้ปัญหาเดียวที่ฉันค้นพบตามที่ค้นพบmosquitto_subและ The Things Network (ซึ่งเป็นที่ที่ฉันค้นพบปัญหา) คือการห่อฟังก์ชันtail และ jq ในเชลล์สคริปต์:

#!/bin/bash
tail -f $1 | while IFS='' read line; do
echo $line | jq '.f1'
done

แล้ว:

./tail_and_jq.sh | tee out.txt
# in a different terminal:
echo '{"f1":1,"f2":2}' >> in.txt
echo '{"f1":3,"f2":2}' >> in.txt

และผลลัพธ์ก็จะปรากฏขึ้น:

1
3

นี่คือการjqติดตั้งล่าสุดผ่าน Homebrew:

$ echo $SHELL
/bin/bash
$ jq --version
jq-1.5
$ brew install jq
Warning: jq 1.5_3 is already installed and up-to-date

นี่เป็นข้อผิดพลาด (ส่วนใหญ่ที่ไม่มีเอกสาร) jqหรือด้วยความเข้าใจของฉันเกี่ยวกับโซ่ท่อหรือไม่?


1
FWIW คุณมีการตั้งค่าที่ค่อนข้างแปลก (ดีเล็กน้อย) ที่นี่tail -fเพื่อใช้ป้อนข้อมูลอย่างต่อเนื่องไปยังโปรแกรมและteeประมวลผลเอาต์พุต หากคุณยังต้องการคำตอบฉันจะแนะนำให้ทำ chain ให้ง่ายขึ้น<in.json jq '.f1' >out.jsonเพื่อที่คุณจะได้ จำกัด สิ่งที่ทำให้มันแคบลง
David Z

ดูเพิ่มเติมที่BashFAQ # 9 - การบัฟเฟอร์คืออะไร หรือเพราะเหตุใดบรรทัดคำสั่งของฉันจึงไม่มีเอาต์พุต:tail -f logfile | grep 'foo bar' | awk ...
Charles Duffy

คำแนะนำที่ดีสำหรับความพยายามในอนาคตขอบคุณ FWIW tailบิตมาจากความพยายามที่จะทำลายไพพ์ลง (เรียกใช้คำสั่งแรกทีและเปลี่ยนเส้นทางไปยังไฟล์หางที่ไพพ์ไปยังคำสั่งถัดไปเปลี่ยนเส้นทางไปยังไฟล์ ฯลฯ ) และเรียกใช้อย่างต่อเนื่องในส่วน <เป็นเครื่องมือที่ดีที่จะเก็บไว้ในใจว่า
Heath Raftery

คำตอบ:


20

เอาต์พุตจากjqถูกบัฟเฟอร์เมื่อเอาต์พุตมาตรฐานถูกไพพ์

ในการขอให้jqล้างบัฟเฟอร์เอาต์พุตหลังจากทุกวัตถุให้ใช้--unbufferedตัวเลือกเช่น

tail -f in.txt | jq --unbuffered '.f1' | tee out.txt

จากjqคู่มือ:

--unbuffered

ล้างเอาต์พุตหลังจากแต่ละอ็อบเจ็กต์ JSON ถูกพิมพ์ (มีประโยชน์ถ้าคุณกำลังไพพ์แหล่งข้อมูลที่ช้าลงjqและjqเอาต์พุตของไพพ์อื่น ๆ )


นอกจากนี้วิธีที่ฉันจะแก้ไขปัญหานี้เพื่อที่จะคิดออกว่าบัฟเฟอร์การส่งออกเป็นปัญหาสมมติว่าฉันจะไม่เพียงแค่เดาว่าจะใช้ส่วน 'jq' ภายใต้ 'ltrace' และ / หรือ 'strace' จะเห็นได้ชัดว่ามันกำลังเรียกฟังก์ชั่นเอาต์พุต C stdio แต่ไม่เรียก syscall การเขียน (2)
AnotherSmellyGeek

1
@AnotherSmellyGeek อาจเป็นไปได้หรือยูทิลิตี้การติดตามที่เทียบเท่ากับ Unices ของเรา (โปรดทราบว่า OP ใช้ Homebrew ซึ่งหมายถึงพวกเขาใช้ macOS และฉันใช้ OpenBSD ไม่มีเครื่องมือ Linux เหล่านี้) ความเป็นไปได้อีกอย่างก็คือการรู้ว่าการบัฟเฟอร์เอาต์พุตอาจเกิดขึ้นได้ในบางกรณี :-)
Kusalananda

สุกใส และขอขอบคุณทุกคำแนะนำเกี่ยวกับการแก้ไขข้อบกพร่องในอนาคต การบัฟเฟอร์เป็นหนึ่งในข้อสงสัยครั้งแรกของฉัน แต่พฤติกรรมที่แตกต่างกันสำหรับการวางท่อกำลังทำให้ความพยายามในการดีบั๊กของฉันค่อนข้างแย่
Heath Raftery

6

สิ่งที่คุณเห็นอยู่ที่นี่คือ C stdio buffering in action มันจะเก็บเอาท์พุทบนบัฟเฟอร์จนกว่าจะถึงขีด จำกัด ที่แน่นอน (อาจเป็น 512 ไบต์หรือ 4KB หรือใหญ่กว่า) จากนั้นส่งทั้งหมดในครั้งเดียว

การบัฟเฟอร์นี้จะถูกปิดใช้งานโดยอัตโนมัติหาก stdout เชื่อมต่อกับเทอร์มินัล แต่เมื่อเชื่อมต่อกับไพพ์ (เช่นในกรณีของคุณ) มันจะเปิดใช้งานลักษณะการบัฟเฟอร์นี้

วิธีปกติในการปิดการใช้งาน / การควบคุมบัฟเฟอร์กำลังใช้งานsetvbuf()ฟังก์ชั่น (ดูคำตอบนี้สำหรับรายละเอียดเพิ่มเติม) แต่ต้องทำในซอร์สโค้ดของjqตัวเองดังนั้นอาจไม่ใช่สิ่งที่ปฏิบัติได้สำหรับคุณ ...

มีวิธีแก้ปัญหา ... (แฮ็คหนึ่งอาจบอกว่า) มีโปรแกรมที่เรียกว่า "unbuffer" ซึ่งแจกจ่ายด้วย "คาดหวัง" ที่สามารถสร้างเทอร์มินัลหลอกและเชื่อมต่อกับโปรแกรม ดังนั้นแม้ว่าjqจะยังคงเขียนไปยังไพพ์ก็จะคิดว่ามันกำลังเขียนไปยังเทอร์มินัลและเอฟเฟกต์การบัฟเฟอร์จะถูกปิดใช้งาน

ติดตั้งแพ็คเกจ "คาดหวัง" ซึ่งควรมาพร้อมกับ "unbuffer" หากคุณยังไม่มี ... ตัวอย่างเช่นบน Debian (หรือ Ubuntu):

$ sudo apt-get install expect

จากนั้นคุณสามารถใช้คำสั่งนี้:

$ tail -f in.txt | unbuffer -p jq '.f1' | tee out.txt

ดูคำตอบนี้สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับ "unbuffer" และคุณสามารถหาหน้าคนได้ที่นี่เช่นกัน


ฉันชอบที่คุณอธิบายว่าเพราะเหตุใดพฤติกรรมที่สังเกตได้เกิดขึ้น แต่เมื่อ Kusalananda ชี้ให้เห็นว่าjqการใช้งานเอาต์พุตที่ไม่มีข้อผิดพลาดจึงไม่จำเป็นต้องมีวิธีแก้ปัญหา
David Z

อ่าดีมาก! ฉันเริ่มมองjqหน้าคน แต่เบื่อไปซักพักแล้วก็ไปทำสิ่งอื่น ... ดีใจที่รู้ว่ามีบางอย่างเช่นนั้น! :-)
filbranden

1
Protip, GNU coreutils มาพร้อมกับstdbuf -o0ซึ่งจะฉีดรหัสผ่าน LD_PRELOAD และทำการsetvbuf()เรียกวิเศษให้คุณ ไม่ว่าจะทำงานบน macOS ฉันไม่แน่ใจ
user1686

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