3 คะแนน โดย GN⁺ 2024-12-14 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • พฤติกรรมที่ผู้ใช้สัมผัสได้ในเทอร์มินัลเกิดจากการรวมกันของระบบปฏิบัติการ, shell, terminal emulator และ ขนบของแต่ละโปรแกรม ซึ่งขนบเหล่านี้สอดคล้องกันมากกว่าที่คิด
  • POSIX ครอบคลุมเป็นหลักถึงปฏิสัมพันธ์ระหว่าง terminal emulator, ระบบปฏิบัติการ, shell และยูทิลิตีหลักบางตัว แต่ไม่ได้กำหนดพฤติกรรมละเอียดของโปรแกรมอย่าง htop
  • รูปแบบอย่าง Ctrl-C, q, Ctrl-D, สี ANSI 16 สี, key binding ของ readline, การปิดสีเมื่อเอาต์พุตไปยัง pipe, และความหมายของ - ว่าเป็น stdin/stdout ล้วนพบซ้ำ ๆ
  • แม้พฤติกรรมบางอย่างจะรู้สึกเหมือนเป็นกฎธรรมชาติ แต่จริง ๆ แล้วโปรแกรมหรือไลบรารีรับอินพุตต้องเป็นผู้ implement เอง เช่น การออกจาก ipython ด้วย Ctrl-D และการหยุดค้นหาด้วย Ctrl-C ใน prompt-toolkit
  • เมื่อต้องสร้างโปรแกรมเทอร์มินัลใหม่หรือทำความเข้าใจพฤติกรรมของเครื่องมือ การดู ค่าคาดหวังพื้นฐาน และข้อยกเว้นก่อน ย่อมเป็นแนวทางที่ใช้ได้จริงมากกว่าคิดว่า “อะไรก็เป็นไปได้”

ผู้มีบทบาทสี่ฝ่ายที่สร้างพฤติกรรมของเทอร์มินัล

  • สิ่งที่เกิดขึ้นในเทอร์มินัลโดยทั่วไปมองได้ว่าเป็นผลลัพธ์ที่ผู้มีบทบาทสี่ฝ่ายร่วมกันสร้างขึ้น
    • ระบบปฏิบัติการ
    • shell
    • terminal emulator
    • โปรแกรม ที่กำลังรันอยู่ในขณะนั้น เช่น top, vim, cat
  • ในชุดผสมอย่าง bash, GNOME Terminal, Linux องค์ประกอบสามอย่างแรกค่อนข้างเป็นที่รู้จักดี และพฤติกรรมบางส่วนถูกทำให้เป็นมาตรฐานโดย POSIX
  • โปรแกรมแต่ละตัวดูเหมือนจะทำอะไรก็ได้ แต่ในประสบการณ์ใช้งานจริงมักทำงานในรูปแบบที่ค่อนข้างสอดคล้องกัน

มาตรฐานมีน้อย แต่มีขนบอยู่

  • แทบไม่มีมาตรฐานจริงที่กำหนดพฤติกรรมของโปรแกรมเทอร์มินัลอย่างครอบคลุม
  • แหล่งอ้างอิงที่ใกล้เคียงมีสองอย่าง
    • POSIX: ครอบคลุมเป็นหลักถึงปฏิสัมพันธ์ระหว่าง terminal emulator, ระบบปฏิบัติการ และ shell รวมถึงกำหนดพฤติกรรมบางส่วนของยูทิลิตีหลักอย่าง cp
    • command line interface guidelines: คู่มือว่าด้วยขนบของ command-line interface
  • กฎเหล่านี้ไม่ใช่ข้อกำหนดที่ผู้เขียนโปรแกรมต้องปฏิบัติตามอย่างเคร่งครัด แต่ใกล้เคียงกับ ขนบทางเทคนิค ที่สังเกตจากการใช้เทอร์มินัลมาตลอด 20 ปี
  • มีข้อยกเว้นมากมาย และข้อยกเว้นเหล่านั้นก็มักมีเหตุผลที่สมเหตุสมผล

กฎบางอย่างโปรแกรมต้อง implement เอง

  • ตำแหน่งไฟล์ตั้งค่า, เอาต์พุตของ --help, วิธีเขียนเอาต์พุตทั่วไปไปยัง stdout และเขียนข้อผิดพลาดไปยัง stderr เป็นความรับผิดชอบของโปรแกรมที่ค่อนข้างชัดเจน
  • ในทางกลับกัน พฤติกรรมบางอย่างดูเป็นธรรมชาติ แต่จริง ๆ แล้วโปรแกรมหรือไลบรารีรับอินพุตต้องจัดการเอง
  • ตัวอย่างเด่นคือการออกจาก REPL ด้วย Ctrl-D
    • cat ไม่ต้อง implement แยกต่างหาก เพราะใน cooked mode ระบบปฏิบัติการจะคืนค่า EOF
    • ipython implement การจัดการ Ctrl-D ด้วย โค้ดของ prompt-toolkit
  • เมื่อเข้าใจความรับผิดชอบในการ implement เหล่านี้ ก็จะไม่น่าแปลกใจมากนักที่รายละเอียดพฤติกรรมจะแตกต่างกันเล็กน้อยในแต่ละโปรแกรม

