1 คะแนน โดย GN⁺ 2024-07-09 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • การแก้ไขข้อความในบรรทัดคำสั่งดูเหมือนเป็นฟีเจอร์พื้นฐาน แต่แต่ละโปรแกรมจัดการอินพุตไม่เหมือนกัน ทำให้ ปุ่มเดียวกันอาจทำงานแตกต่างกันโดยสิ้นเชิง
  • ประสบการณ์ป้อนข้อมูลในเทอร์มินัลโดยทั่วไปแบ่งได้เป็น 4 แนวทาง: ไม่มีระบบอินพุต, GNU readline, ไลบรารีทางเลือกอย่าง libedit, และระบบอินพุตของตัวเอง
  • ในเครื่องมือง่าย ๆ อย่าง cat, nc, dash ปุ่มลูกศรอาจถูกพิมพ์ออกมาเป็น ^[[D แต่ฟีเจอร์ที่เทอร์มินัลมีให้ เช่น Ctrl+W, Ctrl+U, Ctrl+C, Ctrl+Z ยังใช้งานได้
  • เครื่องมือที่ใช้ readline เช่น bash, psql, irb, python3 มีประวัติคำสั่ง, ปุ่มลูกศร, การค้นหาด้วย Ctrl+R, การเลื่อนด้วย Ctrl+A/Ctrl+E และสามารถใช้ rlwrap เพื่อเพิ่มประสบการณ์คล้ายกันให้เครื่องมืออื่นได้
  • หากแยกแยะระบบอินพุตที่ใช้อยู่ได้ ก็จะตัดสินได้เร็วขึ้นว่าทำไมปุ่มลูกศรหรือ Ctrl+R จึงแสดงผลต่างกัน และเลือกดูเอกสารหรือใช้ทางเลี่ยงอย่าง rlwrap ได้ง่ายขึ้น

ประสบการณ์ป้อนข้อมูลที่เปลี่ยนไปตามแต่ละโปรแกรม

  • วิธีป้อนและแก้ไขข้อความในเทอร์มินัลแตกต่างกันมากตามแต่ละโปรแกรม
  • ประเภทหลัก ๆ แบ่งได้ดังนี้
    • cat, nc, git commit --interactive เป็นต้น ไม่รองรับปุ่มลูกศร จึงอาจเห็นสตริงอย่าง ^[[D^[[D^[[C^[[C^
    • irb, python3 บน Linux เป็นต้น ใช้ไลบรารี readline เพื่อให้ฟีเจอร์แก้ไขพื้นฐานอย่างประวัติคำสั่งและปุ่มลูกศร
    • บางกรณีอย่าง /usr/bin/python3 บน Mac ปุ่มลูกศรทำงานได้ แต่ไม่รองรับ Ctrl+left หรือการค้นหาย้อนหลังด้วย Ctrl+R
    • fish, ipython3, micro, vim เป็นต้น ใช้ระบบอินพุตของตัวเอง

โหมด 1: สถานะพื้นฐานที่ไม่มีระบบอินพุต

  • หากโปรแกรมรับแค่ข้อความด้วยการเรียกอย่าง fgets() และไม่ได้มีฟีเจอร์แก้ไขแยกต่างหาก ปุ่มลูกศรอาจถูกแสดงเป็น escape sequence แทนที่จะเป็นคำสั่งแก้ไข
  • ตัวอย่างเช่น เมื่อกดลูกศรซ้ายใน dash จะเห็น ^[[D ถูกพิมพ์ลงในเทอร์มินัลดังนี้
$ ls l-^[[D^[[D^[[D
  • ถึงอย่างนั้น ฟังก์ชันควบคุมพื้นฐานที่เทอร์มินัลมีให้ก็ยังคงอยู่
    • ป้อนข้อความ
    • Backspace
    • Ctrl+W: ลบคำก่อนหน้า
    • Ctrl+U: ลบทั้งบรรทัด
    • Ctrl+C: interrupt โปรเซส
    • Ctrl+Z: พักโปรเซสชั่วคราว
  • แม้อยู่ในสภาพแวดล้อมแบบนี้ การใช้ Ctrl+W ก็ช่วยลบเป็นคำได้โดยไม่ต้องกด Backspace หลายครั้ง
  • สามารถตรวจสอบ control code ที่เทอร์มินัลรองรับได้ด้วย stty -a

โหมด 2: เครื่องมือที่ใช้ readline

  • readline เป็นไลบรารี GNU ที่ทำให้การป้อนข้อความสะดวกขึ้น และใช้ในเครื่องมือแบบโต้ตอบจำนวนมาก
  • คีย์ลัด readline ที่ใช้บ่อยมีดังนี้
    • Ctrl+E หรือ End: ไปท้ายบรรทัด
    • Ctrl+A หรือ Home: ไปต้นบรรทัด
    • Ctrl+left/right arrow: เลื่อนไปข้างหน้าหรือถอยหลังทีละคำ
    • ลูกศรขึ้น: ไปยังคำสั่งก่อนหน้า
    • Ctrl+R: ค้นหาประวัติ
  • Ctrl+W, Ctrl+U ของโหมดพื้นฐานก็ใช้ได้เช่นกัน แต่ใน readline Ctrl+U จะลบตั้งแต่เคอร์เซอร์ถึงต้นบรรทัด ไม่ใช่ลบทั้งบรรทัด
  • การค้นหาประวัติด้วย Ctrl+R ใน bash เป็นฟีเจอร์ที่ readline มีให้
  • psql, irb, python3 เป็นต้น ก็เป็นตัวอย่างของโปรแกรมที่ใช้ readline
  • ดูรายการคำสั่งแก้ไขของ readline ทั้งหมดได้ใน คู่มือ readline

เพิ่ม rlwrap ให้เครื่องมือที่ไม่มี readline

  • แม้โปรแกรมอย่าง nc จะไม่รองรับ readline แต่หากรันด้วย rlwrap nc ก็จะใช้งานได้คล้ายโปรแกรมที่รองรับ readline
  • rlwrap สามารถเพิ่มความสะดวกในการใช้งานเครื่องมือที่ไม่มี readline ได้อย่างมาก
  • ตั้งค่า autocomplete แบบกำหนดเองได้ด้วย แต่ไม่ใช่สิ่งที่ได้ลองใช้งานจริง

เหตุผลที่เครื่องมือไม่ใช้ readline

  • มีหลายเหตุผลที่เครื่องมือบางตัวไม่ใช้ readline
    • โปรแกรมที่เรียบง่ายมากอย่าง cat, nc อาจไม่อยากเพิ่ม dependency ที่ค่อนข้างใหญ่
    • readline ไม่ใช่ LGPL แต่เป็น ไลเซนส์ GPL จึงอาจไม่เข้ากับไลเซนส์ของโปรแกรม
    • หากโปรแกรมมีเพียงบางส่วนที่เป็นแบบโต้ตอบ และอินพุตใกล้เคียงกับตัวอักษรเดี่ยวอย่าง y หรือ n การรองรับ readline อาจมีลำดับความสำคัญต่ำ
  • git มีฟีเจอร์แบบโต้ตอบอย่าง git add -p แต่เมื่อจำเป็นต้องป้อนข้อความยาว ๆ ที่มีความหมาย ก็มักจะเปิด text editor แทน
  • idris2 ไม่ใช้ readline เพื่อลด dependency ให้น้อยที่สุด และแนะนำให้ใช้ rlwrap หากต้องการฟีเจอร์โต้ตอบที่ดีกว่า

วิธีตรวจสอบว่าตอนนี้ใช้ readline อยู่หรือไม่

  • วิธีตรวจสอบที่ง่ายที่สุดคือลองกด Ctrl+R
  • หากเห็นพรอมป์ต์ต่อไปนี้ ก็มีโอกาสสูงว่ากำลังใช้ readline
(reverse-i-search)`':
  • ไลบรารีอื่นก็อาจใช้คำเดียวกันได้ จึงไม่ใช่วิธีแยกแยะที่แน่นอน แต่ยังไม่พบระบบอื่นที่ใช้คำว่า reverse-i-search สำหรับการค้นหาประวัติ

คีย์ลัด readline กับ Emacs

  • คีย์ลัด Ctrl+A เพื่อไปต้นบรรทัด และ Ctrl+E เพื่อไปท้ายบรรทัด อาจถือได้ว่ามาจาก Emacs
  • ใน Emacs, Ctrl+A และ Ctrl+E ก็ทำงานเหมือนกับ readline
  • ในทางกลับกัน Ctrl+W, Ctrl+U ไม่ได้ทำงานเหมือนกันใน Emacs กับเทอร์มินัล
  • อ่านประวัติเพิ่มเติมของโปรเจกต์ readline ได้ที่ ประวัติโปรเจกต์ Readline

โหมด 3: ไลบรารีอินพุตทางเลือกอย่าง libedit

  • /usr/bin/python3 บน Mac ใกล้เคียงกับสถานะกึ่งกลางที่รองรับฟีเจอร์ readline เพียงบางส่วน
  • ตัวอย่างเช่น เมื่อกด Ctrl+left arrow อาจไม่ได้เลื่อนทีละคำ แต่พิมพ์ ;5D ออกมาแทน
$ python3
>>> importt subprocess;5D
  • ในการติดตั้ง Python ค่าเริ่มต้นของ Mac OS โมดูล Python readline จริง ๆ แล้วอิงกับ libedit
  • คาดว่าเป็นเพราะ readline ใช้ไลเซนส์ GPL
  • สามารถตรวจสอบได้ว่า Python ตัวนั้นใช้ libedit หรือไม่ด้วยคำสั่งต่อไปนี้
$ python3 -c "import readline; print(readline.__doc__)"
Importing this module enables command line editing using libedit readline.
  • Python ที่ติดตั้งบน Linux หรือผ่าน Homebrew โดยทั่วไปจะใช้ readline
  • Python 3.13 มีแผนจะถอด dependency ต่อ readline และใช้ไลบรารีของตัวเอง ดังนั้นในอนาคตคำกล่าวว่า “Python ใช้ readline” อาจไม่ถูกต้องเสมอไป

โหมด 4: ระบบอินพุตของตัวเอง

  • โปรแกรมบางตัวใช้ระบบแก้ไขข้อความของตัวเอง และบางครั้งก็มีฟีเจอร์หลากหลายกว่า readline
  • ตัวอย่างที่ใช้ระบบอินพุตของตัวเองมีดังนี้
    • text editor บนเทอร์มินัลส่วนใหญ่ เช่น nano, micro, vim, emacs
    • เชลล์อย่าง fish
    • line editor ของ zsh อย่าง zle
    • REPL อย่าง ipython
    • เครื่องมืออย่าง atuin
  • ในระบบเหล่านี้อาจพบฟีเจอร์ต่อไปนี้
    • autocomplete ที่ปรับให้เข้ากับเครื่องมือมากขึ้น
    • การจัดการประวัติที่ดีกว่า รวมถึง syntax highlighting
    • คีย์ลัดมากขึ้น

ระบบอินพุตของตัวเองที่ได้รับแรงบันดาลใจจาก readline

  • Atuin เป็นเครื่องมือค้นหาประวัติเชลล์ และแม้การทำอินพุตจะเป็น implementation ของตัวเอง แต่ก็ได้รับแรงบันดาลใจจาก readline
  • Atuin ใช้ประโยชน์จากการที่ผู้ใช้คุ้นเคยกับ key binding ของ readline ทำให้ key binding เหล่านั้นทำงานได้แม้ไม่ได้ใช้ readline โดยตรง
  • prompt_toolkit ที่ IPython ใช้ก็รองรับตัวเลือกหลายอย่าง รวมถึง key binding สไตล์ vi
  • ค่าเริ่มต้นของ prompt_toolkit คือการรองรับ key binding สไตล์ readline
  • คล้ายกับที่หลายโปรแกรมรองรับ key binding พื้นฐานแบบ vim เช่น j สำหรับลง และ k สำหรับขึ้น
  • ตัวอย่างเช่น Fastmail รองรับ j, k แม้คีย์ลัดส่วนใหญ่จะไม่ได้เกี่ยวข้องกับ vim มากนัก
  • ระบบอินพุตของตัวเองที่ได้รับแรงบันดาลใจจาก readline อาจเข้ากันกับ readline ได้ไม่สมบูรณ์ในรายละเอียดปลีกย่อย แต่สำหรับผู้ใช้ที่ใช้เพียงคีย์ลัดพื้นฐานไม่กี่อย่าง ก็อาจไม่ใช่ปัญหาใหญ่
  • ระบบแบบนี้โดยทั่วไปอาจมี autocomplete ดีกว่าการใช้ readline อย่างเดียว

เชลล์ที่รองรับโหมด vi

  • bash, zsh, fish ล้วนรองรับ โหมด vi สำหรับการป้อนข้อความ
  • ใน แบบสำรวจที่ไม่เป็นวิทยาศาสตร์ บน Mastodon มี 12% ตอบว่าใช้โหมด vi
  • readline เองก็มีโหมด vi และการรองรับโหมด vi ของ bash ทำงานผ่านสิ่งนี้
  • โปรแกรมอื่น ๆ จำนวนมากที่ใช้ readline ก็อาจมีโหมด vi ได้เช่นกัน

ลำดับการวินิจฉัยสถานะอินพุต

  • หากรู้ว่าใช้ระบบอินพุตแบบใด ก็จะคาดเดาพฤติกรรมของบรรทัดคำสั่งได้ง่ายขึ้น
  • ที่พรอมป์ต์อินพุต สามารถพิจารณาตามลำดับต่อไปนี้
    • หากปุ่มลูกศรไม่ทำงาน มีโอกาสสูงว่าไม่มีระบบอินพุต แต่ยังใช้ Ctrl+W, Ctrl+U ได้ และหากจำเป็นก็เพิ่ม rlwrap ได้
    • หาก Ctrl+R แสดง reverse-i-search มีโอกาสสูงว่าเป็น readline และสามารถใช้คีย์ลัด readline ที่คุ้นเคยกับฟีเจอร์ประวัติพื้นฐานได้
    • หาก Ctrl+R ทำงานอย่างอื่น มีโอกาสสูงว่าเป็นไลบรารีอินพุตของตัวเอง ซึ่งโดยรวมอาจทำงานคล้าย readline และหากจำเป็นก็ควรตรวจเอกสาร
  • ยังมีความซับซ้อนเกี่ยวกับอินพุตอื่น ๆ ที่ไม่ได้กล่าวถึงที่นี่
    • ปัญหาเกี่ยวกับ ssh, tmux
    • ตัวแปรสภาพแวดล้อม TERM
    • ความแตกต่างของการรองรับคัดลอก·วางในแต่ละเทอร์มินัล เช่น GNOME Terminal, iTerm, xterm
    • Unicode
    • ปัญหาอื่น ๆ อีกหลายอย่างเกี่ยวกับอินพุต

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

 
GN⁺ 2024-07-09
ความคิดเห็นบน Hacker News
  • บทความของ Julia ดีเสมอ สิ่งที่ยังขาดไปคือ ถ้าใช้ stty ในเชลล์สคริปต์ ก็สามารถเปลี่ยนพฤติกรรมหลายอย่างของเทอร์มินัลได้ รวมถึงวิธีจัดการอินพุตด้วย
    การทดลองที่เคยทำด้วย sh และ stty: https://gist.github.com/alganet/63f1dbc97b8fd35f7bb14ec30f79...
    ในเทอร์มินัลที่เข้ากันได้กับ VT100 (mintty, xterm, Terminal.app, vscode ฯลฯ) สามารถจับและตีความคีย์ผสมส่วนใหญ่ รวมถึงเจสเจอร์เมาส์ (กด/ลาก/ปล่อย) ได้จากภายในเชลล์
    เดโมที่รันได้คือ bash -c "$(curl -L https://git.io/fjToH)" จากนั้นลองกดปุ่มหรือขยับเมาส์ดู ออกด้วย Ctrl+W รองรับ zsh และ ksh ด้วย แต่ไม่รองรับ dash เป็นต้นที่ไม่มี read -rn1
    นอกจากนี้ ถ้า pipe โปรแกรมแบบโต้ตอบไปที่ cat -v เช่น vi | cat -v ก็จะเห็น VT100 escape ที่โปรแกรมนั้นใช้ และได้เรียนรู้อะไรมากจากการดูว่า vi ทำอย่างไร

    • หากหลังรันคำสั่งแล้วเทอร์มินัลพังจนไม่มีเอาต์พุตหรือเห็นอักขระแปลก ๆ อาจเป็นเพราะคำสั่งนั้นไม่ได้ cleanup tty ให้ถูกต้อง ไม่ต้องฆ่าเทอร์มินัล แค่พิมพ์ stty sane ก็ย้อนกลับได้
    • ตอนอ่านช่วงต้น ๆ ของ UNIX Programming Environment ของ Kernighan ผมเคยคิดวิธีประหลาด ๆ แบบนี้เพื่อสำรวจพฤติกรรม I/O ของเทอร์มินัลด้วยจิตวิญญาณคล้ายกัน
      เปิดเทอร์มินัล 3 หน้าต่าง แล้วรัน 1) man ascii, 2) nc -lvp 9001 | xxd -c1, 3) stty raw -echo; nc -nv 127.0.0.1 9001 จากนั้นในเทอร์มินัลที่ 3 ให้ลองสร้างค่าในคอลัมน์ “Hex” ทั้งหมดของ manpage ตามลำดับ
      จะสังเกตได้ว่าค่าบางค่าอาจสร้างได้หลายวิธี และปุ่มอื่นบางปุ่มก็ส่ง payload หลายไบต์
    • บทความนี้ที่ว่าด้วยการทำ text editor บนเทอร์มินัลก็ช่วยให้เข้าใจ stty และ raw mode ได้ดี: https://viewsourcecode.org/snaptoken/kilo/, https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode...
      พารามิเตอร์และสัญลักษณ์ของ stty ที่ดูเหมือนเวทมนตร์นั้นมาจาก control sequences ที่นิยามไว้เมื่อนานมาแล้ว มาตรฐานที่คลุมเครือจากยุค 1970 อย่าง ECMA-48 ก็มีคำศัพท์เข้าใจยากจำนวนมากที่แทบไม่ใช้กันแล้วในปัจจุบัน
      ตัวอย่างเช่น Select Graphic Rendition(SGR) โดยคร่าว ๆ ก็เหมือนแท็ก HTML เวอร์ชันยุค 1970 หากจะตั้งค่าสี foreground จะใช้รูปแบบประมาณ ESC[38;2;R;G;BmTextESC[0m โดย ESC[ คือ CSI, m คือ SGR, 38 คือสี foreground, 2 คือปริภูมิสี RGB, และ 0 คือการรีเซ็ตสไตล์
      โครงสร้างที่พารามิเตอร์มาก่อนคำสั่งจึงรู้สึกเหมือนกลับด้านอยู่บ้าง เทอร์มินัลก็เหมือนเบราว์เซอร์ คือแต่ละตัวมีขอบเขตและวิธี implement ไม่เหมือนกัน และทุกวันนี้ก็ยังเป็นเช่นนั้น สีรองรับได้ค่อนข้างดี แต่ฟีเจอร์อื่นไม่ใช่แบบนั้น เช่น SGR<7> บอกว่าเป็นโหมด “negative image” แต่ในมาตรฐานไม่ได้อธิบายละเอียด บางเทอร์มินัลจึงแสดงแบบกลับสี ขณะที่บางตัวจัดการอีกแบบ ทำให้ทุกวันนี้ยังเกิดปัญหาอยู่
    • ก่อนดูดโค้ดจากอินเทอร์เน็ตเข้า bash โดยตรง ควรตรวจสอบก่อนเสมอ เดโมนี้ดูเหมือนจะทำให้การตั้งค่าที่ไม่มาตรฐานของผมเพี้ยนไป และผมก็ไม่ได้ตั้งใจจะบันทึกรายละเอียดไว้
    • บน Sonoma จะขึ้นว่า “The invoked shell does not support interactive features”
  • เมื่อกว่า 20 ปีก่อน เคยทำ state machine บน readline เพื่อให้ใช้เป็น editor หลายบรรทัดจริง ๆ ที่เลื่อนขึ้นลงได้ ไม่ใช่แค่บรรทัดเดียวที่ wrap
    วิดีโอ: https://github.com/colmmacc/jot/raw/master/jot-demo.mp4
    CVS repository ของ Unix terminal IM client ที่มีฟีเจอร์นี้: https://c-hey.redbrick.dcu.ie/src/c-hey_cvs_latest/
    เวลาเลื่อนขึ้นลงระหว่างบรรทัดจะติดตามและวาดเคอร์เซอร์ใหม่ และเมื่อขนาดเทอร์มินัลเปลี่ยนก็จะดู SIGWINCH แล้ววาดใหม่ อยากเขียนใหม่ด้วย Rust แล้วปล่อยเป็นไลบรารีเล็ก ๆ แต่ไม่มีเวลา แปลกใจมาตลอดที่ไม่มีใครทำอะไรคล้ายกัน

    • เท่มากจนอยากเอาไอเดียไปใช้ บางกรณีน่าจะทำใน zsh ได้ด้วยโค้ดไม่กี่บรรทัด
      หลัง autoload -Uz zed แล้วใช้ zed เพื่อแก้ไขไฟล์ชั่วคราวและส่งต่อให้ git commit -F ก็จะเริ่ม inline editor ที่มีทุกฟีเจอร์ของ ZLE ได้ บันทึกแล้ว commit ด้วย C-x C-w และยกเลิกด้วย C-c
      ข้อดีของ implementation พื้นฐานนี้คือ zed เป็น ZLE widget ใหม่ จึงควบคุม keymap ได้เต็มที่ โหมด emacs หรือโหมด vi ใช้งานได้ทันที และยังปรับแต่งเพิ่มได้อีก
      ถ้าเขียน ZLE widget เอง ก็สามารถเรียกจากใน line editor ได้โดยตรง ไม่ต้อง wrap คำสั่งเหมือนข้างต้น
      https://github.com/zsh-users/zsh/blob/master/Functions/Misc/...
    • ก่อนดูวิดีโอไม่รู้เลยว่าประสบการณ์แก้ไขแบบนี้ดีกว่าแค่ไหน และเห็นด้วยกับข้อดีที่ไม่ต้องสลับบริบทเหมือนโหมดโต้ตอบของ git commit เอฟเฟกต์แบบ “เครื่องพิมพ์ดีดอินไลน์” ก็ดูเท่
    • หน้าต่างข้อความของ rio ตอนอยู่ใน hold mode ก็ดูคล้ายกัน แต่การออกแบบเรียบง่ายกว่ามากและรองรับเมาส์ดีกว่า: https://p9f.org/magic/man2html/1/rio
    • สงสัยว่าโปรแกรมเวอร์ชันปัจจุบันเสถียรหรือไม่
  • สิ่งที่บทความยังไม่ได้พูดถึง ได้แก่ อักขระแบบกว้าง, ปัญหาที่การกดปุ่มเดียวกันถูกแทนด้วย ANSI escape sequence ที่ต่างกันเพราะโหมดคีย์บอร์ดต่างกัน, ความแตกต่างของสถานะ TTY เช่น local echo, และรายละเอียดปลีกย่อยที่ system call สำหรับเปลี่ยนสถานะ TTY แตกต่างกันไปตามระบบปฏิบัติการ
    การรองรับ terminal emulation ก็ไม่เหมือนกัน ปัจจุบันโดยทั่วไปจะเลียนแบบ xterm แต่แม้ในกลุ่มนั้นก็ไม่มีใครตั้งเป้าให้เข้ากันได้ 100%
    วิธีตรวจสอบความสามารถที่เทอร์มินัลมีให้ก็ยังขาดฉันทามติ บางฝั่งส่ง ANSI escape sequence แล้วรอคำตอบ บางฝั่งดู $TERM และบางเทอร์มินัลประกาศตัวเองเป็น xterm แต่กลับไม่สนใจ sequence ของฟีเจอร์ VT และตั้ง environment variable อื่นแทน พูดตรง ๆ คือเละยิ่งกว่า user agent string เสียอีก
    ทั้งหมดนี้ยังอยู่บนสมมติฐานว่าเป็นระบบ POSIX ถ้าเอา Windows เข้ามาปนด้วยก็จะน่าสนใจขึ้นอีกเท่าตัว

  • ถ้าตั้ง $EDITOR ใน bash เป็นเอดิเตอร์ที่ชอบไว้ สามารถกด Ctrl-X Ctrl-E เพื่อส่งบรรทัดปัจจุบันไปยัง $EDITOR ได้ หลังจากแก้ไขคำสั่งแล้วบันทึกและออก คำสั่งก็จะถูกเรียกทำงาน

    • ถ้าเป็นเซียน Unix รุ่นเก่าที่ใช้โหมด vi ใน ksh ก็กด v ได้เลย
    • คำสั่งในตัว fc ก็ทำสิ่งเดียวกันได้ แค่พิมพ์ fc โดยไม่ต้องใช้ชุดปุ่ม Ctrl
      ยังสามารถเปิดคำสั่งก่อนหน้าด้วยเอดิเตอร์เฉพาะ แทนที่จะใช้ $EDITOR ค่าเริ่มต้นได้ด้วย เช่น ถ้า EDITOR=vi แต่อยากแก้คำสั่งล่าสุดด้วย ed ก็ใช้ fc -e ed
      ในโหมด vi ยังมีวิธีสร้างเชลล์สคริปต์อย่างรวดเร็วจากประวัติคำสั่งได้ด้วย ถ้าคำสั่งที่ต้องการอยู่ลำดับที่ 15 ใน history ก็ทำได้ประมาณ fc 15 แล้วตามด้วย w1.sh, %d, wq
    • ใน fish คือ Alt-e
    • ชอบคีย์ลัดแบบนี้ แต่พอถึงเวลาต้องใช้จริงกลับนึกไม่ออก
    • ใช้ EDITOR=nvim กับ copilot.vim ใน Neovim ให้เหมือน AI shell autocomplete อยู่ ใช้ได้ค่อนข้างดี
      เห็นจากที่นี่: https://twitter.com/arjie/status/1575201117595926530?s=46&t=...
  • ในมุมของมือใหม่ Linux สงสัยว่าทำไมแค่โปรแกรมอ่านข้อความด้วยอะไรอย่าง fgets() ก็ได้ฟีเจอร์อย่าง backspace, Ctrl+W, Ctrl+U มาฟรี ๆ
    เอกสารของ fgets() บอกว่าอ่านจนเจอ newline หรือ EOF เลยสงสัยว่าหมายความว่าโดยค่าเริ่มต้นมันจะบล็อกจนผู้ใช้ป้อน newline และก่อนหน้านั้นเทอร์มินัลจะช่วยแก้ไข line buffer ด้วย backspace หรือ Ctrl+W/Ctrl+U ให้ใช่ไหม
    แต่ก็ไม่ได้รับประกันว่าโปรแกรมจะอ่านทั้งบรรทัดเสมอไป และอาจส่ง count เป็น 2 เพื่ออ่านทีละอักขระได้ ถ้าอย่างนั้นเมื่อเกิด backspace ก็ไม่น่าจะ “ย้อนคืน” อักขระที่อ่านไปแล้วได้ เลยสงสัยว่ามีเวทมนตร์อะไรในบัฟเฟอร์ภายในของ fgets() หรือในกรณีแบบนั้นฟีเจอร์แก้ไขบรรทัดจะพัง
    https://en.cppreference.com/w/c/io/fgets

    • zsh, bash, ksh, fish และโปรแกรม terminal UI ส่วนใหญ่ปล่อยเทอร์มินัลไว้ใน cooked mode แล้วจัดการพฤติกรรมของเทอร์มินัลเอง ใน cooked mode เทอร์มินัลจะไม่ทำ buffering ตาม newline แต่จะอ่านอักขระทุกตัว
      ที่ดูเหมือน buffer ตาม newline นั้นจริง ๆ เป็นเพราะเชลล์จัดการเอง ดังนั้นนักพัฒนาจึงใส่ฟีเจอร์ การจัดการ text buffer ได้ เช่น tab completion หรือการเติม sudo ไว้หน้าบรรทัด
    • โดยค่าเริ่มต้น เทอร์มินัลทำงานใน canonical mode จนกว่าผู้ใช้จะป้อนครบหนึ่งบรรทัด จะยังไม่มีการรับส่งข้อมูลเกิดขึ้น
      อักขระบนหน้าจอก่อนกด Enter อยู่ในหน่วยความจำของเทอร์มินัลเท่านั้น และ system call read ของแอปพลิเคชันจะบล็อกอยู่ที่ file descriptor ของเทอร์มินัล เทอร์มินัลจะไม่เขียนข้อมูลจนกว่าบรรทัดจะเสร็จสมบูรณ์
      ตอนนั้นการแก้ไขบรรทัดที่กำลังพิมพ์ก็มักเป็นสิ่งที่เทอร์มินัลเองมีให้ การที่ตัวอักษรปรากฏบนหน้าจอก็เป็นฟีเจอร์ echo ของเทอร์มินัล และ backspace, delete, Ctrl+C สำหรับ SIGINT, Ctrl+D สำหรับ EOF ก็เช่นกัน
      line editor และ text editor จะปิดฟีเจอร์เหล่านี้ แล้วตั้งเป็น raw mode ทุกอย่างที่พิมพ์จะถูกส่งไปยังแอปพลิเคชันทันทีเพื่อให้จัดการเอง ตัวอย่างที่พบบ่อยคือ password prompt ที่ปิดเฉพาะ echo เพื่อซ่อนอักขระ
      line ending แบบ Unix ดั้งเดิม \n จริง ๆ แล้วก็เป็นฟีเจอร์ของเทอร์มินัลเช่นกัน จากมุมของเทอร์มินัล \n แค่ย้ายเคอร์เซอร์ลงไปหนึ่งบรรทัด ดังนั้นถ้าจะกลับไปต้นบรรทัดต้องมี \r ด้วย line ending ที่แท้จริงคือ \r\n และเทอร์มินัลจะแปลง \n เป็น \r\n ให้แบบมองไม่เห็น สิ่งนี้ก็ปิดได้เช่นกัน
    • ฟีเจอร์นี้มาจาก terminal emulator ในโหมด “cooked”: https://en.wikipedia.org/wiki/Terminal_mode
      ใน raw mode จะไม่ทำงานแบบนี้
    • หนึ่งในงานแรก ๆ ที่ได้รับเงินในฐานะโปรแกรมเมอร์คือการพอร์ตโปรแกรม help ของ VMS ไปยัง SunOS อุปสรรคแรกคือทำอย่างไรให้ ตอบสนองต่อการกดปุ่มแต่ละครั้ง โดยไม่ต้องรอ Enter/Return
      หัวหน้าตั้งใจแทบไม่ช่วย และปล่อยให้หาคำตอบเองเกือบทั้งหมดในปี 1986 ที่ยังไม่มีเว็บและแทบไม่มีหนังสือ O’Reilly จนถึงทุกวันนี้ก็ยังขอบคุณการตัดสินใจนั้นตลอดมา
  • เชลล์ dash ก็รองรับโหมดแก้ไขได้หากคอมไพล์ด้วย libedit ฝั่ง Debian น่าจะไม่ได้ทำแบบนั้นเพราะปัญหาเรื่องพื้นที่ แต่ถ้าต้องการให้คนที่เริ่มจาก bash ภายหลังต้องเรียนรู้น้อยลง ก็ควรรองรับไว้
    มาตรฐาน POSIX ก็ระบุ set -o vi เป็นส่วนขยายแบบเลือกได้ ถ้าเชลล์ที่อ้างว่าเข้ากันได้กับ POSIX นำโหมดแก้ไขนี้ไปใช้ ก็ต้องรองรับ set -o vi
    [set -o] vi: อนุญาตให้แก้ไขบรรทัดคำสั่งของเชลล์โดยใช้เอดิเตอร์ vi ในตัว เมื่อเปิดโหมด vi แล้ว ต้องปิดโหมดแก้ไขบรรทัดคำสั่งอื่น ๆ ที่มีให้ในฐานะส่วนขยายของ implementation”
    https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V...

    • dash ของ Debian ตั้งใจให้เป็น POSIX sh ขนาดเบาสำหรับรันเชลล์สคริปต์ ไม่ได้มุ่งใช้เป็น login shell หรือใช้งานแบบ interactive ดังนั้นในบริบทนั้น การคง dependency ให้เบาไว้จึงสมเหตุสมผล
  • ผม/ฉันคิดมาตลอดว่าเทอร์มินัลเป็นปัจจัยเดียวที่ทำให้ Linux ถูกตรึงไว้กับ ส่วนแบ่งตลาดต่ำกว่า 5% ไปตลอดกาล
    ไม่ใช่แค่การป้อนข้อความที่เป็นปัญหา แต่ประสบการณ์ทั้งหมดมันซับซ้อน ประชากร 95% แค่อยากบินโดรน แต่กลับถูกยื่นห้องนักบิน Concorde ที่ไม่มีป้ายกำกับใด ๆ มาให้
    https://qph.cf2.quoracdn.net/main-qimg-2566f4c91b894e4169d77..., https://media.thedroningcompany.com/images/tincy/WQZpC56vqMp...

    • ถ้ามีแค่ดิสโทรสไตล์ LFS, Gentoo, Arch ก็คงพูดถูก แต่ตราบใดที่ยังมี Ubuntu, Mint, OpenSUSE, Fedora, Manjaro และดิสโทรที่เป็นมิตรกับผู้ใช้อีกมากมาย ข้ออ้างนี้ก็ไม่ค่อยน่าเชื่อ
      การที่มีห้องนักบิน Concorde อยู่เบื้องหลัง Ubuntu และผม/ฉันสามารถเข้าถึงมันได้บนโน้ตบุ๊ก Ubuntu ที่ใช้ทำงาน กลับเป็นเรื่องที่น่าขอบคุณด้วยซ้ำ ถ้าไม่มีระดับการควบคุมแบบนั้นคงไม่ชอบ
      ในทางกลับกัน โน้ตบุ๊กของแม่ก็เป็น Ubuntu และโน้ตบุ๊กของแฟนเป็น Manjaro ทั้งคู่ใช้งานได้ดีโดยแค่ “บินโดรน” ไม่เคยย่างเท้าเข้าไปในห้องนักบิน Concorde เลย แน่นอนว่าผม/ฉันใช้ Arch
    • เคอร์เนล Linux และ user space บางส่วนที่ไม่มีเทอร์มินัล คนส่วนใหญ่ใช้อยู่แล้วในรูปของ Android และ Chromebook
      เทอร์มินัลเป็นเครื่องมือของนักพัฒนาและผู้ดูแลระบบ ที่ดูเหมือนเป็นอย่างนั้นก็เพราะผู้ใช้ GNU/Linux บนเดสก์ท็อปส่วนใหญ่เป็นนักพัฒนาและผู้ดูแลระบบ ซึ่งเป็นแค่ไม่กี่เปอร์เซ็นต์ของประชากร
    • ใน Gnome Terminal ของ Ubuntu หรือ Windows Terminal ของ Windows กด Home เพื่อไปต้นบรรทัด กด End เพื่อไปท้ายบรรทัดได้ และสิ่งที่ป้อนง่าย ๆ ก็ดูจะง่ายพอ ๆ กันหมด
      ที่ต้องจำชอร์ตคัตแปลก ๆ เยอะกลับเป็นเทอร์มินัลของ macOS ซึ่งทุกอย่างถูกทำให้ไม่เป็นธรรมชาติที่สุดเท่าที่จะทำได้
    • สิ่งที่ทำให้ Linux มีส่วนแบ่งตลาดเล็กน้อยคือ การขาดการติดตั้งมาล่วงหน้า แม้แต่ Windows เองก็แทบไม่มีใครใช้ จนกระทั่ง Microsoft แทบจะบังคับให้ OEM บันเดิลและติดตั้งมาล่วงหน้าในยุค 3.x
    • ในทางกลับกัน ประสบการณ์เทอร์มินัลของ GNU/Linux ที่ดีกว่า Unix อาจเป็นหนึ่งในเหตุผลที่มันแซง Unix ได้ พอล็อกอินเข้า BSD box แล้วรู้สึกเหมือนเดินทางย้อนเวลากลับไปยุค 1980
  • อาจเป็นความเห็นที่ไม่เป็นที่นิยม แต่ใน iTerm2 ผม/ฉันเปลี่ยนไปใช้พรีเซ็ต Natural text editing เพื่อให้ตรงกับชอร์ตคัตแก้ไขแบบดั้งเดิมของ macOS
    เพราะภายในแอปเทอร์มินัล โดยปกติมันจะรีแมปกลับเป็น control sequence ให้ จึงมีข้อดีคือได้ฟังก์ชันนี้แทบทุกที่ โดยไม่ต้องไปเปลี่ยนหลายจุดหรือกังวลเรื่องการรองรับ readline
    มันไม่สมบูรณ์แบบ บางครั้งมี sequence ที่ต้องป้อนเองถูกรีแมปไปด้วย แต่เพราะตรงกับ muscle memory เลยทำให้ใช้การแก้ไขคำสั่งบ่อยขึ้นมาก
    https://pliszko.com/blog/post/2021-10-31-natural-text-editin...

    • หนึ่งในสิ่งแรก ๆ ที่ตั้งค่าหลังติดตั้ง iTerm คือเรื่องนี้ การใช้ Cmd+ลูกศร, Option+ลูกศร, Cmd+Delete, Option+Delete ได้อย่างเป็นธรรมชาตินั้นสำคัญมาก
      ไม่อยากให้การแก้ไขข้อความในเทอร์มินัลทำงานแบบพิเศษแตกต่างออกไป
  • readline key binding พื้นฐานสามอย่างที่ถ้าผู้คนรู้แล้วชีวิตจะดีขึ้นคือ Ctrl+W, Ctrl+O, Ctrl+R
    Ctrl+W ลบคำสุดท้าย ตามที่ Julia กล่าวถึงในบทความ
    Ctrl+O รันบรรทัดที่ดึงมาจากประวัติ แล้วดึงบรรทัดถัดไปในประวัติขึ้นมาอีกครั้ง ถ้าเลื่อนไปยังคำสั่งแรกในประวัติแล้วกด Ctrl+O ห้าครั้ง ก็สามารถรันคำสั่งห้าคำสั่งตามลำดับได้
    Ctrl+R ค้นหาย้อนกลับในประวัติขณะพิมพ์สตริงค้นหา กด Ctrl+R อีกครั้งเพื่อไปผลลัพธ์ก่อนหน้า และ Ctrl+S เพื่อเลื่อนไปข้างหน้าตามเวลา รันด้วย Enter หรือ Ctrl+O

    • สงสัยมาตลอดว่าทำไมถึงเลือก Ctrl+S เพราะจากประสบการณ์มันชนกับค่าเริ่มต้น XOFF pause แทบทุกที่
      ถ้าจะใช้คีย์ผสมนั้นกับฟังก์ชันค้นหาไปข้างหน้า ก็ต้องรู้ว่าทำไมมันไม่ทำงาน และต้องเต็มใจปิด XOFF ด้วย
    • ฟังก์ชันสุดท้ายนี้ผม/ฉันใช้ Ctrl+Shift+R มาตลอด เหมือน Shift+Tab ใน GUI ดีใจที่ได้รู้จัก Ctrl+S ด้วย แต่เพราะ muscle memory คงไม่น่าจะใช้ได้
  • ใน Windows Terminal นั้น Ctrl+C ทำงานได้ถูกต้องเสมอ ถ้ากำลังเลือกข้อความอยู่ก็จะคัดลอก และถ้าไม่ได้เลือกอยู่ก็จะฆ่าโปรเซสที่กำลังรันอยู่
    แอปเทอร์มินัลบน Linux ไม่ใช้ Ctrl+C เพื่อคัดลอก แต่บังคับให้ใช้ Ctrl+Shift+C ส่วนการวาง ถ้ากด Ctrl+V ก็จะมีอักขระแปลก ๆ ถูกใส่เข้ามา และแอปอื่น ๆ ก็ใช้ Ctrl+V กันหมด เลยโดน muscle memory เล่นงานอยู่เรื่อย ๆ
    เลยสงสัยว่ามีแอปเทอร์มินัลบน Linux ที่ทำงานเหมือน Windows Terminal และไม่ตีหลังมือเราว่า “ใช้เทเลไทป์ไม่เหมาะสม” บ้างไหม

    • ผมตั้งค่าเทอร์มินัลไว้ให้คัดลอกทันทีเสมอเมื่อเลือกข้อความ ไม่กด Ctrl+C อยู่แล้ว ถ้าไม่ได้จะคัดลอก แล้วจะเลือกข้อความไปทำไมตั้งแต่แรก
    • จุดที่การคัดลอกกับการหยุดทำงานในเทอร์มินัลถูกแยกเป็น Command-C กับ Ctrl-C เป็นหนึ่งในเหตุผลใหญ่ที่สุดที่ทำให้ยังอยู่กับ Mac
    • การคัดลอกทั้งในและนอกเทอร์มินัลทำด้วย ปุ่มกลางของเมาส์ แค่เลือกข้อความ แล้วคลิกปุ่มกลางตรงตำแหน่งที่จะวาง
      วิธีนี้ใช้บัฟเฟอร์ Xorg แยกต่างหาก ดังนั้นหลังจากคัดลอกอะไรสักอย่างด้วย Ctrl+C แล้วไปเลือกข้อความอื่น Ctrl+V จะวางอันแรก ส่วนการคลิกกลางจะวางอันที่สอง
      บน Windows หรือสมาร์ตโฟน บางทีก็น่าหงุดหงิดเวลาที่แค่เลือกอะไรไว้แล้วลืมกดอย่าง Ctrl+C
      คิดว่าในบรรดาเทอร์มินัลบน Linux คงไม่มีตัวไหนที่ทำเหมือน Windows Terminal เพราะโปรแกรมที่กำลังรันอยู่ในเทอร์มินัลอาจอยากจัดการ Ctrl+C เองก็ได้ ตัวอย่างเช่นโปรแกรมแก้ไขข้อความ และตัวเทอร์มินัลเองก็ไม่รู้ด้วยซ้ำว่าตอนนี้มีโปรแกรมอะไรที่กำลังรันอยู่ เพราะการควบคุมงานเป็นหน้าที่ของเชลล์
    • เทอร์มินัล Linux ดี ๆ ส่วนใหญ่ทำได้ผ่านการตั้งค่า เช่น ใน kitty ใช้ map ctrl+c copy_or_interrupt ก็พอ
      https://sw.kovidgoyal.net/kitty/actions/#action-copy_or_inte...
    • ใน WezTerm ที่ตั้งค่าด้วย Lua ได้ก็น่าจะทำได้เหมือนกัน ตอนนี้ผมใช้คีย์ไบน์ดอื่นให้เรียกฟังก์ชัน Lua เฉพาะอยู่แล้ว ไว้ไปอยู่หน้าโน้ตบุ๊กแล้วจะลองทำให้มันทำงานดู