ฉันควรจะดูแลแมวที่ไม่จำเป็นหรือไม่?


50

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

เปรียบเทียบ

sed s/bla/blaha/ data \
| grep blah \
| grep -n babla

และ

cat data \
| sed s/bla/blaha/ \
| grep blah \
| grep -n babla

วิธีหลังมีประสิทธิภาพน้อยลงหรือไม่ ถ้าเป็นเช่นนั้นความแตกต่างมากพอที่จะใส่ใจว่าสคริปต์รันหรือไม่พูดเป็นวินาทีหรือไม่ ความแตกต่างในการอ่านไม่มาก


30
ฉันใช้เวลามากขึ้นในการดูผู้คนโจมตีซึ่งกันและกันเกี่ยวกับการใช้แมวที่ไร้ประโยชน์ในเว็บไซต์นี้มากกว่าที่ระบบของฉันจะเริ่มกระบวนการแมว
Michael Mrozek

4
@Michael: เห็นด้วย 100% Heck มันเอาฉันเวลามากขึ้นเพื่อเชื่อมโยงไปยังได้รับรางวัล Usenet เก่ากว่าหนึ่งครั้งคอมพิวเตอร์ของฉันเคยจะเสีย catinstantiating อย่างไรก็ตามฉันคิดว่าคำถามที่ใหญ่กว่าที่นี่คือการอ่านรหัสซึ่งมักจะมีความสำคัญมากกว่าประสิทธิภาพ เมื่อเร็วขึ้นสามารถเขียนได้สวยกว่าทำไมไม่? การชี้ให้เห็นปัญหาโดยcatทั่วไปจะนำไปสู่ผู้ใช้ที่มีความเข้าใจที่ดีขึ้นเกี่ยวกับท่อและกระบวนการโดยทั่วไป มันคุ้มค่ากับความพยายามดังนั้นพวกเขาจึงเขียนโค้ดที่เข้าใจได้ในครั้งต่อไป
Caleb

3
ฉันมีเหตุผลอื่นที่ฉันไม่ชอบแบบฟอร์มแรก - ถ้าคุณต้องการเพิ่มคำสั่งอื่นที่จุดเริ่มต้นของไพพ์ไลน์คุณต้องย้ายอาร์กิวเมนต์ด้วยดังนั้นการแก้ไขจึงน่ารำคาญกว่า (แน่นอนว่านี่ไม่ได้หมายความว่าคุณต้องใช้catประเด็นของ Caleb เกี่ยวกับการใช้ฟังก์ชั่นและการเปลี่ยนเส้นทางแก้ปัญหาได้เช่นกัน)
Cascabel


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

คำตอบ:


46

"ความชัดเจน" คำตอบคือแน่นอนมาถึงคุณโดยการใช้ประโยชน์ของcatรางวัล

วัตถุประสงค์ของแมวคือการเชื่อมไฟล์ (หรือ "catenate") เข้าด้วยกัน หากเป็นไฟล์เดียวการต่อไฟล์โดยไม่มีอะไรเลยเป็นการเสียเวลาและค่าใช้จ่ายในการดำเนินการ

แมวสร้างอินสแตนซ์ขึ้นมาเพื่อให้โค้ดของคุณอ่านแตกต่างกันไปสำหรับกระบวนการอีกหนึ่งกระบวนการและสตรีมอินพุต / เอาต์พุตอีกหนึ่งชุดที่ไม่ต้องการ โดยทั่วไปแล้วการค้างในสคริปต์ของคุณจะเป็นลูปที่ไม่มีประสิทธิภาพและการดำเนินการตามจริง ในระบบที่ทันสมัยที่สุดคนหนึ่งเป็นพิเศษcatไม่ได้ไปฆ่าประสิทธิภาพของคุณ แต่มีเกือบเสมอวิธีการเขียนโค้ดของคุณอีก

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

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

sed s/blah/blaha/ < data | pipe

แต่มันไม่จำเป็นต้องเป็นอย่างนั้น มันสามารถมาก่อน เช่นรหัสตัวอย่างของคุณสามารถเขียนได้ดังนี้:

< data \
    sed s/bla/blaha/ |
    grep blah |
    grep -n babla

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

function fix_blahs () {
    sed s/bla/blaha/ |
    grep blah |
    grep -n babla
}

fix_blahs < data

fix_blahs < data | fix_frogs | reorder | format_for_sqlจากนั้นคุณสามารถดำเนินการกับ pipleline ที่อ่านเช่นนี้เป็นเรื่องง่ายที่จะติดตามและส่วนประกอบแต่ละอย่างสามารถดีบักได้ง่ายในฟังก์ชั่นที่เกี่ยวข้อง