กฎ 1: โปรแกรมแบบไม่โต้ตอบปิดได้ด้วย Ctrl-C

  • โดยทั่วไป โปรแกรมแบบไม่โต้ตอบ จะจบการทำงานเมื่อกด Ctrl-C
  • เพราะถ้าโปรแกรมไม่ได้ตั้ง signal handler สำหรับ SIGINT ค่าเริ่มต้นเมื่อกด Ctrl-C คือการจบการทำงาน
  • กฎนี้ใช้กับโปรแกรมแบบโต้ตอบอย่าง python3, bc, less ตรง ๆ ไม่ได้
    • ในโปรแกรมแบบโต้ตอบ Ctrl-C อาจไม่ได้ปิดโปรแกรม แต่หยุดงานที่กำลังรันอยู่ในขณะนั้น
    • ตัวอย่างคือการค้นหาใน less หรือโค้ด Python ที่กำลังรันใน python3
  • ไลบรารีจัดการอินพุต prompt-toolkit ที่ ipython ใช้ มี โค้ด สำหรับหยุดการค้นหาด้วย Ctrl-C

กฎ 2: TUI มักปิดด้วย q

  • โปรแกรม TUI อย่าง less, htop มักจบการทำงานเมื่อกด q
  • กฎนี้ใช้ไม่ได้กับโปรแกรมที่การปิดด้วย q ไม่เป็นธรรมชาติ
    • tmux
    • text editor

กฎ 3: REPL ปิดด้วย Ctrl-D บนบรรทัดว่าง

  • REPL อย่าง python3, ed มักจบการทำงานเมื่อกด Ctrl-D บนบรรทัดว่าง
  • เมื่อรันโปรแกรมอย่าง cat ใน cooked mode ระบบปฏิบัติการจะคืนค่า EOF สำหรับ Ctrl-D บนบรรทัดว่าง
  • sqlite3, python3, fish, bash และอื่น ๆ ไม่ได้ใช้ cooked mode จริง แต่ implement shortcut นี้เพื่อเลียนแบบพฤติกรรมพื้นฐาน
  • ดูตัวอย่างการ implement ได้สองแห่ง
  • Erlang REPL เป็นข้อยกเว้นที่ไม่ปิดด้วย Ctrl-D

กฎ 4: ไม่ใช้เกิน 16 สีพื้นฐาน

  • โปรแกรมเทอร์มินัลโดยมากไม่ค่อยใช้สีอื่นนอกเหนือจาก สี ANSI 16 สี พื้นฐาน
  • หากระบุสี hex โดยตรง มีโอกาสสูงที่จะชนกับสีพื้นหลังของผู้ใช้
    • เช่น ข้อความ #EEEEEE แทบมองไม่เห็นบนพื้นหลังสีขาว แต่อาจใช้ได้บนพื้นหลังสีเข้ม
  • หากใช้ 16 สีพื้นฐาน มีความเป็นไปได้สูงที่ผู้ใช้จะตั้งค่าสีใน terminal emulator ให้เข้ากับสีพื้นหลังของตนแล้ว
  • แนวทางนี้ยังช่วยลดสมมติฐานเกี่ยวกับสีที่ terminal emulator รองรับ
  • ข้อยกเว้นคือ text editor
    • Helix โดยค่าเริ่มต้นใช้พื้นหลังสีม่วงที่ไม่ใช่สีพื้นฐานของ ANSI
    • Helix ไม่ใช่โปรแกรม “หลัก” และถือว่าผู้ใช้ที่ไม่ชอบชุดสีสามารถเปลี่ยนธีมได้

