ท่อ {รายการ; } ใช้ได้กับบางโปรแกรมเท่านั้น


13

ต้องการคำอธิบายจากผู้ใช้ระดับสูงสำหรับพฤติกรรมที่คาดเดาไม่ได้เช่น:

ps -eF | { head -n 1;grep worker; }
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
root       441     2  0     0     0   2 paź15 ?       00:00:00 [kworker/2:1H]

ทุกอย่างดูโอเคในขณะที่

ls -la / | { head -n 1;grep sbin; }

แสดงเฉพาะเอาต์พุตจาก head

... ฉันคิดstdout 2>&1และไม่ทำงานไม่ได้สำหรับฉันมันแปลกคำอธิบายใด ๆ หรือแนะนำวิธีจัดการกับมัน?


1
คนสุดท้ายควรพิมพ์ทุกอย่าง headและgrepทำอะไรที่นั่น
จอร์แดน

ใช่คุณถูก. แต่แทนที่จะเป็นเหตุผลทำไม ps -eF ทำงานในขณะที่ ls -la / ไม่?
ast

คำตอบ:


9

ฉันใช้การตรวจสอบบางอย่างstraceและดูเหมือนว่าเป็นเพราะโปรแกรมที่อยู่ทางด้านซ้ายของไปป์ไลน์นั้นเขียนถึงขั้ว เมื่อคำสั่งจะถูกดำเนินการมันจะเขียนข้อมูลทั้งหมดในครั้งเดียวls write()ทำให้headการบริโภค stdin ทั้งหมด

ในทางกลับกันpsเขียนข้อมูลเป็นแบตช์ดังนั้นข้อมูลแรกเท่านั้นที่write()จะถูกใช้โดยheadและจากนั้นจะมีอยู่ การเรียกภายหลังเพื่อwrite()ไปยังgrepกระบวนการที่เพิ่งเกิดใหม่

ซึ่งหมายความว่ามันจะไม่ทำงานหากกระบวนการที่คุณพยายามgrepหาไม่ได้เกิดขึ้นในครั้งแรกwrite()เนื่องจากgrepไม่ได้รับข้อมูลทั้งหมด (จะเห็นน้อยกว่าข้อมูลที่ลบบรรทัดแรก)

นี่คือตัวอย่างของการพยายาม grep สำหรับ pid 1 ในระบบของฉัน:

$ ps -eF | { head -n2; }
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD
root         1     0  0  1697  3768   2 Oct03 ?        00:00:03 /lib/systemd/systemd
$ ps -eF | grep '/lib/systemd/systemd$'
root         1     0  0  1697  3768   2 Oct03 ?        00:00:03 /lib/systemd/systemd
$ ps -eF | { head -n1; grep '/lib/systemd/systemd$'; }
UID        PID  PPID  C    SZ   RSS PSR STIME TTY          TIME CMD

ps -eFตัวอย่างของคุณใช้ได้โดยบังเอิญเท่านั้น


การยกระดับที่ยอดเยี่ยมและครอบคลุมขอบคุณมาก
ast

1
จริงๆแล้วมันเป็นเรื่องของสภาพการแข่งขันมากกว่า เป็นเพียงว่ามันช้ากว่าการwrite()โทรหลายสาย ถ้าheadได้ช้าที่จะดำเนินการก็read()โทร (เช่นว่าบัฟเฟอร์ท่อมีข้อมูลทั้งหมดในนั้น) ก็จะแสดงพฤติกรรมเดียวกันทั้งในและls ps
Patrick

6

สิ่งนี้เกิดจากการบัฟเฟอร์ใน glibc ในกรณีของการส่งออกอยู่ในบัฟเฟอร์ภายในหนึ่งและเป็นเช่นนี้ถูกส่งผ่านไปเพียงเพื่อls headสำหรับps -eFการส่งออกมีขนาดใหญ่และดังนั้นเมื่อheadเสร็จสิ้นต่อไปนี้จะgrepได้รับส่วนที่เหลือของ ( แต่ไม่ใช่ทั้งหมด) psการส่งออกของ

คุณสามารถกำจัดมันได้โดยยกเลิกการบัฟเฟอร์ไปป์ - ตัวอย่างด้วยsed -u(ฉันไม่แน่ใจว่าไม่ใช่ส่วนขยาย GNU):

$ ls -al / | sed -u "#" | { head -n 1; grep bin; }
total 76
drwxr-xr-x   2 root root  4096 Oct  2 21:52 bin
drwxr-xr-x   2 root root  8192 Oct  3 01:54 sbin

4

สิ่งที่เกิดขึ้นคือhead -n 1อ่านมากกว่า 1 บรรทัด สำหรับทรูพุตที่ดีที่สุดหัวอ่านจะอ่านจำนวนไบต์ดังนั้นมันอาจอ่าน 1024 ไบต์ในแต่ละครั้งจากนั้นมองผ่านไบต์เหล่านั้นเพื่อหาตัวแบ่งบรรทัดแรก เนื่องจากตัวแบ่งบรรทัดอาจเกิดขึ้นในช่วงกลางของ 1024 ไบต์นั้นข้อมูลที่เหลือจะหายไป ไม่สามารถนำกลับไปวางบนท่อได้ ดังนั้นกระบวนการถัดไปที่ดำเนินการจะได้รับเพียงไบต์ 1025 และต่อไป

