Tại sao phím Enter không gửi EOL?


19

Unix / Linux EOL là LF, linefeed, ASCII 10, chuỗi thoát \n.

Đây là một đoạn mã Python để có chính xác một lần nhấn phím:

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

Khi tôi nhấn Entertrên bàn phím của mình để đáp lại đoạn trích này, nó sẽ \rtrả lại, vận chuyển trở lại, ASCII 13.

Trên Windows , Entergửi CR LF == 13 10. * nix không phải là Windows; Tại sao không Entercho 13 chứ không phải 10?


Hãy thử đọc hai byte.
Michael Hampton

@MichaelHampton Không, không có gì chờ đợi trên bộ mô tả tệp đó sau khi đọc một byte
con mèo

Câu trả lời:


11

Trong khi câu trả lời của Thomas Dickey hoàn toàn chính xác, Stéphane Chazelas đã đề cập chính xác trong một bình luận cho câu trả lời của Dickey rằng việc chuyển đổi không được đặt trong đá; nó là một phần của kỷ luật dòng

Trong thực tế, bản dịch là hoàn toàn lập trình.

Trang man 3 termios chứa cơ bản tất cả các thông tin thích hợp. (Liên kết đưa đến dự án trang người dùng Linux , trong đó đề cập đến các tính năng chỉ dành cho Linux và phổ biến cho POSIX hoặc các hệ thống khác; luôn kiểm tra phần Tuân thủ theo từng trang ở đó.)

Các iflagthuộc tính đầu cuối ( old_settings[0]trong mã được hiển thị trong câu hỏi trong Python ) có ba cờ có liên quan trên tất cả các hệ thống POSIXy:

  • INLCR: Nếu được đặt, dịch NL sang CR trên đầu vào
  • ICRNL: Nếu được đặt (và IGNCRkhông được đặt), hãy dịch CR sang NL trên đầu vào
  • IGNCR: Bỏ qua CR trên đầu vào

Tương tự, cũng có các cài đặt đầu ra liên quan ( old_settings[1]):

  • OPOST: Cho phép xử lý đầu ra.
  • OCRNL: Ánh xạ CR tới NL trên đầu ra.
  • ONLCR: Ánh xạ NL tới CR trên đầu ra. (XSI; không khả dụng trong tất cả các hệ thống POSIX hoặc Single-Unix.)
  • ONOCR: Bỏ qua (không xuất) CR trong cột đầu tiên.
  • ONLRET: Bỏ qua (không xuất) CR.

Ví dụ, bạn có thể tránh dựa vào ttymô-đun. Hoạt động "Makeraw" chỉ xóa một bộ cờ (và đặt CS8oflag):

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

mặc dù vì mục đích tương thích, bạn có thể muốn kiểm tra xem tất cả các hằng số đó có tồn tại trong mô đun termios hay không (nếu bạn chạy trên các hệ thống không phải POSIX). Bạn cũng có thể sử dụng new_settings[6][termios.VMIN]new_settings[6][termios.VTIME]để đặt xem liệu một lần đọc sẽ chặn nếu không có dữ liệu đang chờ xử lý và thời gian (tính theo số nguyên của số giây). (Thông thường VMINđược đặt thành 0 và VTIMEthành 0 nếu các lần đọc sẽ quay lại ngay lập tức hoặc về một số dương (một phần mười giây) thời gian đọc nên chờ tối đa.)

Như bạn có thể thấy, nói chung (và "hãng sản xuất" nói chung) vô hiệu hóa tất cả các bản dịch trên đầu vào, điều này giải thích hành vi mà mèo đang nhìn thấy:

    new_settings[0] = new_settings[0] & ~termios.INLCR
    new_settings[0] = new_settings[0] & ~termios.ICRNL
    new_settings[0] = new_settings[0] & ~termios.IGNCR

Để có được hành vi bình thường, chỉ cần bỏ qua các dòng xóa ba dòng đó và bản dịch đầu vào không thay đổi ngay cả khi "thô".

Các new_settings[1] = new_settings[1] & ~termios.OPOSTdòng vô hiệu hóa tất cả các xử lý đầu ra, bất kể những gì những lá cờ đầu ra khác nói. Bạn chỉ có thể bỏ qua nó để giữ nguyên xử lý đầu ra. Điều này giữ cho đầu ra "bình thường" ngay cả trong chế độ thô. (Nó không ảnh hưởng đến việc liệu đầu vào có tự động lặp lại hay không; điều đó được điều khiển bởi ECHOcflag trong new_settings[3].)