กฎ 5: รองรับ key binding ของ readline ในระดับหนึ่ง

  • เมื่อมีความหมาย โปรแกรมแทบทั้งหมดจะรองรับ key binding ของ readline ในระดับหนึ่ง
  • หลายโปรแกรมไม่ได้ใช้ readline โดยตรง แต่เลียนแบบ key binding สไตล์ emacs/readline
  • ไม่ใช่ทุกโปรแกรมจะเลียนแบบได้เหมือนกันเป๊ะ
    • atuin ดูเหมือนจะใช้ Ctrl-A เป็น prefix ดังนั้น Ctrl-A จึงไม่ย้ายไปต้นบรรทัด
  • โปรแกรมเหล่านี้ implement บัฟเฟอร์ตัดและวางของตัวเอง ทำให้สามารถลบบรรทัดด้วย Ctrl-U แล้ววางกลับด้วย Ctrl-Y ได้
  • มีข้อยกเว้นด้วย
    • โปรแกรมอย่าง git, cat, nc ไม่มีการรองรับการแก้ไขบรรทัดนอกจาก backspace, Ctrl-W, Ctrl-U
    • text editor แต่ละตัวมีวิธีแก้ไขของตนเอง
  • หัวข้อที่เกี่ยวข้องมีอธิบายเพิ่มเติมใน entering text in the terminal is complicated

กฎ 5.1: Ctrl-W ลบคำสุดท้าย

  • นอกจาก text editor แล้ว ยังไม่เคยเห็นโปรแกรมที่ Ctrl-W ไม่ได้ลบคำสุดท้าย
  • ใน cooked mode โดยค่าเริ่มต้นระบบปฏิบัติการจะลบคำสุดท้ายเมื่อกด Ctrl-W และลบทั้งบรรทัดเมื่อกด Ctrl-U
  • โปรแกรมจำนวนมากเลียนแบบพฤติกรรมพื้นฐานนี้
  • นึกข้อยกเว้นนอกจาก text editor ไม่ออก

กฎ 6: ปิดสีเมื่อเขียนไปยัง pipe

  • โปรแกรมส่วนใหญ่จะปิดสีเมื่อปลายทางเอาต์พุตเป็น pipe
  • ตัวอย่าง
    • rg blah จะไฮไลต์ blah เมื่อเอาต์พุตไปยังเทอร์มินัล แต่จะปิดการไฮไลต์เมื่อเอาต์พุตไปยัง pipe หรือไฟล์
    • ls --color=auto ใช้สีเมื่อเขียนไปยังเทอร์มินัล และไม่ใช้สีเมื่อเขียนไปยัง pipe
  • รูปแบบเอาต์พุตก็อาจเปลี่ยนตามว่าเป็นเอาต์พุตไปยังเทอร์มินัลหรือไม่
    • ls จัดไฟล์เป็นคอลัมน์เมื่อเอาต์พุตไปยังเทอร์มินัล
    • ripgrep จัดกลุ่ม match พร้อมหัวเรื่อง
  • หากอยากบังคับให้เห็นสี สามารถใช้ unbuffer ทำให้เอาต์พุตของโปรแกรมดูเหมือน tty ได้
unbuffer rg blah |  less -R
  • บางโปรแกรมสามารถบังคับสีด้วย flag --color
    • ตัวอย่างข้างต้นทำได้ด้วย rg --color=always | less -R เช่นกัน

กฎ 7: - หมายถึง stdin หรือ stdout

  • โดยทั่วไป หากส่ง - แทนชื่อไฟล์ โปรแกรมจะอ่านจาก stdin หรือเขียนไปยัง stdout ตามทิศทางที่เหมาะสม
  • หากต้องการ format โค้ด Python ใน clipboard ด้วย black แล้วคัดลอกกลับ สามารถรันดังนี้
pbpaste | black - | pbcopy
  • pbpaste เป็นโปรแกรมบน Mac ส่วนบน Linux สามารถทำงานคล้ายกันด้วย xclip
  • โปรแกรมส่วนใหญ่ดูเหมือนจะ implement แนวทางนี้เมื่อมีความหมาย แต่ก็อาจมีข้อยกเว้นมากมาย

