為什麼 Enter 鍵不發送 EOL?
Unix / Linux EOL 是 LF、換行、ASCII 10、轉義序列
\n
。這是一個 Python 片段,可以準確地獲得一個按鍵:
import sys, tty, termios fd = sys.stdin.fileno() old_settings = termios.tcgetattr(fd) try: tty.setraw(sys.stdin.fileno()) ch = sys.stdin.read(1) finally: termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) return ch
當我按下
Enter
我的鍵盤來響應這個片段時,它給出了\r
,輸入,ASCII 13。在Windows上,
Enter
發送CR LF == 13 10
. *nix 不是 Windows;為什麼Enter
給13而不是10?
雖然Thomas Dickey 的回答非常正確,但 Stéphane Chazelas 在對 Dickey 回答的評論中正確地提到了轉換不是一成不變的;它是線路紀律的一部分。
事實上,翻譯是完全可程式的。
man 3 termios手冊頁基本上包含所有相關資訊。(該連結指向Linux 手冊頁項目,其中確實提到了哪些功能僅適用於 Linux,哪些功能對 POSIX 或其他系統是通用的;請始終檢查那裡每個頁面上的符合部分。)
iflag
終端屬性(old_settings[0]
在Python問題中顯示的程式碼中)在所有 POSIXy 系統上都有三個相關標誌:
INLCR
: 如果設置,則在輸入時將 NL 轉換為 CRICRNL
:如果設置(並且IGNCR
未設置),則在輸入時將 CR 轉換為 NLIGNCR
: 在輸入時忽略 CR同樣,也有相關的輸出設置 (
old_settings[1]
):
OPOST
:啟用輸出處理。OCRNL
:在輸出上將 CR 映射到 NL。ONLCR
:在輸出時將 NL 映射到 CR。(XSI;並非在所有 POSIX 或單一 Unix 規範系統中都可用。)ONOCR
: 跳過(不輸出)第一列的 CR。ONLRET
:跳過(不輸出)CR。例如,您可以避免依賴
tty
模組。“makeraw”操作只是清除一組標誌(並設置CS8
oflag):import sys import termios fd = sys.stdin.fileno() old_settings = termios.tcgetattr(fd) ch = None try: new_settings = termios.tcgetattr(fd) new_settings[0] = new_settings[0] & ~termios.IGNBRK new_settings[0] = new_settings[0] & ~termios.BRKINT new_settings[0] = new_settings[0] & ~termios.PARMRK new_settings[0] = new_settings[0] & ~termios.ISTRIP new_settings[0] = new_settings[0] & ~termios.INLCR new_settings[0] = new_settings[0] & ~termios.IGNCR new_settings[0] = new_settings[0] & ~termios.ICRNL new_settings[0] = new_settings[0] & ~termios.IXON new_settings[1] = new_settings[1] & ~termios.OPOST new_settings[2] = new_settings[2] & ~termios.CSIZE new_settings[2] = new_settings[2] | termios.CS8 new_settings[2] = new_settings[2] & ~termios.PARENB new_settings[3] = new_settings[3] & ~termios.ECHO new_settings[3] = new_settings[3] & ~termios.ECHONL new_settings[3] = new_settings[3] & ~termios.ICANON new_settings[3] = new_settings[3] & ~termios.ISIG new_settings[3] = new_settings[3] & ~termios.IEXTEN termios.tcsetattr(fd, termios.TCSANOW, new_settings) finally: termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) return ch
儘管出於兼容性考慮,您可能希望首先檢查 termios 模組中是否存在所有這些常量(如果您在非 POSIX 系統上執行)。您還可以使用
new_settings[6][termios.VMIN]
andnew_settings[6][termios.VTIME]
設置如果沒有待處理的數據,讀取是否會阻塞,以及多長時間(以十進制整數為單位)。(通常VMIN
設置為 0,VTIME
如果讀取應立即返回,則設置為 0,或者設置為正數(十分之一秒)讀取最多應等待多長時間。)如您所見,上述(以及一般的“makeraw”)禁用了輸入的所有翻譯,這解釋了 cat 看到的行為:
new_settings[0] = new_settings[0] & ~termios.INLCR new_settings[0] = new_settings[0] & ~termios.ICRNL new_settings[0] = new_settings[0] & ~termios.IGNCR
要獲得正常行為,只需省略清除這三行的行,即使“原始”,輸入翻譯也不會改變。
該
new_settings[1] = new_settings[1] & ~termios.OPOST
行禁用所有輸出處理,不管其他輸出標誌說什麼。您可以省略它以保持輸出處理完整。即使在原始模式下,這也可以保持輸出“正常”。(它不影響輸入是否自動回顯;由 中的ECHO
cflag控制new_settings[3]
。)最後,當設置了新屬性時,如果設置了任何新設置,則呼叫將成功。如果設置很敏感——例如,如果你在命令行上要求輸入密碼——你應該得到新的設置,並驗證重要的標誌是否正確設置/取消設置,以確保。
如果要查看目前的終端設置,請執行
stty -a
輸入標誌通常在第四行,輸出標誌在第五行,
-
如果標誌未設置,則在標誌名稱前加一個。例如,輸出可能是speed 38400 baud; rows 58; columns 205; line = 0; intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = M-^?; eol2 = M-^?; swtch = M-^?; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0; -parenb -parodd cs8 hupcl -cstopb cread -clocal -crtscts -ignbrk brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc ixany imaxbel iutf8 opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0 isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke
在偽終端和 USB TTY 設備上,波特率無關緊要。
如果您編寫希望讀取密碼等 Bash 腳本,請考慮以下習慣用法:
#!/bin/bash trap 'stty sane ; stty '"$(stty -g)" EXIT stty -echo -echonl -imaxbel -isig -icanon min 1 time 0
每當 shell 退出時,
EXIT
就會執行陷阱。在stty -g
腳本開始時讀取終端的目前設置,因此腳本退出時會自動恢復目前設置。你甚至可以用Ctrl
+中斷腳本C
,它會做正確的事情。(在某些帶有信號的極端情況下,我發現終端有時會被原始/非規範設置卡住(需要一個人在終端上盲目地鍵入reset
+ ),但在恢復實際原始設置之前執行已經治癒了每次我。所以這就是它存在的原因;一種增加的安全性。)Enter``stty sane
read
您可以使用內置的 bash讀取輸入行(未回顯到終端) ,甚至可以使用IFS=$'\0' input="" while read -N 1 c ; do [[ "$c" == "" || "$c" == $'\n' || "$c" == $'\r' ]] && break input="$input$c" done
如果您不設置
IFS
為 ASCII NUL,read
內置將使用分隔符,因此c
它將為空。年輕球員的陷阱。
本質上是“因為自從手動打字機以來就是這樣做的”。真的。
手動打字機有一個供紙的托架,它在您打字時向前移動(載入彈簧),並且有一個槓桿或鑰匙可以釋放托架,讓彈簧將托架返回到左邊距。
隨著電子數據輸入(電傳打字機等)的引入,他們將其發揚光大。所以
Enter
很多終端上的鑰匙都會被貼上標籤Return
。將托架返回到左邊距後發生換行(在手動過程中)。再次,電子設備模仿手動設備,進行單獨
line-feed
操作。這兩個操作都是編碼的(允許電傳打字機不僅僅是創建紙張類型的獨立設備),所以我們有
CR
(輸入)和LF
(換行)。這張來自ASR 33 Teletype Information的圖像顯示了鍵盤,Return
右側和Line-Feed
左側。在右邊,這是主要的關鍵:Unix 是後來出現的。它的開發者喜歡縮短東西(看看所有的縮寫,甚至
creat
是“create”)。面對可能由兩部分組成的過程,他們認為換行只有在它們之前有輸入時才有意義。因此,他們從files中刪除了顯式輸入,並翻譯了終端的Return
鍵以發送相應的換行符。為了避免混淆,他們將換行稱為“換行符”。在終端上寫文本時,Unix 會朝另一個方向轉換:換行變成輸入/換行。
(即“通常”:所謂的“熟模式”,與不進行翻譯的“原始”模式形成對比)。
概括:
- 輸入/換行是序列 13 10
- 設備發送 13(因為在您的術語中是“永遠”)
- 類 Unix 系統將其更改為 13 10
- 其他系統不一定只儲存 10(Windows 在很大程度上只接受 10 或 13 10,這取決於兼容性的重要性)。