เหตุใดการใช้เชลล์ลูปเพื่อประมวลผลข้อความจึงถือว่าไม่ดี?


196

การใช้วนลูปในการประมวลผลข้อความโดยทั่วไปถือว่าเป็นแนวปฏิบัติที่ไม่ดีในเชลล์ POSIX หรือไม่

ในฐานะที่เป็นStéphane Chazelas ชี้ให้เห็นบางส่วนของสาเหตุของการไม่ได้ใช้ห่วงเปลือกความคิด , ความน่าเชื่อถือ , ความสวย , ประสิทธิภาพการทำงานและการรักษาความปลอดภัย

คำตอบนี้อธิบายถึงความน่าเชื่อถือและแง่มุมที่ชัดเจน :

while IFS= read -r line <&3; do
  printf '%s\n' "$line"
done 3< "$InputFile"

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

ด้านแนวคิดและความปลอดภัยเป็นอย่างไร?


ที่เกี่ยวข้อง (อีกด้านหนึ่งของเหรียญ): การyesเขียนลงไฟล์เร็วขนาดไหน?
สัญลักษณ์แทน

1
built-in shell อ่านไม่ได้อ่านทีละตัวอักษร แต่อ่านได้ครั้งละหนึ่งบรรทัด wiki.bash-hackers.org/commands/builtin/read
A.Danischewski

@ A.Danischewski: มันขึ้นอยู่กับเปลือกของคุณ ในbashมันจะอ่านขนาดบัฟเฟอร์ครั้งละหนึ่งลองdashตัวอย่าง ดูเพิ่มเติมunix.stackexchange.com/q/209123/38906
cuonglm

คำตอบ:


256

ใช่เราเห็นหลายสิ่งเช่น:

while read line; do
  echo $line | cut -c3
done

หรือแย่กว่านั้น:

for line in `cat file`; do
  foo=`echo $line | awk '{print $2}'`
  echo whatever $foo
done

(อย่าหัวเราะฉันเคยเห็นหลายคน)

โดยทั่วไปจากผู้เริ่มต้นเชลล์สคริปต์ สิ่งเหล่านี้คือคำแปลที่ไร้เดียงสาของสิ่งที่คุณจะทำในภาษาที่จำเป็นเช่น C หรือ python แต่นั่นไม่ใช่วิธีที่คุณทำสิ่งต่างๆใน shells และตัวอย่างเหล่านั้นไม่มีประสิทธิภาพมากและไม่น่าเชื่อถืออย่างสมบูรณ์ (อาจนำไปสู่ปัญหาด้านความปลอดภัย) เพื่อแก้ไขข้อบกพร่องส่วนใหญ่รหัสของคุณจะอ่านไม่ออก

แนวคิด

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

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

หนึ่งในสิ่งที่ดีที่ใช้ระบบปฏิบัติการยูนิกซ์แนะนำเป็นท่อและผู้เริ่มต้น stdin / stdout / stderr ลำธารว่าคำสั่งทั้งหมดจัดการโดยค่าเริ่มต้น

ใน 45 ปีที่ผ่านมาเราไม่พบว่าดีกว่า API นั้นในการควบคุมพลังของคำสั่งและให้ความร่วมมือกับงาน นั่นอาจเป็นเหตุผลหลักว่าทำไมผู้คนยังคงใช้กระสุนในปัจจุบัน

คุณมีเครื่องมือตัดและเครื่องมือถอดเสียงและคุณสามารถทำได้:

cut -c4-5 < in | tr a b > out

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

เรียกใช้เครื่องมือว่ามีค่าใช้จ่าย (และเราจะพัฒนาสิ่งนั้นในจุดประสิทธิภาพ) เครื่องมือเหล่านั้นอาจถูกเขียนด้วยคำแนะนำนับพันใน C. กระบวนการจะต้องมีการสร้างเครื่องมือจะต้องมีการโหลดเริ่มต้นแล้วทำความสะอาดขึ้นกระบวนการทำลายและรอ