ทำไมการเรียนรู้กฎเหล่านี้จึงใช้เวลานาน

  • กฎเหล่านี้มักไม่ได้เรียนรู้แบบระบุชัดในครั้งเดียว แต่ค่อย ๆ เรียนรู้ผ่านหลายขั้น
    • เริ่มจากเรียนรู้ว่ามีกฎอยู่ เช่น “Ctrl-C ปิดโปรแกรม”
    • พบข้อยกเว้น เช่น find ปิดได้ แต่ less ไม่ปิด
    • จับรูปแบบโดยไม่รู้ตัวว่า ในโปรแกรมแบบไม่โต้ตอบจะปิด ส่วนในโปรแกรมแบบโต้ตอบอาจหยุดเฉพาะงานปัจจุบัน
    • ภายหลังจึงสามารถจัดระเบียบเป็นกฎที่ชัดเจนได้
  • ความเข้าใจจำนวนมากเกี่ยวกับเทอร์มินัลยังคงอยู่ในระดับ การจดจำรูปแบบโดยไม่รู้ตัว
  • การเขียนกฎออกมาเป็นข้อความช่วยให้คนอื่นเรียนรู้พฤติกรรมของเทอร์มินัลได้เร็วขึ้นเล็กน้อย

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

 
GN⁺ 2024-12-14
ความคิดเห็นจาก Hacker News
  • เพิ่มเติมคือ โปรแกรมเทอร์มินัลควรเคารพ สีพื้นหน้า/สีพื้นหลัง เริ่มต้นของผู้ใช้ และไม่เปลี่ยนโดยไม่มีเหตุผล
    แม้จะใช้สี ก็ควรอ่านได้ไม่ว่าพื้นหลัง/พื้นหน้าเริ่มต้นและพาเลตสีของเทอร์มินัลจะเป็นแบบใด และถ้าสื่อความหมายด้วยสีอย่างเดียว ความหมายนั้นจะหายไปเมื่อคัดลอก/วาง หรือในเทอร์มินัลที่ไม่รองรับสี
    อีโมจิหรืออักขระที่ไม่ใช่ ASCII ที่ไม่จำเป็นก็ควรใช้ด้วยความระมัดระวัง และในรูปแบบเอาต์พุตมาตรฐานที่เลื่อนผ่านไปเรื่อย ๆ ไม่ควรลบข้อมูลสำคัญอย่างคำเตือนที่แสดงชั่วคราวหรือชื่อไฟล์
    หากต้องการใช้สีสันฉูดฉาดและแอนิเมชันจำนวนมาก อย่างน้อยก็ควรมี ตัวเลือกการตั้งค่า ให้ปิดได้ง่าย

    • เมื่อหลายปีก่อนใน Docker เคยเจอบั๊กที่น่ารำคาญมาก ซึ่งเมื่อสคริปต์บางตัวพิมพ์ อีโมจิ ไปยังเอาต์พุตมาตรฐาน เทอร์มินัลแบบโต้ตอบจะล่มและคอนเทนเนอร์ก็ถูกปิดไปด้วย
      ไม่รู้ว่าสาเหตุรากได้รับการแก้ไขแล้วหรือยัง แต่ทำให้ต้องสร้าง PR ทางอ้อมให้กับหลายโปรเจกต์โอเพนซอร์ส โดยยอมละทิ้งเจตนาทางศิลป์เดิมไปเล็กน้อย: https://github.com/docker-archive/toolbox/issues/695
    • การทำให้สีอ่านง่ายไม่ว่าพื้นหลัง/พื้นหน้าเริ่มต้นและพาเลตสีของเทอร์มินัลจะเป็นอย่างไรนั้น แทบจะเป็นไปไม่ได้ในทางปฏิบัติ
      อย่างไรก็ดี สามารถมี สีที่ผู้ใช้กำหนดเอง ได้
    • ตามที่บทความต้นฉบับระบุไว้ กฎเหล่านี้ไม่ใช่ข้อกำหนดเชิงสั่งการ แต่เป็น ข้อสังเกตทางเทคนิค
      โดยรวมแล้วใกล้เคียงกับการรวบรวมสิ่งที่พอจะสมมติได้ว่าโปรแกรมเทอร์มินัลทั้งหมดทำตามอยู่แล้ว
    • มีความพยายามที่จะทำให้เรื่องนี้เป็นมาตรฐาน: https://bixense.com/clicolors/
    • อีโมจิและอักขระที่ไม่ใช่ ASCII ที่ไม่จำเป็นควรถูกเน้นย้ำให้มากขึ้น
      ทุกวันนี้ผู้เขียนบางคนสร้างเครื่องมือโดยสมมติว่าผู้ใช้ใช้ ฟอนต์ที่แพตช์แล้ว ซึ่งทำให้ความสามารถในการใช้งานเสียหายอย่างมาก
  • เป็นบทความที่ดี
    ในเมื่อบอกว่าการเรียนรู้ธรรมเนียมเหล่านี้ยาก ก็ขอแนะนำ Command Line Interface Guidelines ซึ่งเป็นเอกสารอ้างอิงที่ชอบใช้เวลาคิดเรื่อง CLI ด้วย: https://clig.dev
    มีทั้งกฎจากบทความต้นฉบับ เช่น ออกจากโปรแกรมด้วย Ctrl-C, รับ - ทางอินพุตมาตรฐาน, ปิดสีเมื่ออยู่ในไพป์ และยังมีเนื้อหาอื่น ๆ อีกมาก

    • เอกสารนี้ถูกอ้างถึงไว้แล้วตั้งแต่ช่วงต้นของบทความ ถัดจากสารบัญทันที
    • เอกสารอ่านเพิ่มเติมของ CLIG ก็ควรแนะนำเช่นกัน: https://clig.dev/#further-reading
      รวมถึงเอกสาร POSIX, GNU, Unix, คู่มือ Heroku CLI และคู่มือแอป CLI แบบ 12-factor
    • มีอันนี้ด้วย: https://usage.jdx.dev/
  • ประเด็นที่ว่า “เอาต์พุตปกติควรออก stdout และข้อผิดพลาดควรออก stderr” สำคัญมากจริง ๆ
    เอาต์พุตมาตรฐาน ควรเป็นที่สำหรับข้อมูลที่โปรแกรมถูกขอให้สร้างเท่านั้น ถ้าผู้ใช้ขอข้อมูล JSON ในเอาต์พุตมาตรฐานก็ควรมีเพียงอ็อบเจกต์ JSON นั้นอย่างถูกต้องเท่านั้น และห้ามมีอย่างอื่นปนเด็ดขาด
    ชื่อ ข้อผิดพลาดมาตรฐาน จริง ๆ แล้วตั้งได้ไม่ค่อยถูกนัก มันใกล้เคียงกับ “สตรีมผู้ใช้มาตรฐาน” ที่ใช้ส่งข้อความทั้งหมดซึ่งผู้ใช้ควรอ่านในเทอร์มินัลมากกว่า ไม่ใช่แค่ข้อความข้อผิดพลาด แต่รวมถึงข้อความสถานะและเอาต์พุตแบบละเอียดด้วย
    แบบนี้จึงจะไพป์เอาต์พุตของโปรแกรมไปยังโปรแกรมอื่นได้อย่างลื่นไหล และข้อมูลที่ไม่ใช่ข้อมูลหลักก็ยังไปที่เทอร์มินัลหรือถูกรีไดเร็กต์ไปที่อื่นได้
    ถ้าโปรแกรมสามารถสร้าง file descriptor สำหรับการเชื่อมต่อเทอร์มินัลตามวัตถุประสงค์เฉพาะได้ง่าย ๆ และสามารถบันทึกหมายเลขไว้ในคู่มือเหมือนรหัสออกจากโปรแกรมได้ก็คงดี เพื่อความเข้ากันได้ ค่าเริ่มต้นอาจส่งไปยัง stdout หรือ stderr ได้ และคิดว่าจะลองทดลองเรื่องนี้ดูสักหน่อย

    • นี่เป็นหนึ่งในส่วนที่ PowerShell ทำได้ถูกต้อง
      มี สตรีมที่แตกต่างกัน 6 แบบ และแต่ละแบบสามารถดักจับแยกกันหรือตั้งค่าต่างกันได้: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_output_streams?view=powershell-7.4
    • ถ้าเป็นกรณีที่แอปไม่ได้สร้างเอาต์พุต การส่งข้อความนั้นไป stderr อาจทำให้การประมวลผลอย่าง grep ไม่ทำงานและทำให้สับสนได้
    • ไม่แน่ใจว่าชอบชื่อ “สตรีมผู้ใช้มาตรฐาน” หรือไม่ แต่เห็นด้วยกับเนื้อหาที่เหลือ
  • เพิ่มอีกไม่กี่อย่าง ถ้าเพิ่งเคยได้ยินคีย์ไบน์ดิง Ctrl-E, Ctrl-W ของ readline/emacs เป็นครั้งแรก ก็ควรรู้ไว้ว่าแหล่งป้อนข้อความส่วนใหญ่ของ macOS ใช้คีย์ไบน์ดิงเหล่านี้
    ถ้าใช้ macOS ลองกด Ctrl-E, Ctrl-W, Ctrl-U ในแถบที่อยู่ของเบราว์เซอร์ตอนนี้ได้เลย
    หากใช้โปรแกรมบรรทัดคำสั่งที่ไม่รองรับการแก้ไขบรรทัดเลย สามารถติดตั้ง rlwrap แล้วรัน REPL ภายใต้นั้นได้ ตัวอย่างเช่น Standard ML of New Jersey มี REPL แต่ไม่มีฟีเจอร์แก้ไขบรรทัด จึงเสริมได้ด้วย rlwrap smlnj
    สำหรับคำว่า “อย่าใช้มากกว่า 16 สี” อยากขยายต่อว่า “อย่าใช้มากกว่า 8 สี หรืออย่างน้อยก็ทำให้ตั้งค่าสีได้” พาเลตสียอดนิยมอย่าง Solarized และ Base 16 ค่าเริ่มต้นมักใช้สี “สว่าง” เป็นเฉดเทาหลายระดับ ดังนั้นสีเขียวสว่างที่ผู้เขียนคิดไว้ อาจกลายเป็นสีเทาเดียวกับข้อความปกติในความเป็นจริง

    • ถ้ากด Ctrl-W ในแถบที่อยู่ของ macOS ในเบราว์เซอร์ส่วนใหญ่ที่เคยใช้ แท็บปัจจุบันจะถูกปิด
      เทอร์มินัลอีมูเลเตอร์ Alacritty ที่ผมใช้ก็เป็นแบบนั้น และตัวจัดการไฟล์ส่วนใหญ่ที่มีแท็บก็ทำงานแบบนั้นเช่นกัน เท่าที่จำได้ Windows Explorer ตอนนี้ก็น่าจะเป็นแบบนั้นแล้ว แต่ไม่ได้ใช้ Windows มานานพอสมควร
  • ธรรมเนียมการกด Ctrl-D เพื่อออกจาก REPL ทำให้ผมโดน GHCi เล่นงานอยู่เสมอ
    ปกติผมกด Ctrl-D แล้วพอไม่มีอะไรเกิดขึ้นก็จะงง จากนั้นค่อยนึกได้ว่าใน GHCi ใช้ไม่ได้ แล้วจึงรัน :q
    จากนั้น Ctrl-D จะใส่อักขระที่มองไม่เห็นไว้หน้าสุดของอินพุต ทำให้เกิดข้อผิดพลาด “lexical error at character '\EOT'” และต้องพิมพ์ :q ใหม่อีกครั้งโดยไม่มีคำนำหน้าที่มองไม่เห็นนั้น GHCi ถึงจะปิด

    • ถ้าลองออกจาก vi บนคีย์บอร์ดของประเทศที่ไม่คุ้นเคย จะรู้สึกว่านั่นยากยิ่งกว่า
    • ผมปิด GHCi ด้วย Ctrl-D เสมอ
      แทบไม่รู้ด้วยซ้ำว่าวิธีอื่นคืออะไร มีการตั้งค่าอะไรที่เปลี่ยนพฤติกรรมนี้หรือเปล่า?
    • ใช้ Windows อยู่หรือเปล่า?
  • อีกอย่างที่ตกหล่นไปคือพฤติกรรมที่มอง ~ เป็นโฮมไดเรกทอรี
    เรื่องนี้ดูเหมือนเป็น ฟีเจอร์ของเชลล์ ไม่ใช่ POSIX API ใน Go นั้น os.ReadFile("~/.bashrc") ใช้งานไม่ได้ แต่ในเชลล์ echo ~/~/~ จะถูกขยายเป็นประมาณ /home/jrockway/~/~
    yourprogram ~/path/to/file ใช้ได้เสมอ แต่ใน REPL ที่ถามชื่อไฟล์อาจใช้ไม่ได้ ในทางปฏิบัติซอฟต์แวร์จำนวนมากรองรับสิ่งนี้ จึงน่าจะนับรวมใน “สิ่งที่โปรแกรม TUI ส่วนใหญ่ทำ” ได้

    • การขยายทิลด์เป็นส่วนหนึ่งของการขยายคำของเชลล์: https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_06_01
      ในการขยายคำ การขยายทิลด์เป็นขั้นตอนแรก แล้วตามด้วยการแยกฟิลด์ การขยายชื่อพาธ และการลบเครื่องหมายอัญประกาศ
      ข้อกำหนดของเชลล์ที่เกี่ยวข้องอยู่ที่ https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_06 และดูข้อกำหนดทั้งหมดได้ที่ https://pubs.opengroup.org/onlinepubs/9799919799/
    • ผมคิดว่าสิ่งนี้ควรเป็นฟีเจอร์ของเชลล์ต่อไป
      หากเครื่องมือเพิ่ม การขยายทิลด์ เอง ก็ต้องรู้โฮมไดเรกทอรีของผู้ใช้ปัจจุบัน ทำให้การติดตั้งใช้งานซับซ้อนขึ้น และอาจเกิดการทำแบบไม่ครบ เช่น รองรับแค่ ~ แต่ไม่รองรับ ~name
      ในเชลล์ POSIX นั้น ~name จะขยายเป็นโฮมไดเรกทอรีของผู้ใช้นั้น ดังนั้นจึงต้องค้นหาโฮมไดเรกทอรีของผู้ใช้任意 รวมถึงผู้ใช้ของระบบด้วย ขึ้นอยู่กับการตั้งค่า NSS เรื่องนี้อาจซับซ้อนกว่าการอ่าน /etc/passwd มาก
      ถ้าเป็น TUI แบบโต้ตอบ การทำการขยายทิลด์อาจมีประโยชน์ แต่ในกรณีนั้นก็ควรสมบูรณ์เท่าเชลล์ โปรแกรม CLI ไม่ควรทำการขยายแบบเวทมนตร์เช่นนี้จะดีกว่า
    • ถ้าจะใส่สิ่งนี้ใน REPL ของตัวเอง ต้องจริงจังมากกับการทำเอกสาร การ escape และการจัดการเครื่องหมายอัญประกาศ
      จะเกิดปัญหาว่าถ้ามีไฟล์ชื่อ "~" จะทำอย่างไร, "~username" จะทำอย่างไร, จะ escape/quote อย่างไร, ถ้าไม่มี $HOME หรือค่าต่างจากโฮมจริงจะทำอย่างไร, และบน Windows จะทำอย่างไร
      ข้อเท็จจริงที่ไม่ค่อยมีคนรู้คือ ใน ssh หากพิมพ์ "~." หลังขึ้นบรรทัดใหม่ ไคลเอนต์จะบังคับปิดการเชื่อมต่อจากฝั่งไคลเอนต์ และเซิร์ฟเวอร์ทำอะไรไม่ได้ ดังนั้นไม่ควรเอาไปใส่ใน workflow
    • กรณีที่เรื่องนี้กวนใจผมอยู่เสมอคือรูปแบบอย่าง ./myprogram --config=~/.config/myprogram
      แน่นอนว่า flag parser จำนวนมากสามารถใช้รูปแบบที่เชลล์ขยายให้ได้คือ ./myprogram --config ~/.config/myprogram ดังนั้นจึงเหมือนว่ารู้เรื่องนี้หรือรองรับโดยบังเอิญ
      แต่เพราะ muscle memory ผมจึงเผลอพิมพ์ = ทุกที น่าจะเป็นนิสัยที่เกิดจากไลบรารี parsing flag ที่ที่ทำงานเก่า และด้วยเหตุผลเดียวกันผมก็ใช้ --underscores_like_this อยู่เสมอ แทนที่จะใช้ --hyphens-like-normal-people
    • ชี้ประเด็นได้ดี
      โดยเฉพาะ .. ถูกจัดการในระดับ OS API จึงคาดหวังได้ง่ายว่าใน POSIX ~ ก็น่าจะเป็นแบบนั้นด้วย
  • เหตุผลหลักที่ผมชอบ CLI มากกว่า GUI หรือบางครั้งมากกว่า TUI คือมันให้ความรู้สึกว่า สอดคล้องกัน มากกว่า
    มันมีธรรมเนียมอยู่ และการทำตามธรรมเนียมทั้งหมดใน CLI นั้นง่ายกว่าการออกแบบ GUI ที่ดีมาก ดังนั้นผลลัพธ์จึงมักมีคุณภาพสูงกว่า
    ผมคิดอยู่บ่อยๆ ว่าจะนำคุณสมบัตินี้ไปสู่ GUI ได้อย่างไร แต่จนถึงตอนนี้คำตอบที่ดีที่สุดก็ประมาณว่า “ทุ่มแรงมากๆ” หรือ “ลดความคาดหวังลง”

    • ผมคิดว่าเหตุผลใหญ่คือ ยกเว้น macOS แล้ว ธรรมเนียมของ GUI ไม่ได้ถูกบังคับใช้อย่างแข็งแรงใน OS และไม่สามารถเลือกใช้ได้ง่าย
      OS อื่นๆ ไม่สามารถทำให้ meta key สำหรับชอร์ตคัต GUI ลงหลักปักฐานได้ จึงมีการชนกันอย่าง ctrl-c มากกว่าเยอะ
      ค่าเริ่มต้นของแอปพลิเคชันเฟรมเวิร์กที่เป็นฐานของแอป native บน macOS มีการทำงานคีย์บอร์ดร่วมกันให้มากมาย ถ้าใช้คลาสแถบเมนูพื้นฐานและคำสั่งพื้นฐาน Command O, N, Q, X, C, V ฯลฯ ก็แทบได้มาฟรี และถ้าใช้ช่องข้อความมาตรฐาน การนำทางด้วย Command/Option/Fn + ปุ่มลูกศรก็มีให้อัตโนมัติ
      Windows ก็ค่อนข้างดีในแง่นี้เช่นกัน แต่การที่ปุ่ม modifier หลักเป็น Ctrl อาจรบกวนการใช้งานแอปที่อิงเทอร์มินัล และ UI framework หลายตัวภายใน OS ดูเหมือนจะมีค่าเริ่มต้นต่างกัน
      ในโลก Linux น่าจะเป็นไปได้ก็ต่อเมื่อมีใครสักคนออกแบบแอปพลิเคชันเฟรมเวิร์กที่ครบถ้วน ดิสโทรต่างๆ ทำให้เป็นมาตรฐาน และพอร์ตแอปเข้ามา window manager ไม่อยากกำหนดพฤติกรรมภายในหน้าต่าง, GUI toolkit ไม่อยากนิยามค่าเริ่มต้นทั้ง OS, และ desktop environment กับดิสโทรก็ไม่อยากรับหน้าที่บังคับใช้หรือพอร์ต UI framework
  • โปรแกรมไม่ควรเพิ่มไฟล์ลงในโฮมไดเรกทอรี และควรเคารพการตั้งค่าอย่าง XDG_CONFIG_HOME

    • เห็นด้วย แต่บทความนี้ตามที่ผู้เขียนพยายามทำให้ชัดเจน ดูจะเป็นการพรรณนามากกว่าการสั่งว่าควรทำอย่างไร
      ไม่ได้พยายามโน้มน้าวว่าแอปพลิเคชันควรทำงานอย่างไร แต่เป็นการไล่รายการพฤติกรรมที่พบได้บ่อยในแอปพลิเคชัน
  • ตัวอย่างข้อยกเว้นของกฎที่ว่า “Ctrl-W ควรลบคำสุดท้าย” คือ mysql(1)
    mysql ลิงก์กับ editline แทน readline ดังนั้นโดยค่าเริ่มต้น Ctrl-W จะลบทั้งหมดไปจนถึงต้นบรรทัด ไม่ใช่แค่คำสุดท้าย
    ช่วงหนึ่งที่ต้องใช้มัน เคียวรีที่อุตส่าห์ตั้งใจเขียนหายไปทั้งก้อน จนแทบจะคลั่งจริง ๆ

    • มันอาจแย่กว่านั้นได้อีก
      สมัยมหาวิทยาลัยเคยโชคร้ายลองใช้ sqlplus เพื่อเชื่อมต่อกับฐานข้อมูล Oracle แต่ไม่เพียงคีย์ลัดเทอร์มินัลอย่าง Ctrl-W จะใช้ไม่ได้เลย ยังเลื่อนเคอร์เซอร์ย้อนกลับในบรรทัดไม่ได้ และไม่มีการไล่ดู history เพื่อกลับไปคำสั่งก่อนหน้าด้วย
      แค่พิมพ์ผิดตัวเดียวก็ต้องพิมพ์ทั้งหมดใหม่ตั้งแต่ต้น และถึงจะพิมพ์ / ตัวเดียวเพื่อรันคำสั่งล่าสุดซ้ำได้ แต่มันก็ไม่ได้ช่วยแก้คำผิด
      Ctrl-L เพื่อล้างหน้าจอก็ใช้ไม่ได้ ต้องรัน clear scr เองแทน สงสัยถ้าใช้คำสั่งแค่ clear คงดูชัดเจนเกินไป
  • อยากเพิ่มด้วยว่าโปรเซสที่รันระยะยาวควรอ่านการตั้งค่าใหม่เมื่อได้รับ SIGHUP

    • นี่ไม่ใช่รายการ “สิ่งที่ควรทำ” แต่เป็นรายการ “สิ่งที่โดยทั่วไปเป็นแบบนั้น”
      ความต่างนั้นใหญ่พอสมควร และน่าเสียดายที่ข้อนี้ไม่ได้แพร่หลายพอจะอยู่ในรายการเดิม
    • ว่ากันอย่างเคร่งครัด ข้อนี้มีแนวโน้มจะอยู่นอกขอบเขตของบทความนี้
      โปรเซสที่รันระยะยาวซึ่งตอบสนองต่อ SIGHUP มักรันแบบแยกออกมาโดยไม่มี TTY ควบคุม จึงค่อนข้างฝืนที่จะเรียกว่า “โปรแกรมเทอร์มินัล”
    • พฤติกรรมนี้จริง ๆ แล้วใช่ว่าจะนำไป implement ได้ง่ายไหม?
      ต้องระวังไม่ให้แอปพลิเคชันเผลอแคชไม่เพียงสถานะการตั้งค่า แต่รวมถึงสถานะที่สืบเนื่องมาจากการตั้งค่าด้วย ไม่อย่างนั้นมันจะไม่สอดคล้องกัน
      สำหรับแอปง่าย ๆ อาจทำได้ง่าย แต่แอปส่วนใหญ่ที่ต้องมีไฟล์ตั้งค่าไม่ได้เรียบง่ายขนาดนั้น