26
ฉันไม่รู้ว่า<fileมาก่อนคำสั่ง นี่เป็นการแก้ปัญหาทั้งหมดของฉัน!

3
@Tim: ทั้ง Bash และ Zsh สนับสนุนแม้ว่าฉันคิดว่ามันน่าเกลียด เมื่อฉันกังวลว่ารหัสของฉันจะสวยและบำรุงรักษาได้ฉันมักจะใช้ฟังก์ชั่นในการทำความสะอาด ดูการแก้ไขล่าสุดของฉัน
คาเลบ

8
@ Tim <fileสามารถมาได้ทุกที่บนบรรทัดคำสั่ง: <file grep needleหรือหรือgrep <file needle grep needle <fileข้อยกเว้นคือคำสั่งที่ซับซ้อนเช่นลูปและการจัดกลุ่ม การเปลี่ยนเส้นทางจะต้องมาหลังจากปิดdone/ }/ )/ ฯลฯ @Caleb สิ่งนี้เก็บไว้ในเชลล์ Bourne / POSIX ทั้งหมด และฉันไม่เห็นด้วยว่ามันน่าเกลียด
Gilles 'หยุดความชั่วร้าย'

9
@Gilles ใน bash คุณสามารถแทนที่$(cat /some/file)ด้วย$(< /some/file)ซึ่งทำสิ่งเดียวกัน แต่หลีกเลี่ยงการวางไข่กระบวนการ
cjm

3
เพียงเพื่อยืนยันว่า$(< /some/file)มีข้อ จำกัด ในการพกพา มันทำงานในทุบตี แต่ไม่เถ้า BusyBox เช่นหรือ FreeBSD sh อาจจะไม่ได้ผลเช่นกันเนื่องจากกระสุนสามนัดสุดท้ายนั้นเป็นลูกพี่ลูกน้องสนิท
dubiousjim

22

นี่คือบทสรุปของข้อเสียบางประการของ:

cat $file | cmd

เกิน

