โปรแกรม Shell ที่มีขนาดใหญ่ที่สุดในโลก
(github.com/oils-for-unix)- วิกินี้ไม่ได้รวบรวมแค่สคริปต์ยาว ๆ แต่เป็นรายชื่อโปรแกรม Shell แบบ “จริงจัง” ที่เขียนด้วยมือและถึงขั้นใช้ โครงสร้างข้อมูลและอัลกอริทึม
- เกณฑ์โดยทั่วไปคือ มากกว่า 5K บรรทัด และจะตัดสคริปต์ที่สร้างอัตโนมัติหรือสคริปต์ completion แบบทำซ้ำ ๆ ออกจากกรณีหลัก
- ตัวอย่างเด่นมีทั้ง ble.sh 87K บรรทัด, kalua ราว 56K SLoC/บรรทัด, Relax-and-Recover 35K บรรทัด, nb 26K บรรทัด, winetricks 22K บรรทัด ซึ่งใหญ่เกินภาพจำของ Shell script ทั่วไปมาก
- ในรายการมี เครื่องมือที่ใช้งานจริง อย่างหลากหลาย เช่น ตัวแก้ไขบรรทัดแบบโต้ตอบ, ส่วนเสริม OpenWRT, ตัวดีบัก Bash, เครื่องมือทดสอบ TLS, การทำ Kubernetes, เครื่องมือสำรองและกู้คืน, เครื่องมือออกใบรับรอง, ตัวมอนิเตอร์ทรัพยากร เป็นต้น
- การทดสอบ OSH “Wild” สามารถพาร์ส Shell ได้มากกว่าหนึ่งล้านบรรทัด แต่ส่วนใหญ่เป็นโปรแกรมเล็กและนิยามแพ็กเกจดิสทริบิวชันแบบทำซ้ำ จึงแยกจากโปรแกรม Shell ขนาดใหญ่
อะไรที่นับว่าเป็น “โปรแกรม Shell ขนาดใหญ่”
- คำว่า “biggest” ในที่นี้หมายถึงความ “substantial” หรือมีขนาดและความซับซ้อนอย่างมีนัยสำคัญ มากกว่าจะวัดจาก จำนวนบรรทัดดิบ อย่างเดียว
- สิ่งที่รวมอยู่โดยหลักคือ สคริปต์ Shell ที่เขียนด้วยมือ
- สคริปต์ขนาดใหญ่ที่ autoconf สร้างขึ้นถือเป็นข้อยกเว้น
- ของที่สร้างอัตโนมัติ เช่น สคริปต์ coreutils 70K บรรทัด จะไม่ถือเป็นโปรแกรม Shell ขนาดใหญ่ในความหมายนี้
- ให้ความสำคัญเป็นพิเศษกับโปรแกรม Shell ที่ใช้ โครงสร้างข้อมูลและอัลกอริทึม
- bash-completion แม้จะซับซ้อน แต่โครงสร้างเป็นแบบทำซ้ำ โดยมีฟังก์ชันค่อนข้างเรียบง่ายสำหรับแต่ละคำสั่งบนเครื่อง Unix จึงเกือบจะเป็นตัวอย่างโต้แย้ง
- เส้นฐานคร่าว ๆ คือ เกิน 5K บรรทัด
- โปรแกรม Shell ที่ใหญ่ที่สุดแบบไม่ทำซ้ำมักอยู่ในช่วง 10K+ บรรทัด
- ยังไม่พบโปรแกรมที่เกิน 100K บรรทัด
ตัวอย่างโปรแกรม Shell ที่ใหญ่ที่สุด
- akinomyoga/ble.sh: รวม 87K บรรทัด, 63K LoC ไม่นับคอมเมนต์
- เป็นตัวแก้ไขบรรทัดแบบโต้ตอบคล้าย fish ที่เขียนด้วย bash ล้วน
- ไฟล์หลัก
out/ble.shมี 39K บรรทัด, 29K LoC ไม่นับคอมเมนต์ และเมื่อรวมไฟล์โมดูลแล้วจะเกิน 80K บรรทัด - มีคอมเมนต์ภาษาญี่ปุ่นจำนวนมาก
- ใช้
bind -xเพื่ออ่าน raw bytes จากเทอร์มินัล, ถอดรหัสเองด้วย explicit state machine หลายตัว, และคงไว้พร้อมอัปเดต drawing buffer - มีทั้ง timing และ “fibers”
- รายละเอียดเกี่ยวกับ Shell parser อยู่ในคอมเมนต์ของ issue 663 และถูกมองว่าเป็นหนึ่งในกรณีที่ใช้โครงสร้างข้อมูลใน Shell ได้ซับซ้อนที่สุด
- มีความพยายามจะให้รันบน OSH และพาร์สได้เป็นส่วนใหญ่
- คอมมิตแรกในปี 2015 มี 8K บรรทัด / 6K LoC และการพัฒนาจริงเริ่มในปี 2013
- kalua: ส่วนเสริม OpenWRT ที่เขียนด้วย POSIX shell ขนาดประมาณ 56K SLoC/บรรทัด
- Relax-and-Recover: เครื่องมือสำรองและกู้คืนขนาด 35K บรรทัด, 24K LoC
- คอมมิต git แรกคือมีนาคม 2009 และตอนนั้นมี 4K บรรทัด / 3K LoC
- xwmx/nb: ตัว
nbเองมีขนาด 26K บรรทัด, 22K LoC และเขียนด้วย bash- ถ้านับการทดสอบ bats เป็น bash ด้วย จะมีเพิ่มอีก 91K บรรทัด, 61K LoC
- คอมมิตแรกอยู่ในปี 2014 และประวัติการคอมมิตที่คึกคักเริ่มต้นราวต้นปี 2016
- vegardit/bash-funk: ไลบรารี Bash ขนาดรวม 27K บรรทัด, 24K LoC
- คอมมิตแรกคือพฤษภาคม 2017 และตอนนั้นมี 10K บรรทัด / 8K LoC
- winetricks: Shell script ขนาด 22K บรรทัด สำหรับติดตั้งโปรแกรม Windows หลายชนิดบน Wine
- drwetter/testssl.sh: bash แบบไฟล์เดียวขนาด 21K บรรทัด
- ดูเหมือนจะเขียนด้วยมือ
- เริ่มจากคำสั่ง
opensslไม่กี่คำสั่งในปี 2006 - ติดปัญหาระหว่างพาร์สที่ issue #606
- rkhunter: โปรแกรม Bourne shell ขนาด 21K บรรทัด ที่พัฒนาตั้งแต่ปี 2003 ถึง 2018
- เว็บไซต์ทางการคือ rkhunter.sourceforge.net
- Simplenetes: แนะนำตัวว่าเป็น “Kubernetes in 17K lines of Shell”
- ถูกจัดว่าเป็นกรณีที่น่าทึ่ง แต่ดูเหมือนอยู่ในสถานะ dormant
- มีลิงก์ไปยัง Hacker News Thread
- inxi 2.3.56: โปรแกรม bash ขนาด 16K บรรทัด ที่ถูกระบุว่า obsolete
- fork มาจาก
infobashในปี 2008 - ตอนนั้น
infobashมี 889 บรรทัด และinfobashเริ่มต้นในปี 2005 - ตั้งแต่ v2.9 เป็นต้นไป
inxiถูกแทนที่ด้วย implementation ภาษา Perl
- fork มาจาก
- bashdb: ตัวดีบัก Bash ที่เขียนด้วย bash ราว 14K บรรทัด
- มีลิงก์พื้นหลังที่เกี่ยวข้องคือ Implementing Debuggers
- romkatv/powerlevel10k: ในไดเรกทอรี
internal/มีสคริปต์ zsh ขนาด 12K บรรทัด- ยังมี config และ helper script เพิ่มอีก 8K บรรทัด
- คอมมิตแรกอยู่ในปี 2014
- dylanaraps/neofetch: โปรแกรมขนาด 10K บรรทัด ที่เขียนด้วย Bash 3.2 และใช้แสดงข้อมูลระบบ
- ยังทำความสามารถเกี่ยวกับภาพที่น่าสนใจได้ด้วย
- คอมมิตแรกอยู่ในปี 2015
- distrobox: สคริปต์ bash ขนาด มากกว่า 7K บรรทัด ที่ทำให้ใช้งาน Linux ดิสทริบิวชันใดก็ได้ภายในเทอร์มินัล
- acme.sh: Shell script ขนาด 8K บรรทัด สำหรับออกและต่ออายุใบรับรอง
implementation ที่เด่นแม้ขนาดเล็กกว่า
- bashforth: ขนาดราว 3,800 บรรทัด แม้ไม่ใหญ่มาก แต่ implement ภาษาการเขียนโปรแกรมจริงขึ้นมา
- มีช่องว่างและคอมเมนต์มาก
- yoda: มีขนาดประมาณครึ่งหนึ่งของ bashforth แต่ implement ทั้ง interpreter และ compiler ครบชุด
- เป็น implementation ที่ใหม่กว่าของผู้เขียนคนเดียวกันราว 20 ปี และมีฟีเจอร์มากกว่า
- มีคอมเมนต์ว่า “What learned you have, unlearn you must!”
โปรแกรม Shell สำคัญอื่น ๆ
- abcde / A Better CD Encoder: ใช้สำหรับริป CD และมีขนาดราว 5.5K LoC
- thc-segfault: เซิร์ฟเวอร์ pubnix ที่ทำด้วย Bash เป็นหลัก ขนาด 3.3K LoC
- ffmpeg/configure: configure script ของ FFmpeg ที่เขียนด้วยมือ ขนาด 8.4K LoC
- ffhevc: Bash wrapper ที่เขียนด้วยมือทั้งหมดสำหรับเข้ารหัสวิดีโอ HEVC ด้วย FFmpeg และ libx265 ขนาด 4K LoC
- ffx264: Bash wrapper ที่เขียนด้วยมือทั้งหมดสำหรับเข้ารหัสวิดีโอ H.264/AVC ด้วย FFmpeg และ libx264 ขนาด 3.9K LoC
- h264enc: Bash wrapper ที่เขียนด้วยมือทั้งหมดสำหรับเข้ารหัสวิดีโอ H.264/AVC ด้วย MEncoder ขนาด 9.2K LoC
- bashtop: ตัวมอนิเตอร์ทรัพยากรขนาด 5.3K LoC
- halcyon: ระบบติดตั้งแอป Haskell ขนาด 6.6K LoC
- เป็นโค้ดที่เขียนด้วยมือ โดยใส่ใจเรื่อง bash semantics และการตรวจข้อผิดพลาด และมีสไตล์เฉพาะตัวที่ได้แรงบันดาลใจจาก functional programming
- wordshell: โค้ดขนาดราว 7K บรรทัด สำหรับจัดการหลายไซต์ WordPress จาก command line
- BaCon: ขนาดราว 10K บรรทัด และใช้แปลงโปรแกรมที่เขียนด้วย BASIC ให้เป็น C
- มีทั้ง implementation แบบ BASIC และแบบ Shell script
- FireHOL: สคริปต์หลักมีขนาด 9K บรรทัด และเครื่องมือ FireQOS เพิ่มอีก 3K บรรทัด
- เป็นทั้งภาษาและโปรแกรมรันสำหรับสร้าง secure, stateful firewall จากคอนฟิกที่มนุษย์อ่านง่าย
- gxadmin: ขนาด 11K LoC เป็นชุดของ templated SQL query และยูทิลิตีประมวลผลข้อมูลสำหรับดูแล Galaxy ซึ่งเป็น scientific workflow engine
- mulle-bashfunctions: ไลบรารีฟังก์ชันสำหรับ bash/zsh ขนาดราว 6K บรรทัด
- ใช้ใน
mulle-sdeซึ่งmulle-sdeเองก็เป็น Shell script อีกชุดขนาด 100K บรรทัด
- ใช้ใน
- x11docker: ขนาด 11.6K บรรทัด สำหรับรันแอป GUI ในคอนเทนเนอร์ docker หรือ podman
ภาษาแนว Shell และ DSL
- modernish: portable shell dialect ที่เขียนด้วย Shell
- bats: DSL สำหรับเขียนเทสต์ และสร้างโค้ด bash
- bashible: DSL คล้าย Ansible ที่เขียนด้วย bash
- มีลิงก์ไปยัง comments
- clash: เฟรมเวิร์กเชิงวัตถุที่เข้ากันได้กับ POSIX shell สมัยใหม่ทั้งหมด
- bash Infinity: standard library และ boilerplate framework สำหรับ bash
โปรแกรมที่เล็กกว่าและ ecosystem ที่เกี่ยวข้อง
- สคริปต์ของ Alpine, Aboriginal, Debian แยกไปอยู่ใน blog post
- completion script มีขนาดใหญ่ แต่บ่อยครั้งก็ ทำซ้ำ
- Zsh completion ชื่อ _git เป็นโค้ดขนาด 8.3K บรรทัด
- ยังยก
git-completion.bashและ Docker completion เป็นตัวอย่างด้วย
- dyne/Tomb: สคริปต์ zsh ขนาดราว 3,500 บรรทัด
- Basalt: package manager แบบ full-featured ที่เขียนด้วย Bash ล้วน
- มีขนาดระดับไม่กี่พันบรรทัด แต่มี rich ecosystem ที่รวมแอปและไลบรารีมากกว่า 15 รายการ
- bash-core: ไลบรารีที่ขยาย builtin
trapและshoptพร้อมเพิ่ม stacktrace และความสะดวกพื้นฐานที่จำเป็น - bash-object: ไลบรารีที่สร้างโครงสร้างข้อมูลซ้อนระดับใดก็ได้ด้วย Bash ล้วน และมีเทสต์เกือบ 200 รายการ
- bash-json: ไลบรารีสำหรับพาร์สและแสดงผล JSON ด้วย Bash ล้วน
- tablespoon/fun/cli-clock: นาฬิกาตัวอักษรหลายบรรทัดที่เขียนด้วย bash
- json.bash / jb: เครื่องมือ command line และไลบรารี bash สำหรับสร้าง JSON
- มีขนาดราว 1,700 บรรทัด และมีเทสต์ราว 3,000 บรรทัด
การทดสอบ OSH และข้อควรระวังในการใช้ Shell
- OSH "Wild" Tests สามารถพาร์ส Shell ได้มากกว่าหนึ่งล้านบรรทัด
- แต่ส่วนใหญ่เป็นโปรแกรมเล็กและนิยามแพ็กเกจดิสทริบิวชันแบบทำซ้ำ เช่น Alpine
PKGBUILDและ Gentooebuild
- แต่ส่วนใหญ่เป็นโปรแกรมเล็กและนิยามแพ็กเกจดิสทริบิวชันแบบทำซ้ำ เช่น Alpine
- Shell Programs That Run Under OSH เป็นลิงก์ไปยังรายชื่อโปรแกรม Shell ที่รันบน OSH ได้
- shell script are dangerous เตือนว่า Shell เป็นโปรแกรมสำหรับจัดการภายในระบบทั้งแบบ interactive console และแบบไม่โต้ตอบ มีความสามารถมาก อันตรายมาก และไม่ได้ถูกสร้างมาเพื่อพัฒนาแอปพลิเคชัน
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
พอขุดลงไปดูพบว่า OMS เป็นชุดสคริปต์เชลล์ขนาดมหึมาที่รันบนเซิร์ฟเวอร์ AIX ซึ่งวิวัฒนาการมานานกว่า 10 ปีแล้วถูกปล่อยทิ้งไว้ โค้ดมีมากกว่า 50,000 บรรทัด ข้อมูลคำสั่งซื้อ·การชำระเงิน·ข้อมูลอื่น ๆ ถูกย้ายระหว่างเซิร์ฟเวอร์ด้วย FTP แล้วพาร์สด้วย sed/awk ที่ซับซ้อน ส่วนสต็อกก็ติดตามด้วยไฟล์ข้อความและย้ายผ่าน FTP เช่นกัน
ตอนนั้น Perl ดูเป็นทางเลือกที่ใช้งานได้จริงที่สุดสำหรับย้ายกองยุ่งเหยิงนี้ จึงเริ่มจากส่วนที่ง่ายที่สุด เปลี่ยนเป็นโมดูล Perl เล็ก ๆ แล้วค่อย ๆ รีแฟกเตอร์เป็นขั้น ๆ ภายในแอปพลิเคชัน Perl ที่ใหญ่กว่า ภายใน 3 เดือนลดทั้งระบบเหลือ Perl ราว 5,000 บรรทัด และปัญหาระบบเดิมล่มก็แทบหายไป ขณะเดียวกันก็เร็วขึ้น 10~100 เท่า มันเลวร้ายมาก แต่ก็เป็นหนึ่งในงานที่น่าพึงพอใจที่สุดที่เคยทำมาจนถึงตอนนี้
สงสัยว่าได้อ่านโค้ดเดิมทั้งหมดและเข้าใจอย่างลึกซึ้งก่อน แล้วทำให้พฤติกรรมตรงกันเป๊ะ ๆ หรือว่าโยนก้อนใหญ่ ๆ ทิ้งแล้วเขียนใหม่ตามที่คิดว่า “มันควรทำงานแบบนี้” กันแน่ และก็สงสัยด้วยว่ามี boilerplate จำนวนมากที่แทนที่ได้อย่างรวดเร็วหรือเปล่า
สคริปต์ตัวแรกที่ใหญ่จริง ๆ ที่เขียนคือ ตัวติดตั้งราว 7,000 บรรทัด สำหรับ Enrust CA และไดเรกทอรี และต้องรันได้บน Unix แทบทุกตัวในยุคนั้น ตอนแรกมันไม่ได้เป็นแบบนั้น แต่ขยายใหญ่ขึ้นตามความต้องการของลูกค้า
ตัวการติดตั้งเองไม่ได้ซับซ้อนนัก แต่การอัปเกรดค่อนข้างซับซ้อน และในสมัยนั้นยูทิลิตีทุกตัวบน Unix แต่ละเจ้าแตกต่างกันเล็กน้อย โค้ดส่วนใหญ่ของสคริปต์คือการตรวจจับและจัดการความแตกต่างเหล่านั้น รวมถึงการตรวจจับข้อผิดพลาด·กู้คืน·rollback และการจัดการแพ็กเกจกับ dependency แบบดึกดำบรรพ์มาก ๆ
Unix ของ DEC รุ่นที่ไม่ใช่ Ultrix เป็นตัวที่เข้าใจยากที่สุด ใช้เวลาหลายวันกว่าจะตระหนักว่ายูทิลิตี command-line ทุกตัว ตัดเอาต์พุตตามความกว้างคอลัมน์ของเทอร์มินัล และผ่านไป 30 ปีแล้วผมยังจำเรื่องนั้นได้
HP-UX มี breaking change ทุกรีลีส และถ้าจำไม่ผิดรองรับตั้งแต่ 6.5 ถึง 11 ส่วน Ultrix, ฝั่ง Novell, NeXT, Sequent แทบจำไม่ได้แล้ว จำได้ว่า AIX แปลก ๆ แต่ลืมไปแล้วว่าเพราะอะไร OS สามหรือสี่ตัวของ Sun ก็มีความต่างกันเหมือนกัน แต่คู่มือยอดเยี่ยมมากและดีที่สุด
wcกับไบนารีหลักและไลบรารีช่วยของโปรเจกต์หนึ่ง ตอนนี้รวมได้ 6,224 บรรทัดเป็นสคริปต์สำหรับจัดการ pipeline ที่รับประกันความเป็นเชิงเส้น โดยประกอบเป็นชุดคอนเทนเนอร์ของ input protocol adapter, filter อย่างน้อยหนึ่งตัว และ output protocol adapter เป้าหมายคือทำให้คนที่ไม่จำเป็นต้องเป็นผู้เชี่ยวชาญด้านคอนเทนเนอร์หรือโปรโตคอลก็ใช้ได้ ตราบใดที่รู้ว่าต้องการให้ไฟล์ถูกกรอง·แปลงอย่างไรขณะผ่าน pipeline
ไบนารีระดับบนสุดมีโครงสร้างแบบมีฟังก์ชันย่อยเหมือน
git [ git options ] < git action> [action options]หรือsystemctlมีแม้กระทั่ง subcommand ที่เพิ่ม subcommand ใหม่ โดยจะสร้างไลบรารีที่จำเป็นและเติมคำจำกัดความฟังก์ชันจากเทมเพลตไว้ล่วงหน้า ในเทมเพลตมีฟังก์ชัน usage แบบสั้น/ยาว ทำให้cbap -hหรือcbap pipeline -hให้คำแนะนำที่เป็นประโยชน์มี subcommand สำหรับจัดการ base image, component และ pipeline โค้ดส่วนใหญ่มีไว้สำหรับทดสอบว่าคำจำกัดความของ component และ pipeline ถูกเขียนอย่างถูกต้องหรือไม่ pipeline ใช้รูปแบบที่แทบจะเป็น TOML จึงมีโค้ดสำหรับพาร์ส TOML และแปลง section เป็น array ส่วน component เป็นไฟล์
key=valueง่าย ๆ จึงมีโค้ดสำหรับดึงฝั่งซ้าย·ฝั่งขวาและตรวจสอบ schemaเนื่องจาก component ใน pipeline แชร์ attribute กันได้ จึงมีโค้ดสำหรับค้นหา attribute ร่วมจากไฟล์
varและetcและกำหนด attribute ของ component ด้วย มีฟังก์ชันจำนวนมากสำหรับจัดการ user·group·directory·FIFO ตามข้อกำหนดด้านความปลอดภัย เมื่อตั้งค่า pipeline แล้ว จะสร้าง·นำไปใช้กับ user, group, SELinux type, MCS category จากนั้น map ไปยังไฟล์ service ที่เริ่ม component จึงมี การจัดการ systemd เยอะมากชุดการเรียกที่ใหญ่ที่สุดน่าจะเป็นฟังก์ชันสำหรับดึงและตั้งค่า attribute ของ component หรือจริง ๆ แล้วคือ attribute ของคอนเทนเนอร์ สำหรับแต่ละ attribute มีฟังก์ชัน getter, ฟังก์ชันตรวจสอบความถูกต้อง และเวอร์ชัน inline ภายใน pipeline เพื่อให้คำจำกัดความคอนเทนเนอร์แบบขับเคลื่อนด้วยข้อมูลยืดหยุ่นที่สุดเท่าที่ทำได้
ยังมีโค้ดที่ใช้ Bash reference จำนวนมากเพื่อกำหนดตัวแปรจากไฟล์, environment variable และ command line ทำให้ทดสอบได้เร็ว รองรับระดับผู้ใช้ 4 ระดับ ได้แก่ maintainer ที่จัดการตัวโค้ดเอง, developer ที่พัฒนาคำจำกัดความของ component, integrator ที่สร้าง pipeline จาก component และ operator ที่ติดตั้ง pipeline และสามารถคัดลอก·แพ็กเกจตัวเองเพื่อส่งออกให้ผู้ใช้ในแต่ละระดับได้
เนื่องจากระบบเป้าหมายอาจเป็น Linux แบบใดก็ได้ จึงแพ็กเกจและแตกไฟล์ด้วย
makeselfตัวอย่างเช่น เมื่อ integrator สร้างคำจำกัดความ pipeline ก็จะได้ไฟล์makeselfและเมื่อรันบนระบบเป้าหมาย จะสร้าง user·group·directory·FIFO ทั้งหมด หรือก็คือ IPC ระหว่าง component, ใช้ DAC/MAC, สร้างไฟล์ systemd, คัดลอก image ให้ผู้ใช้แต่ละคน แล้วรัน pipeline และยังสามารถย้อนทุกอย่างกลับได้ด้วยตัวเลือกสำหรับลบมี seccomp อยู่บ้าง แต่ตอนนี้พักไว้ก่อนเพราะต้องหาสมดุลระหว่าง allowlist กับ blocklist ส่วน ShellCheck นั้นใช้แบบเข้มงวดจริง ๆ
จะคาดหวังว่าทุกสภาพแวดล้อมมี
bc/dcก็ไม่ได้ และบางเครื่องที่ผมมี Bash ก็เก่ามากจนรองรับ associative array ได้จำกัดมาก ทางประนีประนอมคือเลือก target เป็น AWK ซึ่งเป็นภาษาทั่วไปที่ใช้สบายกว่าเชลล์ส่วนใหญ่มาก และมีอยู่ทุกที่ที่เป็นสภาพแวดล้อม POSIX: https://beyondloom.com/blog/lila.htmlbc/dcมีในทุกสภาพแวดล้อมไม่ได้ผมค่อนข้างแปลกใจที่การติดตั้ง Ubuntu บน WSL2 ดูเหมือนจะไม่มี
bc/dcอยู่ด้วย ผมใช้ AWK สำหรับการคำนวณแบบจุดลอยตัว แต่ก็เป็นแค่การเรียกเป็นโปรเซสภายนอกจากมุมมองของคนที่เคยเขียนและดูแลโปรแกรม Perl ขนาดใหญ่หลายครั้งตลอดอาชีพ เหตุผลที่ผู้คนทำแบบนี้มันมีอยู่จริง
ภาษาอย่าง Java หรือ Python เหมาะเมื่ออินเทอร์เฟซและรูปแบบข้อมูลถูกกำหนดไว้ชัดเจน และแทบไม่มีการโต้ตอบกับ OS ถ้าใช้ JSON/XML/YAML หรือสื่อสารกับฐานข้อมูลหรือโปรแกรมอื่นผ่าน HTTP(S) ก็เป็นสถานการณ์ในอุดมคติที่ภาษาเหล่านี้โดดเด่น
แต่เมื่อจัดการกับข้อความปริมาณมากและการโต้ตอบกับ OS, Java กับ Python จะกลายเป็นความเจ็บปวดอย่างหนัก ในทางกลับกัน Shell/Perl ทำให้งานประเภทนี้ง่ายกว่ามาก
งานอัตโนมัติแทบทุกแบบ, อินเทอร์เฟซที่ยุ่งเหยิงและไม่ได้มาตรฐาน, ไฟล์ข้อความ/ล็อก, รูปแบบข้อมูลที่ไม่มีโครงสร้างหรือมีโครงสร้างไม่พอ ล้วนอยู่ในหมวดนี้ เมื่อบวกกับ ความเข้ากันได้ย้อนหลัง ของ Perl, ฐานการติดตั้งที่กว้าง และประสิทธิภาพแล้ว สำหรับงานเหล่านี้แทบไม่มีทางเลือกอื่นนอกจาก Perl
ผมมองมานานแล้วว่า หนึ่งในเหตุผลใหญ่ที่ทุกวันนี้บริษัทขนาดใหญ่จ้างคนหลายพันคนมาทำงานด้วยมือเพื่อทำสิ่งที่สามารถอัตโนมัติได้แบบเล็ก ๆ น้อย ๆ คือการใช้ Perl ลดลง พอพยายามทำงานอัตโนมัติขนาดใหญ่ด้วย Python หรือ Java ก็มักจะท้อและเลิกไปไม่นาน เพราะความเยิ่นเย้อของโค้ดที่ต้องเขียนและดูแล รวมถึงขนาดโดยรวมของงาน
ดังนั้นตอนนี้คงกลายเป็นว่าต้องมีพนักงานประจำราคาแพงเพิ่มเข้ามา ถ้าผู้ใช้ปลายทางใช้ Windows ก็มีตัวเลือกที่คล้าย Perl อยู่บนเดสก์ท็อปอยู่แล้ว นั่นคือ PowerShell และมันทำหน้าที่คล้าย Perl
Bash+grep มีแนวโน้มจะไหลไปสู่การรันโปรเซสใหม่ทุกบรรทัดของข้อความ ถ้าจะทำให้มีประสิทธิภาพ ต้องลดงานให้เหลือน้อยที่สุด และถ้าจะทำเช่นนั้นก็ต้องมีการประมวลผลแบบแบตช์และการลบงานซ้ำ ซึ่งหมายความว่าต้องจัดการข้อมูลแบบมีสถานะพร้อมติดตามบริบทของการลบซ้ำ และสิ่งนี้ทำได้ง่ายกว่าในภาษาการเขียนโปรแกรมที่เหมาะสม
Bash+grep เหมาะกับการประมวลผลข้อความแบบไร้สถานะ จึงทำให้งานซ้ำซ้อนบานปลายได้ง่าย อีกวิธีในการลดงานคือการกรองที่แม่นยำ ซึ่งแสดงออกแบบ imperative ได้สะอาดกว่ามากในภาษาที่เหมาะสม grep กับ regex ไม่เหมาะกับงานแบบนี้เลย
ถ้าใช้รูปแบบแบบแบ่งเป็นบรรทัด git จะใส่ escape เพื่อพยายามรับทุกอย่าง แต่การรองรับไม่สม่ำเสมอ และสามารถปิดได้โดยใช้ตัวเลือก
-zเพื่อขอรูปแบบสตริงที่จบด้วย null ดูเหมือนว่า Bash จะไม่มีวิธีจัดการสิ่งนี้ได้ แต่ในภาษาระดับต่ำพอ มันจัดการได้อย่างเป็นธรรมชาติ อีกทั้งยังทำ incremental streaming ได้โดยไม่ต้องเริ่มโปรเซสใหม่ทุกบรรทัดของข้อความด้วยแถมยังใช้โค้ดเบสเดียวกับทุกงานได้ ไม่ว่าตรงกลางจะมี HTTP หรืออย่างอื่นก็ตาม
มีลิงก์บางอันสำหรับบริบท “นี่คือการประดิษฐ์ Perl ขึ้นมาใหม่หรือเปล่า?”: https://www.oilshell.org/blog/2021/01/why-a-new-shell.html#a...
“Unix shell ควรวิวัฒน์เหมือน Perl 5 ที่มีตัวเลือกอัปเกรดแบบเข้ากันได้ ไม่ใช่ Big Bang แบบ Perl 6/Raku”: https://www.oilshell.org/blog/2020/07/blog-roadmap.html#the-...
ชมภาพรวม YSH: https://www.oilshell.org/release/latest/doc/ysh-tour.html
เรื่องนี้อาจดูไม่ใช่ข้อได้เปรียบใหญ่ จนกว่าคุณจะต้องทำงานในสภาพแวดล้อมที่ติดตั้งอะไรจากอินเทอร์เน็ตไม่ได้ หรือไม่มีทางเข้าถึงอินเทอร์เน็ตเลย
ปัญหาหลักของการเขียนโปรแกรมขนาดใหญ่เป็นสคริปต์ Bash คือภาษาเชลล์สคริปต์ไม่ได้ถูกออกแบบมาเพื่อรับมือกับความซับซ้อนตั้งแต่แรก
มันยอดเยี่ยมสำหรับการประสานคำสั่งเล็ก ๆ และการต่อเครื่องมือที่มีอยู่เข้าด้วยกันอย่างรวดเร็วแบบสำรวจไปเรื่อย ๆ แต่เมื่อ Bash เริ่มยาวเกินสองสามร้อยบรรทัด ข้อจำกัดที่ทำให้การดูแลระยะยาวและการขยายระบบปวดหัวก็จะโผล่มาต่อเนื่อง
อย่างแรกคือ ความอ่านง่าย ไวยากรณ์ของ Bash อาจเข้าใจยากมากเมื่อมันใหญ่ขึ้น กฎเรื่องขอบเขตตัวแปรมีรายละเอียดปลีกย่อย การจัดการข้อผิดพลาดก็หยาบ และการจัดการสตริงก็เลอะเทอะได้อย่างรวดเร็ว สุดท้ายผู้ดูแลต้องเสียเวลาแกะว่าเกิดอะไรขึ้น และยากที่จะเปลี่ยนแปลงได้อย่างมั่นใจ
ถัดมาคือการขาดเครื่องมือที่แข็งแรง ภาษา mature กว่านี้มีเครื่องมือวิเคราะห์แบบสแตติก, linter, debugger ที่ช่วยจับข้อผิดพลาดทั่วไปได้ตั้งแต่เนิ่น ๆ ใน Bash สิ่งเหล่านี้ไม่มีหรือมีข้อจำกัดมาก หากไม่มี guardrail เหล่านี้ โปรแกรม Bash ขนาดใหญ่จะเสี่ยงต่อข้อผิดพลาดเงียบ ๆ, regression และบั๊กละเอียดอ่อนมากขึ้น
การทดสอบก็เป็นปัญหาเช่นกัน แม้จะทดสอบสคริปต์ Bash ได้ แต่กระบวนการมักยุ่งยากกว่า และถ้ามีลอจิกหรือโครงสร้างข้อมูลซับซ้อนก็ยิ่งลำบากขึ้น เมื่อจัดการ edge case อย่างช่องว่างในชื่อไฟล์หรือเงื่อนไขแวดล้อมที่ไม่คาดคิด ก็จะมีโค้ดป้องกันกองพะเนินที่ตรวจสอบได้อย่างเจ็บปวด
สุดท้าย อีโคซิสเต็มเองก็ไม่ได้ถูกสร้างมาเพื่อการพัฒนา Bash ขนาดใหญ่ คุณจะเสีย modularization, package management, การจัดการ dependency แบบมาตรฐาน และแพตเทิร์นการพัฒนาสมัยใหม่ที่ Python หรือ Go มีให้ เมื่อเวลาผ่านไป สิ่งที่ขาดเหล่านี้จะสะสมและทำให้ช้าลง
การใช้ Bash สำหรับงานครั้งเดียวหรืองานอัตโนมัติง่าย ๆ นั้นไม่เป็นไร นั่นคือสิ่งที่ Bash ทำได้ดี แต่ถ้าคิดจะสร้างอะไรใหญ่ ๆ โดยทั่วไปควรใช้ภาษาที่ออกแบบมาเพื่อสร้างและดูแลแอปพลิเคชันซับซ้อนมากกว่า และแม้ learning curve หรือการตั้งค่าเริ่มต้นจะสูงกว่าเล็กน้อย แต่ระยะยาวจะช่วยประหยัดเวลา
การใช้ ShellCheck เป็น linter จะช่วยจับกับดักที่พบบ่อยได้มาก ใน Bash/เชลล์มีกับดักและพฤติกรรมที่ไม่คาดคิดเยอะจริง ๆ จนแม้แต่คนเขียน Bash ที่ชำนาญก็ยังพลาดได้
อย่างไรก็ตาม Bash/เชลล์มีตำแหน่งที่ค่อนข้างเฉพาะตัวในลำดับชั้นของภาษา มันมีอยู่แทบทุกที่ และมีแนวโน้มว่าจะยังคงอยู่แม้ผ่านไปอีก 30 ปี ถ้าต้องการโปรแกรมที่รันได้แทบทุกที่และยังรันได้ในอีก 30 ปีข้างหน้า เชลล์/Bash ก็เป็นตัวเลือกที่ดี
ไม่ใช่สคริปต์ที่เขียนไว้นานมากแล้วด้วยซ้ำ แต่ไม่รู้ว่าทำไมถึงเลือก Bash สคริปต์ทำงานได้ก็จริง แต่รู้สึกเหมือนแค่มองโค้ดผิดมุมก็อาจมีอะไรพังได้
-xโปรแกรมเชลล์แบบเขียนด้วยมือที่ใหญ่ที่สุดที่เคยใช้เป็นประจำในอดีตน่าจะเป็น abcde (A Better CD Encoder) ขนาดประมาณ 5,500 บรรทัด
https://abcde.einval.com
https://git.einval.com/cgi-bin/gitweb.cgi?p=abcde.git;a=blob...
โปรแกรมจำนวนไม่น้อยในกลุ่มนี้เป็นอัญมณีของจริง ตัวอย่างเช่นสคริปต์ rkhunter โค้ดก็ดี ยังมีช่องให้ปรับปรุง และยังเป็นคลังข้อมูลด้วย
ขนาดโค้ดส่วนใหญ่ของสคริปต์แบบนี้ถูกใช้ไปกับการรับประกันว่าบนหลายแพลตฟอร์มมียูทิลิตีที่จำเป็นอยู่จริง และทำงานตามที่คาดไว้กับตัวเลือก command line ต่าง ๆ นี่เป็นจุดที่เจ็บปวดที่สุดสำหรับคนเขียนเชลล์สคริปต์แบบจริงจัง และยากกว่าสัญญาณกับ subprocess เสียอีก
ถ้า rkhunter ถูกเขียนด้วยภาษาโปรแกรมที่ “จริงจัง” ข้อมูลแบบนี้น่าจะโปร่งใสน้อยลง อาจถูกดันเข้าไปเป็นเรคคอร์ดในโครงสร้างข้อมูลแล้วค่อยถูกค้นหา หรือทำงานผ่านหลายฟังก์ชันบนโครงสร้างข้อมูลซ้อนกัน หรือแย่กว่านั้นคือผ่านชุดเมธอดกับคลาสก็ได้ ล็อกก็อาจถูกแตกเป็น JSON แล้วบีบอัดลงฐานข้อมูล ก่อนจะต้องเข้าถึงผ่านเมธอดอื่น
เพราะเชลล์สคริปต์ไม่มีเครื่องมือซับซ้อนแบบนั้น จึงกลับมีแนวโน้มที่จะเผยให้เห็นตรง ๆ ว่ากำลังเกิดอะไรขึ้น ดังนั้น rkhunter จึงทำหน้าที่เป็นเอกสารที่ดีพอสมควรเกี่ยวกับ exploit และ rootkit หลายตัวด้วย โดยไม่จำเป็นต้องขุดไล่จากไฟล์หนึ่งไปอีกไฟล์ จากโครงสร้างหนึ่งไปอีกโครงสร้าง หรือจากฐานข้อมูลหนึ่งไปอีกฐานข้อมูลมากนัก
ไคลเอนต์ FreeBSD Update เป็นโค้ด sh ประมาณ 3,600 บรรทัด
เมื่อเทียบกับโปรแกรมอื่นที่กล่าวถึงในที่นี้ก็ไม่ได้ใหญ่ แต่เมื่อดูจากปริมาณฟังก์ชันในฐานะ “เครื่องมือที่อัปเดตทั้งระบบปฏิบัติการ” ก็นับว่าหนักพอสมควร โค้ดที่ใช้สร้างอัปเดตแบ่งอยู่หลายไฟล์ รวมกันแล้วน่าจะมากกว่านี้
poudriereมีปริมาณโค้ด sh ประมาณ 3 เท่าของไคลเอนต์ FreeBSD Update: https://github.com/freebsd/poudriere/blob/master/src/share/p...แม้จะ “แค่” 7.1 พันบรรทัด แต่สคริปต์ที่ผมชอบคือ acme.sh ที่ใช้สำหรับออกและต่ออายุใบรับรองจาก Let’s Encrypt
https://github.com/acmesh-official/acme.sh/blob/master/acme....
บางครั้งสิ่งเดียวที่รับประกันได้ว่ามีให้ใช้ก็คือเชลล์ และก็มีสถานการณ์ที่จำเป็นต้องพกพาได้จริง ๆ
แต่โดยทั่วไป ถ้ามีแอปพลิเคชันเชลล์ขนาดใหญ่ คุณอาจต้องกลับมาทบทวนทางเลือกในชีวิตอีกครั้ง
แถมโดยทั่วไปแค่เชลล์อย่างเดียวก็ทำอะไรได้ไม่มาก ต้องใช้คำสั่งอย่าง
find,grep,sed,cat,head,tail,cutด้วย คำสั่งเหล่านี้ก็มี ปัญหาเรื่อง portability ของตัวเองการตั้งเป้าไปที่ BusyBox อาจเป็นทางเลือกที่ดีที่สุด แต่ทันทีที่ออกนอกระบบ Linux ทั่วไป การเขียน Bourne shell script ที่พกพาได้ก็จะยากหรือแทบเป็นไปไม่ได้
ถ้ามี C compiler ก็สามารถออกจากเชลล์ไปเขียนโปรแกรม C ให้เชลล์สคริปต์ประกอบใช้งาน หรือจะติดตั้งภาษาสคริปต์ที่ดีกว่าอย่าง Lua ก็ได้ ณ ตอนนี้ กรณีที่จำเป็นต้องใช้เชลล์เท่านั้นจริง ๆ ดูเหมือนจะเป็นเรื่องเฉพาะกลุ่มมาก