การกล่าวอ้างcutเป็นเหมือนการเปิดลิ้นชักครัวใช้มีดใช้ล้างมันเช็ดให้แห้งใส่มันกลับเข้าไปในลิ้นชัก เมื่อคุณทำ:

while read line; do
  echo $line | cut -c3
done < file

มันเหมือนไฟล์แต่ละบรรทัดเอาreadเครื่องมือออกมาจากลิ้นชักห้องครัว (อันที่ซุ่มซ่ามเพราะมันไม่ได้ถูกออกแบบมาสำหรับมัน ) อ่านบรรทัดล้างเครื่องมืออ่านของคุณวางมันกลับเข้าไปในลิ้นชัก จากนั้นกำหนดเวลาการประชุมสำหรับเครื่องมือechoและcutรับพวกเขาออกมาจากลิ้นชักเรียกพวกเขาล้างพวกเขาให้แห้งนำพวกเขากลับมาไว้ในลิ้นชักและอื่น ๆ

เครื่องมือเหล่านี้บางตัว ( readและecho) ถูกสร้างขึ้นในเชลล์ส่วนใหญ่ แต่แทบจะไม่สามารถสร้างความแตกต่างได้ที่นี่ตั้งแต่echoและcutยังคงต้องทำงานในกระบวนการแยกต่างหาก

มันเหมือนกับการตัดหัวหอม แต่การล้างมีดของคุณและนำกลับมาใส่ในลิ้นชักห้องครัวระหว่างแต่ละชิ้น

วิธีที่ชัดเจนคือการนำcutเครื่องมือของคุณออกมาจากลิ้นชักหั่นหัวหอมใหญ่ทั้งหมดของคุณแล้วนำกลับมาใส่ในลิ้นชักหลังจากทำงานทั้งหมดเสร็จแล้ว

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

อ่านเพิ่มเติมในคำตอบที่ดีของบรูซ เครื่องมือภายในการประมวลผลข้อความระดับต่ำในเชลล์ (ยกเว้นอาจจะzsh) มี จำกัด ยุ่งยากและโดยทั่วไปไม่เหมาะสำหรับการประมวลผลข้อความทั่วไป

ประสิทธิภาพ

ดังที่ได้กล่าวไว้ก่อนหน้านี้การรันหนึ่งคำสั่งมีค่าใช้จ่าย ค่าใช้จ่ายมากถ้าคำสั่งนั้นไม่ได้สร้างขึ้น แต่ถึงแม้ว่าพวกเขาจะสร้างขึ้นในราคาที่มีขนาดใหญ่

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

นอกจากนี้เชลล์ยังรันคำสั่งในกระบวนการแยกต่างหาก Building Block เหล่านั้นจะไม่แชร์หน่วยความจำหรือสถานะทั่วไป เมื่อคุณทำfgets()หรือfputs()ใน C นั่นคือฟังก์ชั่นใน stdio stdio เก็บบัฟเฟอร์ภายในไว้สำหรับอินพุตและเอาต์พุตสำหรับฟังก์ชัน stdio ทั้งหมดเพื่อหลีกเลี่ยงการโทรบ่อยครั้ง

ที่สอดคล้องกันแม้แต่สาธารณูปโภคเปลือก builtin ( read, echo, printf) ไม่สามารถทำเช่นนั้น readมีวัตถุประสงค์เพื่ออ่านหนึ่งบรรทัด หากอ่านผ่านอักขระบรรทัดใหม่นั่นหมายความว่าคำสั่งถัดไปที่คุณรันจะพลาด ดังนั้นreadต้องอ่านอินพุตทีละหนึ่งไบต์ (การใช้งานบางอย่างมีการปรับให้เหมาะสมถ้าอินพุตเป็นไฟล์ปกติที่พวกเขาอ่านชิ้นและค้นหากลับ แต่ใช้งานได้กับไฟล์ปกติbashเท่านั้นและเช่นอ่าน 128 ไบต์ซึ่งเป็น ยังคงน้อยกว่าโปรแกรมอรรถประโยชน์ข้อความอย่างมาก)