คำสั่งแรกของคุณเกิดขึ้นเพราะkworkerกระบวนการนี้เกิดขึ้นหลังจากการheadอ่านครั้งแรก

เพื่อให้สามารถใช้งานheadได้จะต้องอ่านตัวอักษรครั้งละ 1 ตัว แต่มันช้ามากดังนั้นจึงไม่เป็นเช่นนั้น
วิธีเดียวที่จะทำสิ่งนี้ได้อย่างมีประสิทธิภาพคือการมีกระบวนการเดียวทำทั้ง "หัว" และ "grep"

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

echo -e '1\n2\n3\n4\n5' | perl -ne 'print if $i++ == 0 || /4/'

หรือ

echo -e '1\n2\n3\n4\n5' | awk '{if (NR == 1 || /4/) print }'

มีมากขึ้น ...


ใช่ฉันรู้จัก 'วิธีการ awk' เพื่อจัดการกับงานนี้ แต่สงสัยว่าทำไมพฤติกรรมจึงคาดเดาไม่ได้กับ {list; } ขอบคุณที่ให้ความกระจ่างว่ามันทำงานอย่างไร ฉันประทับใจกับคำตอบข้างต้นทั้งหมด
AST

2

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

$ ps -eF   | { IFS= read -r x ; echo "$x" ; grep worker; }
$ ls -la / | { IFS= read -r x ; echo "$x" ; grep sbin; }

the readอยู่ในตัวเชลล์และไม่ใช้บัฟเฟอร์ทั้งหมดของอินพุตเพียงแค่ส่งออกหนึ่งบรรทัดดังนั้นการใช้readจะปล่อยให้เอาต์พุตที่เหลือทั้งหมดสำหรับคำสั่งต่อไปนี้

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

$ ps -eF   | { sleep 5 ; head -n 1 ; grep worker; }
$ ls -la / | { sleep 5 ; head -n 1 ; grep sbin; }

ตอนนี้ทั้งสองตัวอย่างข้างต้นล้มเหลวในทางเดียวกัน - The อ่านบัฟเฟอร์ทั้งหมดของการส่งออกเพียงในการผลิตหนึ่งบรรทัดและบัฟเฟอร์ที่ไม่สามารถใช้ได้ต่อไปนี้headgrep

คุณสามารถเห็นปัญหาบัฟเฟอร์ได้ชัดเจนยิ่งขึ้นโดยใช้ตัวอย่างที่กำหนดหมายเลขบรรทัดเอาต์พุตดังนั้นคุณสามารถบอกได้ว่าบรรทัดใดหายไป:

$ ps -eF          | cat -n | { sleep 5 ; head -n 1 ; head ; }
$ ls -la /usr/bin | cat -n | { sleep 5 ; head -n 1 ; head ; }

วิธีง่ายๆในการดูปัญหาการบัฟเฟอร์คือการใช้seqที่สร้างรายการของตัวเลข เราสามารถบอกได้ว่าหมายเลขใดหายไป:

$ seq 1 100000    | { sleep 5 ; head -n 1 ; head ; }
1

1861
1862
1863
1864
1865
1866
1867
1868
1869

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

$ seq 1 100000 | { sleep 5 ; IFS= read -r x ; echo "$x" ; head ; }
1
2
3
4
5
6
7
8
9
10
11

ด้านล่างเป็นตัวอย่างเต็มรูปแบบที่แสดงheadปัญหาการบัฟเฟอร์การแสดงวิธีการ headใช้บัฟเฟอร์ทั้งหมดของเอาต์พุตเพียงเพื่อสร้างห้าบรรทัดในแต่ละครั้ง บัฟเฟอร์ที่ใช้นั้นไม่สามารถใช้ได้กับheadคำสั่งถัดไป ในลำดับ:

$ seq 1 100000 | { sleep 5 ; head -5 ; head -5 ; head -5 ; head -5 ; }
1
2
3
4
5

1861
1862
1863
1864
499
3500
3501
3502
3503
7
5138
5139
5140
5141

จากตัวเลข1861ด้านบนเราสามารถคำนวณขนาดของบัฟเฟอร์ที่ใช้headโดยการนับseqเอาต์พุตจาก1ถึง 1860:

$ seq 1 1860 | wc -c
8193

เราเห็นว่าheadมีการบัฟเฟอร์โดยการอ่าน 8KB (8 * 1024 ไบต์) เต็มรูปแบบของการส่งออกท่อในเวลาแม้กระทั่งการผลิตเพียงไม่กี่บรรทัดของผลผลิตของตัวเอง

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