Cuối cùng, khi các thuộc tính mới được đặt, cuộc gọi sẽ thành công nếu bất kỳ cài đặt mới nào được đặt. Nếu cài đặt nhạy cảm - ví dụ: nếu bạn đang yêu cầu nhập mật khẩu trên dòng lệnh -, bạn nên lấy cài đặt mới và xác minh các cờ quan trọng được đặt / bỏ đặt chính xác, để chắc chắn.

Nếu bạn muốn xem cài đặt thiết bị đầu cuối hiện tại của mình, hãy chạy

stty -a

Các cờ đầu vào thường nằm trên dòng thứ tư và các cờ đầu ra trên dòng thứ năm, với -tên cờ trước nếu cờ không được đặt. Ví dụ: đầu ra có thể là

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

Trên giả hành và các thiết bị USB TTY, tốc độ truyền không liên quan.

Nếu bạn viết các tập lệnh Bash muốn đọc ví dụ mật khẩu, hãy xem xét thành ngữ sau:

#!/bin/bash
trap 'stty sane ; stty '"$(stty -g)" EXIT
stty -echo -echonl -imaxbel -isig -icanon min 1 time 0

Cái EXITbẫy được thực hiện bất cứ khi nào vỏ thoát ra. Việc stty -gđọc các cài đặt hiện tại của thiết bị đầu cuối khi bắt đầu tập lệnh, vì vậy các cài đặt hiện tại sẽ được khôi phục khi tập lệnh thoát, tự động. Bạn thậm chí có thể làm gián đoạn tập lệnh bằng Ctrl+ Cvà nó sẽ làm đúng. (Trong một số trường hợp góc có tín hiệu, tôi thấy rằng thiết bị đầu cuối đôi khi bị kẹt với cài đặt thô / phi núi (yêu cầu một thiết bị gõ reset+ Entermù ở thiết bị đầu cuối), nhưng chạy stty sanetrước khi khôi phục cài đặt gốc thực tế đã chữa khỏi mọi lúc tôi. Vậy đó là lý do tại sao nó ở đó; một loại an toàn bổ sung.)

Bạn có thể đọc các dòng đầu vào (chưa được gắn vào thiết bị đầu cuối) bằng cách sử dụng readbash tích hợp hoặc thậm chí đọc từng ký tự đầu vào bằng cách sử dụng

IFS=$'\0'
input=""
while read -N 1 c ; do
    [[ "$c" == "" || "$c" == $'\n' || "$c" == $'\r' ]] && break
    input="$input$c"
done

Nếu bạn không được đặt IFSthành ASCII NUL, tích readhợp sẽ tiêu thụ các dấu phân cách, do đó csẽ trống. Bẫy cho các cầu thủ trẻ.