ด้านเดียวกับเอาท์พุทechoไม่สามารถบัฟเฟอร์เอาต์พุตเพียงอย่างเดียว แต่ต้องส่งออกทันทีเนื่องจากคำสั่งถัดไปที่คุณรันจะไม่แชร์บัฟเฟอร์นั้น

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

ระหว่างwhile readลูปนั้นและเทียบเท่า (ควร) cut -c3 < fileในการทดสอบอย่างรวดเร็วของฉันมีอัตราส่วนเวลาซีพียูประมาณ 40000 ในการทดสอบของฉัน (หนึ่งวินาทีต่อครึ่งวัน) แต่แม้ว่าคุณจะใช้เชลล์บิลด์อินเท่านั้น:

while read line; do
  echo ${line:2:1}
done

(ที่นี่ด้วยbash) ซึ่งยังคงอยู่ประมาณ 1: 600 (หนึ่งวินาทีกับ 10 นาที)

ความน่าเชื่อถือ / ความชัดเจน

มันยากมากที่จะทำให้รหัสนั้นถูกต้อง ตัวอย่างที่ฉันให้เห็นบ่อยเกินไปในป่า แต่มีข้อบกพร่องมากมาย

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

ด้วยค่าเริ่มต้นของ$IFSบนอินพุตเช่น:

   foo\/bar \
baz
biz

read lineจะเก็บ"foo/bar baz"เข้า$lineไม่ได้" foo\/bar \"ที่คุณคาดหวัง

หากต้องการอ่านบรรทัดคุณจำเป็นต้องใช้:

IFS= read -r line

นั่นไม่ใช่วิธีที่ใช้งานง่ายมาก แต่นั่นเป็นวิธีที่จำได้ว่ากระสุนไม่ได้ถูกใช้งานอย่างนั้น

echoเหมือนกันสำหรับ echoขยายลำดับ คุณไม่สามารถใช้มันสำหรับเนื้อหาที่กำหนดเองเช่นเนื้อหาของไฟล์สุ่ม คุณต้องการprintfที่นี่แทน

และแน่นอนว่ามีการลืมความหมายทั่วไปของตัวแปรที่ทุกคนพูดถึง ดังนั้นมันจึงมากกว่า:

while IFS= read -r line; do
  printf '%s\n' "$line" | cut -c3
done < file

ตอนนี้มีคำเตือนอีกสองสามข้อ:

  • ยกเว้นzshว่าจะไม่ทำงานหากอินพุตมีอักขระ NUL ในขณะที่ยูทิลิตี้ข้อความอย่างน้อย GNU จะไม่มีปัญหา
  • หากมีข้อมูลหลังจากขึ้นบรรทัดใหม่แล้วมันจะถูกข้าม
  • ภายในลูป stdin จะถูกเปลี่ยนเส้นทางดังนั้นคุณต้องให้ความสนใจว่าคำสั่งในนั้นไม่อ่านจาก stdin
  • สำหรับคำสั่งภายในลูปเราไม่ได้สนใจว่ามันจะสำเร็จหรือไม่ โดยปกติแล้วข้อผิดพลาด (ดิสก์เต็มอ่านข้อผิดพลาด ... ) เงื่อนไขจะได้รับการจัดการไม่ดีมักจะไม่ดีกว่าที่เทียบเท่าที่ถูกต้อง

หากเราต้องการแก้ไขปัญหาเหล่านี้บางส่วนข้างต้นนั่นจะกลายเป็น:

while IFS= read -r line <&3; do
  {
    printf '%s\n' "$line" | cut -c3 || exit
  } 3<&-
done 3< file
if [ -n "$line" ]; then
    printf '%s' "$line" | cut -c3 || exit
fi

นั่นกลายเป็นชัดเจนน้อยลง