< $file cmd
  • ก่อนอื่นหมายเหตุ: มี (โดยเจตนาสำหรับจุดประสงค์ของการสนทนา) ไม่มีเครื่องหมายคำพูดคู่ล้อมรอบ$fileด้านบน ในกรณีcatที่เป็นปัญหาเสมอยกเว้นzsh; ในกรณีของการเปลี่ยนเส้นทางนั่นเป็นเพียงปัญหาสำหรับbashหรือksh88และสำหรับเชลล์อื่น ๆ เฉพาะเมื่อมีการโต้ตอบ (ไม่ใช่ในสคริปต์)
  • ข้อเสียเปรียบที่อ้างถึงบ่อยที่สุดคือกระบวนการพิเศษที่เกิดขึ้น โปรดทราบว่าถ้าcmdเป็น builtin นั่นเป็นกระบวนการ 2 อย่างในเชลล์บางตัวเช่นbashกัน
  • ยังคงอยู่ในส่วนของประสิทธิภาพยกเว้นในเชลล์ที่catมีอยู่แล้วภายในซึ่งยังมีคำสั่งเพิ่มเติมที่ถูกเรียกใช้งาน (และแน่นอนโหลดและเริ่มต้นแล้ว (และไลบรารีที่เชื่อมโยงด้วย)
  • สำหรับประสิทธิภาพในการทำงานของไฟล์ขนาดใหญ่นั่นหมายความว่าระบบจะต้องกำหนดเวลาcatและcmdประมวลผลอีกครั้งและเติมและล้างบัฟเฟอร์ไพพ์อย่างต่อเนื่อง แม้ว่าcmdจะ1GBมีขนาดใหญ่read()สายระบบในเวลาที่ควบคุมจะต้องกลับไปมาระหว่างcatและcmdเพราะท่อไม่สามารถถือมากกว่าหนึ่งในไม่กี่กิโลไบต์ของข้อมูลได้ตลอดเวลา
  • บางcmds (ชอบwc -c) สามารถทำ optimisations บางอย่างเมื่อ stdin ของพวกเขาเป็นไฟล์ปกติที่พวกเขาไม่สามารถทำอะไรกับcat | cmdเป็น stdin ของพวกเขาเป็นเพียงท่อแล้ว ด้วยcatและไปป์ก็หมายความว่าพวกเขาไม่สามารถseek()อยู่ในไฟล์ สำหรับคำสั่งเช่นtacหรือtailที่สร้างความแตกต่างอย่างมากในประสิทธิภาพตามที่หมายความว่าcatพวกเขาจำเป็นต้องเก็บข้อมูลทั้งหมดในหน่วยความจำ
  • cat $fileและแม้กระทั่งรุ่นที่ถูกต้องมากขึ้นcat -- "$file"จะไม่ทำงานอย่างถูกต้องสำหรับชื่อไฟล์บางอย่างที่เฉพาะเจาะจงเช่น-(หรือ--helpหรืออะไรที่เริ่มต้นด้วย-ถ้าคุณลืม--) หากมีใครยืนยันในการใช้catงานเขาควรใช้cat < "$file" | cmdแทนเพื่อความน่าเชื่อถือ
  • หาก$fileไม่สามารถเปิดให้อ่านได้ (ปฏิเสธการเข้าถึงไม่มีอยู่ ... ) < "$file" cmdจะรายงานข้อความแสดงข้อผิดพลาดที่สอดคล้องกัน (โดยเชลล์) และไม่ทำงานcmdในขณะที่cat $file | cmdจะยังคงทำงานอยู่cmdแต่ด้วย stdin ดูเหมือนว่าเป็นไฟล์ว่างเปล่า นั่นก็หมายความว่าในสิ่งที่ชอบ< file cmd > file2, file2ไม่ได้ clobbered ถ้าfileไม่สามารถเปิดได้

2
เกี่ยวกับประสิทธิภาพ: การทดสอบนี้แสดงให้เห็นถึงความแตกต่างตามลำดับ 1% เว้นแต่ว่าคุณกำลังประมวลผลเพียงเล็กน้อยบนสตรีมoletange.blogspot.dk/2013/10/useless-use-of-cat.html
Ole Tange

2
@OleTange นี่คือการทดสอบอื่น: truncate -s10G a; time wc -c < a; time cat a | wc -c; time cat a | cat | wc -c. มีพารามิเตอร์มากมายที่เข้ามาในรูปภาพ การปรับประสิทธิภาพสามารถไปได้ตั้งแต่ 0 ถึง 100% ไม่ว่าในกรณีใดฉันไม่คิดว่าโทษนั้นอาจเป็นค่าลบ
Stéphane Chazelas

2
wc -cเป็นกรณีที่ไม่ซ้ำกันสวยเพราะมีทางลัด หากคุณทำแทนwc -wมันก็เปรียบได้กับgrepตัวอย่างของฉัน (เช่นการประมวลผลที่น้อยมาก - ซึ่งเป็นสถานการณ์ที่ '<' สามารถสร้างความแตกต่างได้)
Ole Tange

@OleTange แม้ ( wc -wในไฟล์กระจัดกระจาย 1GB ใน C locale บน linux 4.9 amd64) จากนั้นฉันพบว่าวิธี cat ใช้เวลาเพิ่มขึ้น 23% เมื่ออยู่บนระบบมัลติคอร์และ 5% เมื่อรวมเข้ากับแกนเดียว แสดงค่าใช้จ่ายเพิ่มเติมที่เกิดขึ้นจากการเข้าถึงข้อมูลโดยมากกว่าหนึ่งคอร์ คุณอาจได้รับผลลัพธ์ที่แตกต่างกันถ้าคุณเปลี่ยนขนาดของไปป์ใช้ข้อมูลต่าง ๆ เกี่ยวข้องกับ I / O จริง ๆ ใช้การใช้ cat ที่ใช้ splice () ... ทั้งหมดยืนยันว่ามีพารามิเตอร์จำนวนมากเข้ามาในรูปภาพ และในกรณีใด ๆcatจะไม่ช่วย
Stéphane Chazelas

1
สำหรับฉันด้วยไฟล์ 1GB wc -wความแตกต่างประมาณ 2% ... ความแตกต่าง 15% ถ้ามันเป็น grep อย่างง่าย ๆ จากนั้นแปลกถ้ามันอยู่ในไฟล์ NFS แชร์มันเร็วกว่าจริง 20% ที่จะอ่านมันถ้าpipedจากcat( gist.github.com/rdp/7162414833becbee5919cda855f1cb86 ) Weird ...
rogerdpack

16

การวาง<fileที่ส่วนท้ายของไพพ์ไลน์นั้นสามารถอ่านได้น้อยกว่าcat fileการเริ่มต้น Natural English อ่านจากซ้ายไปขวา

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

ใช้catรักษาcommand | command | commandรูปแบบ


ฉันยอมรับว่าการใช้<เพียงครั้งเดียวทำให้โค้ดอ่านได้น้อยลง
A.Danischewski

@ Jim คุณสามารถแก้ให้สามารถอ่านได้โดยการสร้างนามแฝงไป<เช่นนี้แล้วใช้เช่นalias load='<' นามแฝงสามารถนำมาใช้ในสคริปต์หลังจากทำงานload file | sed ... shopt -s expand_aliases
niieani

1
ใช่ฉันรู้เกี่ยวกับชื่อแทน อย่างไรก็ตามแม้ว่านามแฝงนี้จะแทนที่สัญลักษณ์ด้วยคำ แต่ผู้อ่านจำเป็นต้องทราบเกี่ยวกับการตั้งค่านามแฝงส่วนตัวของคุณดังนั้นจึงไม่สามารถพกพาได้
Jim

8

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

ในกรณีของสองสิ่งนี้:

sed 's/foo/bar/' somefile
<somefile sed 's/foo/bar/'

เชลล์เริ่มกระบวนการ sed ที่อ่านจาก somefile หรือ stdin (ตามลำดับ) จากนั้นทำการประมวลผลบางอย่าง - มันจะอ่านขึ้นจนกว่าจะถึงบรรทัดใหม่แทนที่ new 'foo' (ถ้ามี) บนบรรทัดนั้นด้วย 'bar' แล้วพิมพ์ บรรทัดนั้นไปยัง stdout และลูป

ในกรณีของ:

cat somefile | sed 's/foo/bar/'

เชลล์วางไข่เป็นกระบวนการ cat และกระบวนการ sed และเชื่อมโยง stdout ของ cat เข้ากับ stdin ของ sed กระบวนการ cat อ่านก้อนขนาดใหญ่หรือขนาดใหญ่หลายกิโลไบต์จากไฟล์จากนั้นเขียนมันลงไปที่ stdout โดยที่ sed sommand รับจากที่นั่นดังตัวอย่างที่สองด้านบน ในขณะที่ sed กำลังประมวลผล chunk นั้นแมวกำลังอ่าน chunk อื่นและเขียนลงใน stdout เพื่อให้ sed ทำงานได้ในครั้งต่อไป

กล่าวอีกนัยหนึ่งงานพิเศษที่จำเป็นโดยการเพิ่มcatคำสั่งไม่ใช่แค่งานพิเศษของการวางไข่catกระบวนการพิเศษแต่ยังเป็นงานพิเศษในการอ่านและเขียนไบต์ของไฟล์สองครั้งแทนที่จะเป็นหนึ่งครั้ง ตอนนี้การพูดจริงและในระบบที่ทันสมัยนั้นไม่ได้สร้างความแตกต่างอย่างมาก - มันอาจทำให้ระบบของคุณทำงานได้โดยไม่จำเป็นเพียงไม่กี่ไมโครวินาที แต่ถ้าเป็นสคริปต์ที่คุณวางแผนที่จะเผยแพร่ผู้คนที่ใช้สคริปต์บนเครื่องที่มีความสามารถต่ำกว่าเกณฑ์ที่กำหนดไว้สักสองสามไมโครวินาทีสามารถรวมกันเป็นจำนวนมากได้


2
ดูoletange.blogspot.dk/2013/10/useless-use-of-cat.htmlcatสำหรับการทดสอบของค่าใช้จ่ายของการใช้ที่เพิ่มขึ้น
Ole Tange

@OleTange: ฉันเพิ่งเจอสิ่งนี้และเยี่ยมชมบล็อกของคุณ (1) ในขณะที่ฉันเห็นเนื้อหา (ส่วนใหญ่) เป็นภาษาอังกฤษฉันเห็นคำหลายคำใน (ฉันเดา) ภาษาเดนมาร์ก: "Klassisk", "Flipcard", "Magasin", "Mosaik", "Sidebjælke", "Øjebliksbillede" , "Tidsskyder", "Blog-arkiv", "Om mig", "Skrevet" และ "Vis kommentarer" (แต่“ Tweet”,“ Like” และแบนเนอร์คุกกี้เป็นภาษาอังกฤษ) คุณรู้เกี่ยวกับสิ่งนี้และมันอยู่ภายใต้การควบคุมของคุณ? (2) ฉันมีปัญหาในการอ่านตารางของคุณ (2a) เนื่องจากเส้นตารางไม่สมบูรณ์และ (2b) ฉันไม่เข้าใจความหมายของ“ Diff (pct)”
G-Man

blogspot.dk ดำเนินการโดย Google ลองแทนที่ด้วย blogspot.com "Diff (pct)" คือ ms ที่catหารด้วย ms โดยไม่มีหน่วยเป็นcatเปอร์เซ็นต์ (เช่น 264 ms / 216 ms = 1.22 = 122% = 22% ช้ากว่าด้วยcat)
Ole Tange
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.