1
Ôi, vì thần, không có gìbao giờ đơn giản :(
mèo

Tôi chấp nhận câu trả lời này bởi vì nó hữu ích nhất đối với tôi với tư cách là một nhà phát triển Python, mặc dù câu trả lời khác rất hay
mèo

2
@cat: Mặc dù điều này có thể hữu ích nhất với bạn, tôi vẫn nói câu trả lời của Thomas Dickey là đúng hơn . Thay vào đó, bạn muốn bạn chấp nhận điều đó.
Động vật danh nghĩa

4
Mặc dù bạn sẵn sàng từ bỏ đại diện +15 của mình nhưng bạn hoàn toàn tin tưởng, @cat hoàn toàn đúng. Việc một câu trả lời có được chấp nhận hay không không là dấu hiệu cho thấy đó là câu trả lời "đúng" nhất trong số các câu trả lời được đăng. Nó chỉ có nghĩa là OP được ưa thích vì bất kỳ lý do cá nhân nào. "Chính xác nhất" thường được đánh giá cao nhất. Chấp nhận câu trả lời tùy thuộc vào sở thích cá nhân, nếu OP thích bạn, không có lý do gì để không chấp nhận câu trả lời.
terdon

1
@terdon: Được rồi, tôi đứng sửa rồi.
Động vật danh nghĩa

30

Về cơ bản "bởi vì nó đã được thực hiện theo cách đó kể từ khi máy đánh chữ thủ công". Có thật không.

Một máy đánh chữ thủ công có một cỗ xe được nạp giấy và nó di chuyển về phía trước khi bạn gõ (tải một lò xo), và có một đòn bẩy hoặc chìa khóa sẽ giải phóng cỗ xe, để lò xo đưa cỗ xe về lề trái.

Khi nhập dữ liệu điện tử (teletype, v.v.) đã được giới thiệu, họ đã tiến hành điều đó. Vì vậy, Enterchìa khóa trên nhiều thiết bị đầu cuối sẽ được dán nhãn Return.

Nguồn cấp dữ liệu đã xảy ra (trong quy trình thủ công) sau khi trả lại vận chuyển về lề trái. Một lần nữa, các thiết bị điện tử bắt chước các thiết bị thủ công, thực hiện một line-feedhoạt động riêng biệt .

Cả hai hoạt động đều được mã hóa (để cho phép teletype nhiều hơn một thiết bị độc lập tạo ra một loại giấy), vì vậy chúng tôi có CR(vận chuyển trở lại) và LF(nạp liệu đường truyền). Hình ảnh này từ ASR 33 Teletype Information hiển thị bàn phím, Returnở bên phải và Line-Feedngay bên trái. Ở bên phải , nó là chìa khóa chính:

nhập mô tả hình ảnh ở đây

Unix đã xuất hiện sau đó. Các nhà phát triển của nó thích rút ngắn mọi thứ (nhìn vào tất cả các chữ viết tắt, ngay cả creatđối với "tạo"). Đối mặt với một quá trình có thể gồm hai phần, họ đã quyết định rằng các nguồn cấp dữ liệu chỉ có ý nghĩa nếu chúng được đi trước bởi lợi nhuận vận chuyển. Vì vậy, họ đã bỏ trả lại vận chuyển rõ ràng từ các tệp và dịch Returnkhóa của thiết bị đầu cuối để gửi nguồn cấp dữ liệu tương ứng. Để tránh nhầm lẫn, họ gọi nguồn cấp dữ liệu là "dòng mới".

Khi viết văn bản trên thiết bị đầu cuối, Unix dịch theo hướng khác: một nguồn cấp dữ liệu trở thành vận chuyển trở lại / nguồn cấp dữ liệu.

(Nghĩa là "bình thường": cái gọi là "chế độ nấu", trái ngược với chế độ "thô" khi không có bản dịch nào được thực hiện).

Tóm lược:

  • vận chuyển-trở lại / dòng-thức ăn là trình tự 13 10
  • các thiết bị sẽ gửi 13 (từ "mãi mãi" trong điều khoản của bạn)
  • Các hệ thống giống như Unix thay đổi thành 13 10
  • Các hệ thống khác không nhất thiết chỉ lưu trữ 10 (Windows chủ yếu chấp nhận chỉ 10 hoặc 13 10, tùy thuộc vào mức độ tương thích quan trọng).

1
Tôi tìm kiếm một hình ảnh đẹp để hiển thị các đòn bẩy cho một máy đánh chữ thủ công, nhưng chỉ tìm thấy hình ảnh có độ phân giải thấp.
Thomas Dickey

3
Nếu bạn phải gõ một trong số đó, bạn cũng sẽ viết tắt mọi thứ!
Michael Hampton

3
Về phần lịch sử: máy đánh chữ thủ công tôi đã sử dụng, tương tự như cái này chỉ có một cần gạt. Khi bạn kéo nó, đầu tiên nó quay trục lăn (đường cấp liệu) và sau đó nó sẽ chỉ kéo xe ngựa theo. Và chính cái kéo này đã tải mùa xuân. Mỗi chữ cái được gõ, hoặc tab được nhấn, sẽ giải phóng phần nào lò xo, di chuyển cỗ xe trở lại vị trí "không tải", ở cuối dòng, không phải là bắt đầu.
RealSkeptic

2
Về đầu vào, CR được dịch (theo nguyên tắc dòng tty) sang LF, không phải CR LF. Đó là đầu ra (bao gồm tiếng vang của đầu vào) LF được dịch sang CR LF. Khi bạn nhập foo<Return>ở chế độ nấu, ứng dụng sẽ đọc foo\nfoo\r\nđược gửi lại theo dòng kỷ luật cho tiếng vang đến thiết bị đầu cuối.
Stéphane Chazelas

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.