มีปัญหาอื่น ๆ อีกมากมายที่มีการส่งผ่านข้อมูลไปยังคำสั่งผ่านอาร์กิวเมนต์หรือการดึงเอาท์พุทของพวกเขาในตัวแปร:

  • ข้อ จำกัด เกี่ยวกับขนาดของข้อโต้แย้ง (การใช้งานยูทิลิตี้ข้อความบางอย่างมีข้อ จำกัด เช่นกันแม้ว่าผลของการเข้าถึงเหล่านั้นโดยทั่วไปจะมีปัญหาน้อยกว่า)
  • อักขระ NUL (เช่นปัญหากับยูทิลิตี้ข้อความ)
  • อาร์กิวเมนต์ที่ใช้เป็นตัวเลือกเมื่อเริ่มต้นด้วย-(หรือ+บางครั้ง)
  • นิสัยใจคอต่างๆของคำสั่งต่างๆมักจะใช้ในคนที่เป็นเหมือนลูปexpr, test...
  • ตัวดำเนินการจัดการข้อความ (จำกัด ) ของเชลล์ต่างๆที่จัดการอักขระหลายไบต์ด้วยวิธีที่ไม่สอดคล้องกัน
  • ...

ข้อพิจารณาด้านความปลอดภัย

เมื่อคุณเริ่มทำงานกับตัวแปรเชลล์และอาร์กิวเมนต์ของคำสั่งคุณกำลังเข้าสู่ฟิลด์ของฉัน

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

เมื่อคุณอาจต้องการใช้ลูป

TBD


24
ชัดเจน (เต็มตา) สามารถอ่านได้และมีประโยชน์อย่างยิ่ง ขอบคุณอีกครั้ง. นี่เป็นคำอธิบายที่ดีที่สุดที่ฉันเคยเห็นทุกที่บนอินเทอร์เน็ตสำหรับความแตกต่างพื้นฐานระหว่างเชลล์สคริปต์และการเขียนโปรแกรม
Wildcard

2
มันเป็นข้อความแบบนี้ที่ช่วยให้ผู้เริ่มต้นเรียนรู้เกี่ยวกับ Shell Scripts และเห็นความแตกต่างเล็กน้อย ควรเพิ่มตัวแปรการอ้างอิงเป็น $ {VAR: -default_value} เพื่อให้แน่ใจว่าคุณจะไม่ได้รับค่าว่าง และตั้งค่าคำนาม -o เป็นตะโกนใส่คุณเมื่ออ้างอิงค่าที่ไม่ได้กำหนดไว้
unsignedzero

6
@ A.Danischewski ฉันคิดว่าคุณไม่มีจุด ใช่cutเช่นมีประสิทธิภาพ cut -f1 < a-very-big-fileมีประสิทธิภาพเท่าที่คุณจะได้รับถ้าคุณเขียนเป็น C อะไรที่ไม่มีประสิทธิภาพมากและมีแนวโน้มที่จะเกิดข้อผิดพลาดเกิดขึ้นได้cutสำหรับทุก ๆ บรรทัดa-very-big-fileในวงวนเปลือกหอยซึ่งเป็นจุดที่เกิดขึ้นในคำตอบนี้ ที่เกี่ยวข้องกับคำสั่งสุดท้ายของคุณเกี่ยวกับการเขียนรหัสที่ไม่จำเป็นซึ่งทำให้ฉันคิดว่าบางทีฉันไม่เข้าใจความคิดเห็นของคุณ
Stéphane Chazelas

