3 คะแนน โดย GN⁺ 2023-11-24 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • ในตระกูล Unix อาจมีไฟล์ปฏิบัติการชื่อเป็นสัญลักษณ์ตัวเดียวคือ /bin/[ และไวยากรณ์ที่ดูเหมือนนิพจน์เงื่อนไขของเชลล์นั้น แท้จริงแล้วทำงานอยู่บนการเรียกคำสั่งและรหัสออกจากโปรแกรม
  • test ใช้ประเมินนิพจน์และจะคืนค่า 0 ถ้าเป็นจริง หรือ 1 ถ้าเป็นเท็จ และเมื่อถูกเรียกด้วย [ ก็จะตรวจด้วยว่าอาร์กิวเมนต์ตัวสุดท้ายเป็น ] หรือไม่
  • เนื่องจากหลายเชลล์มี คำสั่งแบบ built-in สำหรับ test และ [ ด้วย ข้อความผิดพลาดหรือพฤติกรรมระหว่าง /bin/test ภายนอกกับ implementation แบบ built-in ของเชลล์จึงอาจต่างกันได้
  • ส่วน [[ ซึ่งเป็นส่วนขยายของ Bash ไม่ใช่คำสั่งภายนอกแต่เป็นไวยากรณ์แบบ built-in จึงใช้กฎที่ต่างจาก [ เช่น ในตัวอย่าง long* จะไม่ถูกขยายแบบ glob แต่ถูกเปรียบเทียบเป็นสตริงตามตัวอักษร
  • สำหรับสคริปต์ที่ต้องการพกพาได้ควรใช้ [ แต่ถ้าเป็น Bash โดยเฉพาะ การใช้ [[ อย่างสม่ำเสมอจะเหมาะกว่า ทั้งนี้ควรเลือกโดยเข้าใจความต่างของกฎการขยายระหว่างสองแบบ

ตัวตนของ /bin/[ และ /bin/test

  • บนระบบ Unix อาจมีไฟล์ปฏิบัติการชื่อเป็นสัญลักษณ์ตัวเดียวคือ /bin/[
    • คำสั่งตัวอย่าง ls /bin/? จะแสดง /bin/[
  • /bin/[ และ /bin/test อาจชี้ไปยังไบนารีเดียวกัน
    • ในตัวอย่าง ทั้งสองพาธแสดงว่าเป็นไฟล์ที่มี inode และขนาดเดียวกัน
    • อย่างไรก็ดี ไม่จำเป็นต้องเป็น hard link บนทุกระบบเสมอไป
  • test เป็นโปรแกรมในเชลล์สำหรับ ประเมินนิพจน์
    • เปรียบเทียบสตริง
    • เปรียบเทียบตัวเลข
    • ตรวจสอบเงื่อนไขของไฟล์
  • ถ้าผลการประเมินเป็นจริงจะคืนรหัสออกจากโปรแกรม 0 ถ้าเป็นเท็จจะคืน 1

ทำไม [ จึงทำงานเหมือนคำสั่ง

  • test a = b ดูไม่ค่อยเหมือนนิพจน์เงื่อนไข แต่ถ้าเขียนตรรกะเดียวกันเป็น [ a = b ] จะดูคุ้นเคยมากกว่า
  • แม้ [ จะดูเหมือนเป็นไวยากรณ์พิเศษ แต่จริง ๆ แล้วเป็นการเรียกคำสั่ง
    • if [ a = b ]; then ... fi คือการรันคำสั่ง [ แล้วตรวจรหัสออกจากโปรแกรม
    • เมื่อเรียกด้วย [ โปรแกรมจะตรวจว่าอาร์กิวเมนต์สุดท้ายเป็นวงเล็บเหลี่ยมปิด ] หรือไม่
  • คำสั่ง if ไม่ได้ตีความนิพจน์เงื่อนไขโดยตรง แต่แยกทางตาม รหัสออกจากโปรแกรมของคำสั่ง ที่ส่งเข้าไป
    • test a = a; echo $? คือ 0
    • test a = b; echo $? คือ 1
    • [ a = a ]; echo $? คือ 0
    • [ a = b ]; echo $? คือ 1
  • ในบริบทเดียวกัน true และ false ก็อาจมองได้ว่าเป็นไบนารีช่วยที่คืนรหัสออกจากโปรแกรม

ความต่างระหว่างไบนารีภายนอกกับคำสั่งแบบ built-in ของเชลล์

  • test และ [ ถูกใช้บ่อยในเชลล์สคริปต์ ดังนั้นเชลล์ส่วนใหญ่จึงมี implementation แบบ built-in ของมันด้วย
  • แม้รับอินพุตเดียวกัน ไบนารีภายนอกกับคำสั่ง built-in ของเชลล์ก็อาจให้ผลลัพธ์ต่างกัน
    • /bin/test a b คือ test: a: unexpected operator
    • test a b คือ dash: 2: test: a: unexpected operator
  • ความต่างแบบนี้เกิดได้ไม่ใช่แค่กับ test และ [ แต่รวมถึงคำสั่งที่ดูง่ายอย่าง echo ด้วย
  • เพราะแต่ละเชลล์มี implementation แบบ built-in ต่างกัน พฤติกรรมของสคริปต์จึงอาจเปลี่ยนไปตามเชลล์ที่ใช้รัน

กฎอีกชุดหนึ่งที่ส่วนขยาย [[ ของ Bash ใช้

  • [[ เป็นส่วนขยายของ Bash และใช้แทน [ ได้
  • ความต่างที่สำคัญที่สุดคือ [[ เป็น ไวยากรณ์แบบ built-in เสมอ
    • ต่างจาก [ ที่อาจถูกรันเป็นไบนารีภายนอกได้ ใน [[ นั้น Bash สามารถเปลี่ยนกฎภาษาภายในนิพจน์ได้
  • ในตัวอย่าง glob นั้น [ และ [[ ทำงานต่างกัน
    • หลัง touch long-name แล้ว [ long* = long-name ] && echo match จะพิมพ์ match
    • อาร์กิวเมนต์ของคำสั่ง [ จะถูกใช้กฎการขยายปกติของเชลล์ ทำให้ long* ถูกขยายเป็น long-name ในไดเรกทอรี
    • [[ long* = long-name ]] && echo match จะไม่พิมพ์อะไร
    • [[ จะปฏิบัติกับ long* เป็นสตริงตามตัวอักษรและเปรียบเทียบตรง ๆ กับ long-name จึงล้มเหลว
  • ถ้าเป็นสคริปต์ที่ใช้กับ Bash เท่านั้น ก็ยังใช้ความสามารถอย่างการจับคู่ด้วย regular expression =~ ผ่าน [[ ได้ด้วย

ในสคริปต์ควรเลือกอะไร

  • ถ้าเป็นเชลล์สคริปต์ที่ต้องการพกพาได้ ควรใช้ [
  • จะใช้ test ก็ได้ แต่ไม่ใช่ตัวเลือกที่นิยมทั่วไป
  • ถ้าสคริปต์นั้นใช้กับ Bash เท่านั้น การใช้ [[ อย่างสม่ำเสมอจะดีกว่า
  • ตัวเชลล์เองก็มีตัวดำเนินการนิพจน์อย่าง !, &&, ||
    • ตัวดำเนินการเหล่านี้ทำงานตามสถานะการจบของคำสั่ง
    • grep ^hello$ ... && grep ^bye$ ... จะมีรหัสออกจากโปรแกรมรวมเป็น 0 เมื่อทั้งสองคำสั่งสำเร็จ
    • ถ้า grep ตัวแรกไม่สำเร็จ คำสั่งหลัง && ก็จะไม่ทำให้ผลรวมสำเร็จ ดังนั้นรหัสออกจากโปรแกรมรวมจะเป็น 1
  • เพราะฉะนั้นจึงสามารถผสมนิพจน์ test/[ กับตัวดำเนินการตรรกะของเชลล์ในเงื่อนไขเดียวกันได้
    • ตัวอย่าง: [ a = b ] || grep -q ^hello$ /usr/share/dict/words
  • POSIX ไม่ได้กำหนดว่า /bin/[ และ /bin/test ต้องเป็น hard link กัน
    • บน NetBSD เคยเป็น hard link
    • macOS Catalina ให้เป็นสำเนาแยกของไบนารีเดียวกัน
    • Debian testing ให้เป็นคนละไบนารี
    • ข้อกำหนดของ POSIX ไม่ได้บังคับว่าไฟล์ทั้งสองต้องเป็นลิงก์กัน

1 ความคิดเห็น

 
GN⁺ 2023-11-24
ความคิดเห็นจาก Hacker News
  • ผมเป็นผู้เขียนต้นฉบับเอง ขอบคุณที่แชร์ และดีใจที่ขึ้นไปถึงหน้าแรกแล้ว ชื่อน่าจะควรมี (2020) ต่อท้าย และ "test" หมายถึงคำสั่งจริง ๆ จึงน่าจะไม่ควรเขียนขึ้นต้นด้วยตัวใหญ่
    ผมยังมีบทความที่เกี่ยวข้องซึ่งเขียนในปี 2021 ด้วย โดยพูดถึงไปถึงโอเปอเรเตอร์ [[ ของ bash น่าจะอ่านได้สนุกในบริบทนี้: https://jmmv.dev/2021/08/useless-use-of-gnu.html

    • พูดอย่างเคร่งครัด [[ ไม่ใช่คำสั่งในตัว แต่โดยพื้นฐานแล้วใกล้เคียงกับ องค์ประกอบทางไวยากรณ์ มากกว่า ภายในอาจใช้คำสั่งในตัวที่แทบเข้าถึงไม่ได้ก็ได้ แต่จุดที่น่าสนใจคือ ]] ก็เป็น reserved word เช่นกัน ทั้งที่ไม่สามารถอยู่ในตำแหน่งที่ reserved word มีความหมายได้
      ในเชลล์บางตัวที่ไม่ใช่ bash หากต้องการประกาศฟังก์ชันบางประเภท ต้องใช้คีย์เวิร์ด function ส่วน $(shell) ของ make อาจทำให้วัดความต่างด้านประสิทธิภาพได้เมื่อ build target จำนวนมาก ถึงอย่างนั้น ถ้าเป็นกรณีที่ไม่ได้ทำอะไรเลยก็ถือว่าเสียเปรียบ ดังนั้นโดยทั่วไปถ้าต้องการบังคับให้สร้างใหม่ ควรใช้ include มากกว่า การที่ GNU ไม่สนใจ POSIX นั้นสมเหตุสมผลอย่างยิ่ง เพราะ POSIX ไม่ค่อยมีประโยชน์ในการแก้ปัญหาจริงส่วนใหญ่
    • ส่วนขยาย GNU จำนวนมากที่พูดถึงใน https://jmmv.dev/2021/08/useless-use-of-gnu.html มีประโยชน์มากสำหรับ การใช้งานแบบโต้ตอบ การค้นหาไดเรกทอรีปัจจุบันโดยไม่ต้องระบุ . อย่างชัดเจนก็มีประโยชน์ และการเติมตัวเลือกต่อท้ายคำสั่งที่เพิ่งพิมพ์ไปก็สะดวกจริง ๆ เวลาเจอคำสั่งที่ไม่รองรับสิ่งพวกนั้นก็มักจะหงุดหงิดเสมอ
      ในสคริปต์ โดยทั่วไปการเขียนให้เข้ากับ POSIX sh นั้นสมเหตุสมผล อย่างน้อยก็ควรรู้ว่ากำลังใช้ไวยากรณ์เฉพาะของ Bash อยู่หรือไม่
    • บทความนั้นบ่นว่าการใช้ส่วนขยาย GNU อย่าง --ignore-case, set -o pipefail ทำให้ ความสามารถในการพกพา ของสคริปต์ลดลง เรื่องนั้นเองก็ถูกต้อง
      แต่ไม่ได้อธิบายว่าทำไมผู้ใช้ Linux ต้องใส่ใจกับความสามารถในการพกพาขนาดนั้น OpenBSD และ FreeBSD ยังอยู่ได้ดี แต่จำนวนผู้ใช้น้อยมากจนดูไม่ใช่สิ่งที่ต้องกังวลเป็นพิเศษ อาจบอกได้ว่าเพื่อความเป็นธรรมก็ควรคำนึงถึงระบบปฏิบัติการเหล่านั้นด้วย แต่เกณฑ์นั้นควรหยุดที่ตรงไหน? ต้องคำนึงถึงของ obscure อย่าง vxWorks ด้วยไหม? ฝั่ง BusyBox, Alpine น่าสนใจกว่า แต่ความเปลี่ยนแปลงใหญ่เสียจนแทบจะต้องพอร์ตแยกอยู่ดี มีเหตุผลอื่นที่โน้มน้าวใจได้ไหมว่าควรใส่ใจกับ ecosystem ที่ไม่ใช่ GNU?
    • อะไรอย่าง if [ a = b ] || grep -q ^hello$ /usr/share/dict/words; then echo "test failed and grep succeeded"; fi ก็เป็นแค่ วิธีใช้เชลล์ ธรรมดาในชีวิตประจำวันไม่ใช่หรือ?
    • เรื่องข้างเคียง แต่ดูเหมือนซอฟต์แวร์บล็อกจะทำให้ชื่อเรื่องเสีย ตัวอย่างเช่นมันแสดงเป็น make $(shell …) expansion แต่เดิมควรเป็น make $(shell ...) expansion
      อย่างที่ในเนื้อหาเขียนถูกต้อง มันเป็นจุดสามจุด ไม่ใช่เครื่องหมายจุดไข่ปลาเดี่ยว ดังนั้น mldr เองก็ไม่ถูก น่าจะเป็นบั๊กที่ไม่เกี่ยวกันสองตัวส่งผลพร้อมกัน
  • ถ้าดันประเด็นสุดท้ายไปอีกขั้น ก็ลบตัวบล็อก if ออกได้ด้วย if [ a = b ]; then echo "Oops!"; else echo "Expected; phew!"; fi จะกลายเป็น [ a = b ] && echo "Oops!" || echo "Expected; phew!"
    ไม่รู้ว่าควรทำแบบนี้บ่อยแค่ไหน แต่บางครั้งก็มีประโยชน์เวลาพิมพ์ output สำหรับดีบักไปยัง standard error แบบมีเงื่อนไข เช่น [ "$debug" ] && echo "what's going on" >&2 ความจริงที่ว่าบล็อก if ตรวจสอบคำสั่งทั่วไป ทำให้ทำแบบ if grep -q 'debug' /var/log/nginx/access.log; then echo "Debug request found!"; fi ได้ด้วย สิ่งที่ยังไม่ได้ดูคือควรเขียนเป็น [ $(expr 1 + 1) -eq 2 ] && [ $(expr 2 + 2) -eq 3 ] หรือควรใช้ logical AND ในตัวของ test อย่าง [ $(expr 1 + 1) -eq 2 -a $(expr 2 + 2) -eq 4 ] ถ้าประสิทธิภาพไม่ใช่ปัญหา ทั้งสองฝั่งก็ดูสมเหตุสมผลด้วยเหตุผลคล้ายกัน

    • ไม่ควรมอง [ a = b ] && echo "Oops!" || echo "Expected; phew!" เป็นกฎทั่วไป bash น่าจะตีความบรรทัดนี้เป็น ([ a = b ] && echo "Oops!") || echo "Expected; phew!"
      ดังนั้นถ้าชุดคำสั่งหลัง && ล้มเหลว โค้ดหลัง || ก็จะถูกเรียกอยู่ดี เช่น ถ้า >/dev/full echo "strings match" ล้มเหลวเพราะเขียนไม่ได้ ก็จะพิมพ์ "strings don't match" ออกมา ทั้งที่สตริงตรงกัน ซึ่งต่างจากความหมายของบล็อก if
    • ทางที่ดีควรหลีกเลี่ยงการย่อแบบนั้น ถ้าคุณใช้ set -e ซึ่งเป็นสิ่งที่ควรใช้ if [ a = b ]; then echo "Oops!"; fi จะทำงานตามที่คาด แต่ [ a = b ] && echo "Oops!" จะจบด้วย error เมื่อ expression a ไม่เท่ากับ b
    • ตาม POSIX แล้ว binary primary -a, -o และโอเปอเรเตอร์ (, ) ถูกระบุว่า เลิกแนะนำให้ใช้ ดูรายละเอียดได้ใน "Application Usage" ของ https://pubs.opengroup.org/onlinepubs/9699919799/utilities/t...
    • ไม่ว่าจะใช้ -a หรือใช้ test สองตัวกับ && ถ้าใช้ arithmetic evaluation ของ bash ได้ ก็ไม่จำเป็นต้องออกไปเรียก expr: [ $((1+1)) -eq 2 ]
  • ตั้งแต่หลายปีก่อน ผมเลิกใช้ [ แล้ว test ช่วยตอกย้ำว่านี่ไม่ใช่ไวยากรณ์ แต่เป็นแค่ คำสั่ง เหมือนคำสั่งอื่น ๆ และ man test ก็สะดวกกว่าไปคุ้ย man bash มาก

    • ฟังดูไม่ค่อยเข้ากันนะ GNU Coreutils ไม่ได้มีแค่หน้า manual man test แต่ยังมี man [ ด้วย
      ใน Bash มี help test เป็นชีตสรุปเร็ว ๆ คำสั่ง [ เก่ามาก และมีอยู่แล้วใน Version 7 Unix ตั้งแต่ปี 1979
  • เห็นด้วย [ และ [[ ที่เป็นของ bash โดยเฉพาะทำให้เกิดความสับสนมากว่าแท้จริงแล้วเกิดอะไรขึ้น จนยากจะมั่นใจได้
    แต่การที่ [[ รับประกันว่าเป็นคำสั่ง built-in นั้นย่อมมีเหตุผลในยุคที่ประสิทธิภาพของ shell script ยังมีความหมาย และก็ไม่ได้นานขนาดนั้น

    • อ่านบทความนี้แล้วคิดว่าคงจะใช้แค่ test ต่อไป ไม่ค่อยได้เขียนสคริปต์ bash บ่อย เลยสะดุดกับคำสั่ง if อยู่เสมอ โดยเฉพาะ กฎเรื่องช่องว่าง พอเห็นเหตุผลแล้วก็ชัดเจนมาก และการใช้ test ทำให้เห็นชัดกว่าว่ามันเป็นเพียงการส่งอาร์กิวเมนต์เข้าไปเท่านั้น
  • กับ [ และ test หลุมพรางที่ใหญ่ที่สุดคือ พฤติกรรมเมื่อมีอาร์กิวเมนต์เดียว เช่น ถ้าต้องการตรวจว่าตัวแปรไม่ว่าง อาจเขียนว่า [ -n $FOO ]
    แต่ถ้า FOO ไม่ได้ถูกตั้งค่าไว้ มันจะ expand เป็นไม่มีอะไรเลย ไม่ใช่สตริงว่าง จึงกลายเป็นเหมือน [ -n ] POSIX กำหนดว่ารูปแบบของ [ ที่มีอาร์กิวเมนต์เดียว ถ้าอาร์กิวเมนต์นั้น ซึ่งในที่นี้คือ "-n" ไม่ว่าง จะต้องสำเร็จ ดังนั้นจึงรายงานผิดว่า $FOO ไม่ว่าง ต้องใส่เครื่องหมายคำพูดครอบตัวแปรเสมอ

    • ควรย้ายประโยคสุดท้ายมาไว้ข้างหน้า ให้ใส่เครื่องหมายคำพูดครอบตัวแปร ไม่ใช่ว่า spec ของคำสั่ง built-in test เองมีหลุมพราง แต่หลุมพรางอยู่ที่ตัว shell เอง
      พฤติกรรมที่พูดถึงนั้นสมเหตุสมผล [ "$FOO" ] เป็นรูปแบบที่ตรวจว่าเนื้อหาไม่ว่างเสมอ ไม่ว่าเนื้อหาจะเป็นอะไรก็ตาม แม้จะเป็น "-n" ก็ตาม
    • ใช้ ShellCheck กับสคริปต์ก็พอ
    • ถ้า $FOO มีช่องว่างอยู่ มันจะ expand เป็นหลายอาร์กิวเมนต์ แค่ ใส่เครื่องหมายคำพูดครอบตัวแปรเสมอ
    • [ x"$FOO" != x"" ]
    • ถ้าเป็นกรณีนี้คงใช้ [ -n "${FOO?}" ] เพื่อให้สคริปต์หยุดทันทีเมื่อ $FOO เป็น null หรือไม่ได้ถูกตั้งค่า
  • chubot เคยเขียนเอกสารที่น่าสนใจซึ่งเจาะลึกส่วนที่ละเอียดอ่อนกว่าของ test/[/[[ บทความอื่น ๆ ในบล็อกนั้นก็อธิบายความแปลกของ shell ได้ค่อนข้างน่าสนใจเช่นกัน
    ¹ https://www.oilshell.org/blog/2017/08/31.html
    ² https://www.oilshell.org/blog/2016/11/18.html

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

  • [[ เป็นของ bash โดยเฉพาะ ถ้ารู้ว่าจะใช้แต่ bash ก็ใช้ได้ รายละเอียดบทความอธิบายไว้ดีแล้ว

    • zsh ก็มีนะ :)
      https://zsh.sourceforge.io/Doc/Release/Conditional-Expressio...
    • ใช้ [[ ตลอดก็ได้
      zsh กับ ksh ก็มี และจริง ๆ แล้วค่อนข้างมั่นใจว่าเริ่มมาจาก ksh ตั้งแต่ปี 1988 หรือก่อนหน้านั้น
    • กล่าวคือ test และ [ ถูกระบุไว้ใน POSIX และปกติมักมีอยู่เป็นไบนารีจริง ๆ เพียงแต่คำสั่ง built-in ของ shell อาจบังไว้ได้
      ส่วน [[ ไม่ได้ถูกระบุไว้ใน POSIX และปกติมักมีอยู่แค่เป็น built-in ของ shell เท่านั้น
    • ถ้าไม่ได้ตั้งใจใช้ shell ที่แตกต่างไปเลยอย่าง Fish ก็ไม่เข้าใจว่าทำไมถึงไม่ใช้ Bash
      การมุ่งรองรับแค่ shell ตัวร่วมต่ำสุดดูเหมือนเป็น ความกังวลแบบโบราณ ไปแล้ว
  • ไม่ค่อยเข้าใจว่าทำไมคำสั่ง if สุดท้ายถึงทำให้งง ถ้าเป็นเพราะตอนเริ่มเรียน shell script คนมักสมมติว่า [ เป็นส่วนหนึ่งของภาษา bash script ไม่ใช่โปรแกรมอีกตัวหนึ่ง แบบนี้ก็เข้าใจได้แล้ว ถ้าไม่ใช่ก็อยากให้ช่วยอธิบายว่ามันน่าประหลาดใจตรงไหน

    • ต่อให้ไม่รู้ว่า [ เป็นไบนารี ก็ยังไม่ค่อยเห็นว่าทำไมถึงน่างง มันดูเป็น bash ธรรมดามาก
  • มีรสนิยมแรง ๆ เรื่อง shell ซึ่งไม่ค่อยเข้ากับคนส่วนใหญ่ในโลก
    ผมคิดว่าไม่ควรใช้ [ เลย และควรใช้แต่ test เท่านั้น [ ทำให้เข้าใจผิดว่ากลไกของมันเป็นส่วนหนึ่งของไวยากรณ์ภาษา แต่จริง ๆ แล้วมันเป็นแค่ "โปรแกรม" อีกตัวหนึ่ง ในที่นี้ "โปรแกรม" รวมถึงคำสั่ง built-in และฟังก์ชันด้วย if/||/&& ดูที่สถานะการจบการทำงาน และโปรแกรมไม่สามารถดูสถานะการจบการทำงานของอย่างอื่นได้ นอกจากดูตัวแปรวิเศษ $? ซึ่งหลังจาก expand แล้วก็เป็นแค่สตริง case ดูสตริง แต่ไม่ได้ทำงานโดยอิงสถานะการจบการทำงาน และก็ไม่ได้ตั้งค่าสถานะการจบการทำงานเป็นส่วนหนึ่งของการทำงาน case ... esac ด้วย "โปรแกรม" เป็นสิ่งที่ตั้งค่าสถานะการจบการทำงาน อีกอย่าง [/test ควรใช้เฉพาะเมื่อต้องประเมินโครงสร้างของ filesystem เช่น test -f /dev/null เท่านั้น การประเมินสตริงควรใช้ case แน่นอนว่าพอเห็นสคริปต์ส่วนใหญ่แล้วมันคันไม้คันมือ และสคริปต์ที่ผมเขียน คนอื่นก็มองว่าแปลก

    • มุกย้อนกลับมาหาผมเอง นั่นแหละคือสาระของบทความ
      เวลาผมใช้ shell ผมชอบรูปแบบที่วางโปรแกรมหลัง if ไว้คนละบรรทัด แล้วค่อยไป then เพื่อเน้นว่าสิ่งหลัง if คือ "ดูสถานะการจบการทำงานของคำสั่งสุดท้ายก่อน then" เช่น ในโครงสร้างอย่าง if; ls /tmp/goober; test -d /tmp/goober; echo "I'll always execute the then clause because echo will always return a 0 return code"; then ... fi นั้น if จะดูแค่สถานะการจบการทำงานของ echo สุดท้าย ดังนั้น then clause จึงถูกรันเสมอ
    • เจอเพื่อนร่วมทางเดียวกันแล้ว สคริปต์ที่มี test มา 8 ปีเป็นหลักฐานว่าผมเห็นด้วย เป็นเส้นทางที่โดดเดี่ยว... โทษ Google shell style guide
      นิสัยชอบใช้ test เกิดจากการเขียนสคริปต์ที่ต้องรันได้ทั้งใน sh และ bash แต่ผมก็ยังใช้ต่อ เพราะในเชิงความหมายมันเข้าใจได้มากกว่าการปฏิบัติกับตัวอักษร [ เหมือนเป็นคำสั่ง การที่ ] ไม่ใช่ไบนารีแยกต่างหาก แต่เป็นอาร์กิวเมนต์ของ [ ก็แปลกเหมือนกัน เข้าใจเหตุผลทางเทคนิค แต่ให้ความรู้สึกเหมือน hack
    • อ่านคอมเมนต์ที่นี่แล้ว ดูเหมือนมีคนที่ใช้แต่ test ยังไงก็ไม่เปลี่ยนอยู่หลายสิบคน หลายสิบคนเลย!
  • ผมถูกสอนให้ใช้ [[ เฉพาะเวลาต้องการทำ regex matching เท่านั้น เช่น if [[ "${foo}" =~ ^bar$ ]]; then echo Yes; fi
    นอกนั้นก็ใช้แค่ "test" หรือ "[" ตอนนี้เขียน bash ไป 85,000 บรรทัดแล้ว ไม่ได้จะบอกว่า bash ยอดเยี่ยม แต่จนถึงตอนนี้มันยังตอบโจทย์งานหลายอย่างของผมได้

    • ถ้าต้องการทำ pattern matching ที่ค่อนข้างเรียบง่ายแบบเข้ากันได้กับ POSIX, expr สามารถ match basic regular expression ได้ และยังคืนค่า capture group ได้ด้วย

1: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/e...

  • โดนเล่นงานอยู่พักหนึ่งเพราะความจริงที่ว่าต้องใช้ regular expression ตรง ๆ และไม่ครอบด้วยเครื่องหมายคำพูด ผมเป็นพวกที่ใส่เครื่องหมายคำพูดกับทุกอย่างแทบจะเหมือนศาสนา เลยใช้เวลาพอสมควรกว่าจะหาว่าทำไม regex ง่าย ๆ แบบโง่ ๆ ถึงไม่ match
  • ตัวอย่างนั้นไม่ได้มีความหมายอะไรมาก ใช้ [ "$foo" = bar ] && echo Yes ก็พอ
    สำหรับการ match substring แค่ [ กับ glob * โดยมากก็เพียงพอแล้ว เช่น [ "$bar" = extra* ] && echo '$bar began with extra' ประมาณนี้ dialect ของ regex ใน Bash ค่อนข้างดิบ จึงแทบไม่คุ้มที่จะฝืนใช้ งานที่ซับซ้อนควรใช้เครื่องมืออื่นอย่าง grep, awk, perl จะถูกกว่า ถ้ายึดติดว่าจะทำทุกอย่างด้วย bash แม้กระทั่งงานซับซ้อนที่ต้องการการนำกลับมาใช้ซ้ำที่สูงขึ้น ความเป็นโมดูลมากขึ้น และชนิดข้อมูลในตัว ผลตอบแทนก็จะลดลงอย่างรวดเร็ว