5
"ใน 45 ปีเราไม่พบดีกว่า API นั้นเพื่อควบคุมพลังของคำสั่งและให้ความร่วมมือกับงาน" - อันที่จริง PowerShell ได้แก้ปัญหาการแยกวิเคราะห์ที่หวั่นไหวด้วยการส่งข้อมูลที่มีโครงสร้างแทนที่จะเป็นสตรีมไบต์ เหตุผลเพียงอย่างเดียวที่เชลล์ยังไม่ได้ใช้ (มีแนวคิดมาพักหนึ่งแล้วและได้ทำการตกผลึกบางครั้งรอบ ๆ Java เมื่อรายการคอนเทนเนอร์มาตรฐานและประเภทพจนานุกรมปัจจุบันกลายเป็นกระแสหลัก) ผู้ดูแลระบบของพวกเขายังไม่เห็นด้วย รูปแบบข้อมูลที่มีโครงสร้างทั่วไปที่จะใช้ (.
ivan_pozdeev

6
@OlivierDulac ฉันคิดว่ามันเป็นเรื่องตลก ส่วนนั้นจะเป็น TBD ตลอดไป
muru

43

เท่าที่ความคิดและความชัดเจนเป็นไปได้เปลือกมักจะสนใจในไฟล์ "addressable unit" ของพวกเขาคือไฟล์และ "address" เป็นชื่อไฟล์ เชลล์มีวิธีทดสอบทุกชนิดสำหรับการมีอยู่ของไฟล์ประเภทไฟล์การจัดรูปแบบชื่อไฟล์ (เริ่มต้นด้วยการวนรอบ) เชลล์มีคำสั่งเบื้องต้นเพียงเล็กน้อยสำหรับการจัดการกับเนื้อหาไฟล์ โปรแกรมเมอร์เชลล์ต้องเรียกใช้โปรแกรมอื่นเพื่อจัดการกับเนื้อหาไฟล์

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


25

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

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

สำหรับส่วนแรก ( initialization) มันไม่สำคัญว่าคำสั่งเชลล์จะทำงานช้า - มันใช้คำสั่งเพียงไม่กี่คำเท่านั้นหรืออาจเป็นลูปสั้น ๆ สองสามคำ แม้ว่าเราจะเขียนส่วนนั้นอย่างไม่มีประสิทธิภาพก็มักจะใช้เวลาน้อยกว่าหนึ่งวินาทีในการเริ่มต้นทั้งหมดและก็ไม่เป็นไร - มันเกิดขึ้นเพียงครั้งเดียวเท่านั้น

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

นั่นคือเมื่อเราจำเป็นต้องใช้เครื่องมืออื่น ๆ และความงามของ Unix shell script คือพวกมันทำให้มันง่ายมากที่เราจะทำเช่นนั้น

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

Unix มีเครื่องมือในตัวที่ยอดเยี่ยมมากมายตั้งแต่แบบเรียบง่ายไปจนถึงแบบซับซ้อนที่เราสามารถใช้เพื่อสร้างท่อของเรา ฉันมักจะเริ่มต้นด้วยวิธีง่าย ๆ และใช้ซับซ้อนกว่าเมื่อจำเป็นเท่านั้น

ฉันจะพยายามใช้เครื่องมือมาตรฐานที่มีอยู่ในระบบส่วนใหญ่และพยายามทำให้การใช้งานของฉันเป็นแบบพกพาแม้ว่ามันจะไม่สามารถทำได้เสมอไป และหากภาษาโปรดของคุณคือ Python หรือ Ruby บางทีคุณอาจไม่สนใจความพยายามเป็นพิเศษในการตรวจสอบให้แน่ใจว่าติดตั้งในทุกแพลตฟอร์มที่ซอฟต์แวร์ของคุณจำเป็นต้องใช้บน :-)

เครื่องมือง่ายๆ ได้แก่head, tail, grep, sort, cut, tr, sed, join(เมื่อผสาน 2 ไฟล์) และawkหนึ่งสมุทรหมู่อื่น ๆ อีกมากมาย มันน่าทึ่งที่บางคนสามารถทำได้ด้วยการจับคู่รูปแบบและsedคำสั่ง

เมื่อมันซับซ้อนมากขึ้นและคุณต้องใช้ตรรกะบางอย่างกับแต่ละบรรทัดawkเป็นตัวเลือกที่ดี - ทั้งซับใน (บางคนใส่สคริปต์ awk ทั้งหมดใน 'หนึ่งบรรทัด' แม้ว่าจะไม่สามารถอ่านได้มาก) หรือใน สคริปต์ภายนอกแบบสั้น

ในฐานะที่awkเป็นภาษาที่ตีความ (เช่นเชลล์ของคุณ) มันวิเศษมากที่สามารถทำการประมวลผลแบบบรรทัดต่อบรรทัดได้อย่างมีประสิทธิภาพ แต่มันสร้างขึ้นเพื่อวัตถุประสงค์นี้และรวดเร็วมาก

แล้วก็มีPerlภาษาสคริปต์อื่น ๆ อีกมากมายที่สามารถประมวลผลไฟล์ข้อความได้ดีและมีไลบรารีที่มีประโยชน์มากมาย

และสุดท้ายก็มี C ตัวเก่าถ้าคุณต้องการความเร็วสูงสุดและความยืดหยุ่นสูง (แม้ว่าการประมวลผลข้อความจะค่อนข้างน่าเบื่อ) แต่อาจเป็นการใช้เวลาของคุณในการเขียนโปรแกรม C ใหม่สำหรับงานประมวลผลไฟล์ที่แตกต่างกันที่คุณเจอ ฉันทำงานกับไฟล์ CSV เป็นจำนวนมากดังนั้นฉันจึงเขียนยูทิลิตีทั่วไปหลายรายการใน C ซึ่งฉันสามารถนำกลับมาใช้ในโครงการต่าง ๆ มากมาย ด้วยเหตุนี้สิ่งนี้จึงขยายขอบเขตของ 'เครื่องมือ Unix ที่เร็วและเร็ว' ที่ฉันสามารถเรียกได้จากเชลล์สคริปต์ของฉันดังนั้นฉันจึงสามารถจัดการโครงการส่วนใหญ่ได้โดยการเขียนสคริปต์เท่านั้นซึ่งเร็วกว่าการเขียนและการดีบักโค้ด C bespoke ทุกครั้ง!

คำแนะนำสุดท้าย:

  • อย่าลืมที่จะเริ่มเชลล์สคริปต์หลักของคุณด้วยexport LANG=Cหรือเครื่องมือมากมายจะจัดการกับไฟล์ธรรมดา ASCII ของคุณเป็น Unicode ทำให้ช้าลงมาก
  • พิจารณาการตั้งค่าexport LC_ALL=Cหากคุณต้องการsortจัดลำดับที่สอดคล้องโดยไม่คำนึงถึงสภาพแวดล้อม!
  • หากคุณต้องการsortข้อมูลของคุณซึ่งอาจใช้เวลามากกว่า (และทรัพยากร: CPU, หน่วยความจำ, ดิสก์) มากกว่าทุกสิ่งดังนั้นลองลดจำนวนsortคำสั่งและขนาดของไฟล์ที่เรียงลำดับ
  • ไปป์ไลน์เดียวหากเป็นไปได้มักจะมีประสิทธิภาพมากที่สุด - การรันหลาย ๆ pipelines ตามลำดับโดยมีไฟล์ตัวกลางอาจอ่านได้และ debug ได้มากขึ้น แต่จะเพิ่มเวลาที่โปรแกรมของคุณใช้

6
ท่อของเครื่องมือง่าย ๆ มากมาย (โดยเฉพาะที่กล่าวถึงเช่นหัว, หาง, grep, การเรียงลำดับ, ตัด, TR, sed, ... ) มักจะใช้โดยไม่จำเป็นโดยเฉพาะถ้าคุณมีอินสแตนซ์ awk ในท่อที่สามารถทำได้ งานของเครื่องมือง่ายๆเหล่านั้นเช่นกัน อีกประเด็นที่ต้องพิจารณาคือในท่อคุณไม่สามารถส่งข้อมูลสถานะจากกระบวนการทางด้านหน้าของไปป์ไลน์ไปยังกระบวนการที่ปรากฏทางด้านหลังได้อย่างน่าเชื่อถือและเชื่อถือได้ ถ้าคุณใช้ไพพ์ไลน์ของโปรแกรมอย่างง่ายโปรแกรม awk คุณมีพื้นที่สถานะเดียว
Janis

14

ใช่ แต่...

คำตอบที่ถูกต้องของStéphane Chazelasจะขึ้นอยู่กับแนวคิดของการมอบหมายการดำเนินงานทุกข้อความไบนารีที่เฉพาะเจาะจงเช่นgrep, awk, sedและอื่น ๆ

เนื่องจากสามารถทำสิ่งต่าง ๆ ได้ด้วยตัวเองการวางส้อมอาจเร็วกว่า (แม้จะใช้ล่ามคนอื่นเพื่อทำงานทั้งหมด)

ตัวอย่างดูที่โพสต์นี้:

https://stackoverflow.com/a/38790442/1765658

และ

https://stackoverflow.com/a/7180078/1765658

ทดสอบและเปรียบเทียบ ...

แน่นอน

ไม่มีการพิจารณาเกี่ยวกับการป้อนข้อมูลของผู้ใช้และความปลอดภัย !

อย่าเขียนเว็บแอปพลิเคชันภายใต้ !!

แต่สำหรับงานการดูแลเซิร์ฟเวอร์จำนวนมากซึ่งสามารถใช้แทนใช้ builtins bash อาจมีประสิทธิภาพมาก

ความหมายของฉัน:

เครื่องมือการเขียนเช่นutils binไม่ใช่งานประเภทเดียวกันกับการดูแลระบบ

ดังนั้นคนไม่เหมือนกัน!

ในกรณีที่sysadminsต้องรู้shellพวกเขาสามารถเขียนต้นแบบโดยใช้เครื่องมือที่เขาต้องการ (และรู้จักกันดีที่สุด)

หากยูทิลิตี้ใหม่ (ต้นแบบ) มีประโยชน์จริง ๆ แล้วบางคนอาจพัฒนาเครื่องมือเฉพาะโดยใช้ภาษาที่เหมาะสมกว่านี้


1
ตัวอย่างที่ดี วิธีการของคุณมีประสิทธิภาพมากกว่า lololux อย่างแน่นอน แต่ให้สังเกตว่าคำตอบของ tenibai (วิธีที่ถูกต้องในการทำ IMO นี้คือไม่ใช้เชลล์วน) เป็นคำสั่งที่มีขนาดเร็วกว่าของคุณ และคุณจะเร็วมากขึ้นหากคุณไม่ได้bashใช้ (เร็วกว่า 3 เท่าด้วย ksh93 ในการทดสอบระบบของฉัน) bashโดยทั่วไปแล้วเป็นเปลือกที่ช้าที่สุด แม้zshจะเร็วเป็นสองเท่าในสคริปต์นั้น คุณยังมีปัญหาเล็กน้อยกับตัวแปร unquoted readและการใช้งานของ ดังนั้นคุณแสดงให้เห็นถึงจุดต่าง ๆ ของฉันที่นี่
Stéphane Chazelas

@ StéphaneChazelasฉันเห็นด้วยbashน่าจะเป็นที่คนเชลล์ที่ช้าที่สุดสามารถใช้วันนี้ แต่ใช้กันอย่างแพร่หลายมากที่สุดต่อไป
F. Hauri

@ StéphaneChazelasฉันโพสต์ข้อความภาษาperlในคำตอบของฉันแล้ว
F. Hauri

1
@Tensibai คุณจะพบPOSIXsh , Awk , Sed , grep, ed, ex, cut, sort, join... ทั้งหมดที่มีความน่าเชื่อถือมากกว่าทุบตีหรือ Perl
Wildcard

1
@Tensibai ของระบบทั้งหมดที่เกี่ยวข้องโดย U&L ส่วนใหญ่ (Solaris, FreeBSD, HP / UX, AIX, ระบบ Linux ที่ฝังตัวส่วนใหญ่ ... ) ไม่ได้bashติดตั้งโดยค่าเริ่มต้น bashส่วนใหญ่พบเฉพาะในแอปเปิ้ล MacOS และระบบ GNU (ฉันคิดว่านั่นคือสิ่งที่คุณเรียกกระจายหลัก ) แต่ระบบจำนวนมากนอกจากนี้ยังมีมันเป็นแพคเกจที่เลือกได้ (เช่นzsh, tcl, python... )
Stéphane Chazelas
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.