ทำไมปุ่ม Enter ไม่ส่ง EOL


19

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

บนวินโดวส์ , ส่งEnter CR LF == 13 10* ระวังไม่ใช่ Windows; ทำไมEnterให้ 13 มากกว่า 10


ลองอ่านสองไบต์
Michael Hampton

@MichaelHampton Nope ไม่มีสิ่งใดรอคำอธิบายไฟล์นั้นหลังจากอ่านหนึ่งไบต์
cat

คำตอบ:


11

ในขณะที่คำตอบของโทมัสผ้ากันเปื้อนค่อนข้างถูกต้องStéphane Chazelas ถูกกล่าวถึงอย่างถูกต้องในความคิดเห็นของคำตอบของผ้ากันเปื้อนว่าการแปลงไม่ได้อยู่ในหิน; มันเป็นส่วนหนึ่งของวินัยในสายงาน

ในความเป็นจริงการแปลเป็นโปรแกรมอย่างสมบูรณ์

หน้าคน 3 termiosหน้าคนมีพื้นข้อมูลที่เกี่ยวข้องทั้งหมด (ลิงก์ใช้กับโครงการ man-page ของ Linuxซึ่งพูดถึงคุณลักษณะที่เป็น Linux เท่านั้นและเป็นเรื่องธรรมดาสำหรับ POSIX หรือระบบอื่น ๆ ให้ตรวจสอบส่วนที่สอดคล้องกับแต่ละหน้าเสมอ)

iflagแอตทริบิวต์ขั้ว ( old_settings[0]ในรหัสที่แสดงในคำถามในPython ) มีสามธงที่เกี่ยวข้องในทุกระบบ POSIXy:

  • INLCR: หากตั้งไว้ให้แปล NL เป็น CR บนอินพุท
  • ICRNL: หากตั้งค่า (และIGNCRไม่ได้ตั้งค่า) ให้แปล CR เป็น NL บนอินพุต
  • IGNCR: ละเว้น CR บนอินพุต

ในทำนองเดียวกันมีการตั้งค่าผลลัพธ์ที่เกี่ยวข้อง ( old_settings[1]) เช่นกัน:

  • OPOST: เปิดใช้งานการประมวลผลเอาต์พุต
  • OCRNL: แม็พ CR กับ NL บนเอาต์พุต
  • ONLCR: แมป NL กับ CR บนเอาต์พุต (XSI; ไม่สามารถใช้ได้ในระบบ POSIX หรือ Single-Unix-Specification ทั้งหมด)
  • ONOCR: ข้าม (ห้ามเอาต์พุต) CR ในคอลัมน์แรก
  • ONLRET: ข้าม (ห้ามเอาต์พุต) CR

ตัวอย่างเช่นคุณสามารถหลีกเลี่ยงการพึ่งพาttyโมดูล การดำเนินการ "makeraw" เพียงแค่ล้างชุดของธง (และชุด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

แม้ว่าเพื่อความเข้ากันได้คุณอาจต้องการตรวจสอบว่าค่าคงที่ทั้งหมดนั้นมีอยู่ในโมดูล termios ก่อนหรือไม่ (ถ้าคุณใช้ระบบที่ไม่ใช่ POSIX) นอกจากนี้คุณยังสามารถใช้new_settings[6][termios.VMIN]และnew_settings[6][termios.VTIME]ตั้งค่าว่าการอ่านจะบล็อกหรือไม่หากไม่มีข้อมูลที่รอดำเนินการและระยะเวลา (เป็นจำนวนเต็มของ deciseconds) (โดยปกติแล้วVMINจะตั้งค่าเป็น 0 และVTIMEเป็น 0 หากผู้อ่านควรกลับมาทันทีหรือเป็นจำนวนบวก (สิบวินาที) ระยะเวลาที่การอ่านควรรอนานที่สุด)

ดังที่คุณเห็นข้างต้น (และ "makeraw" โดยทั่วไป) ปิดใช้งานการแปลทั้งหมดในอินพุตซึ่งจะอธิบายพฤติกรรมที่แมวเห็น:

    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เส้นปิดการใช้งานการประมวลผลการส่งออกทั้งหมดโดยไม่คำนึงถึงสิ่งที่ธงส่งออกอื่น ๆ บอกว่า คุณสามารถละเว้นมันเพื่อให้การประมวลผลเอาต์พุตยังคงอยู่ สิ่งนี้จะทำให้เอาต์พุต "ปกติ" แม้อยู่ในโหมด raw (ไม่ส่งผลกระทบต่อว่าอินพุตถูก echoed โดยอัตโนมัติหรือไม่นั้นถูกควบคุมโดยECHOcflag ใน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

บน pseudoterminals และอุปกรณ์ USB TTY อัตรารับส่งข้อมูลไม่เกี่ยวข้อง

หากคุณเขียนสคริปต์ Bash ที่ต้องการอ่านเช่นรหัสผ่านให้พิจารณาสำนวนต่อไปนี้:

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

EXITกับดักจะถูกดำเนินการเมื่อใดก็ตามที่ออกจากเปลือก stty -gอ่านการตั้งค่าปัจจุบันของอาคารในช่วงเริ่มต้นของสคริปต์เพื่อให้การตั้งค่าปัจจุบันมีการบูรณะเมื่อออกจากสคริปต์โดยอัตโนมัติ คุณสามารถขัดจังหวะสคริปต์ด้วยCtrl+ Cและมันจะทำสิ่งที่ถูกต้อง (ในบางกรณีที่มีสัญญาณฉันพบว่าบางครั้งเทอร์มินัลติดอยู่กับการตั้งค่า raw / noncanonical (ต้องการให้พิมพ์reset+ Enterสุ่มสี่สุ่มห้าที่เทอร์มินัล) แต่การเรียกใช้stty saneก่อนที่จะกู้คืนการตั้งค่าดั้งเดิมที่แท้จริงได้หายไปทุกครั้ง ฉันนั่นคือเหตุผลว่าทำไมมันถึงอยู่ที่นั่นความปลอดภัยที่เพิ่มเข้ามา)

คุณสามารถอ่านบรรทัดอินพุต (ไม่ได้ถูกสะท้อนไปยังเทอร์มินัล) โดยใช้readbash ในตัวหรือแม้แต่อ่านอักขระทีละอักขระโดยใช้

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

หากคุณไม่ได้ตั้งค่าIFSเป็น ASCII NUL ในreadตัวเครื่องจะใช้ตัวคั่นดังนั้นจึงcจะว่างเปล่า กับดักสำหรับผู้เล่นรุ่นเยาว์


1
โอ้เพื่อเห็นแก่พระเจ้าไม่มีอะไรเป็นที่เคยง่าย :(
แมว

ฉันยอมรับคำตอบนี้เพราะมีประโยชน์มากที่สุดสำหรับฉันในฐานะที่เป็นงูหลามงูหลามแม้ว่าจะมีอีกคนหนึ่งที่ยอดเยี่ยม
แมว

2
@cat: ในขณะนี้อาจจะเป็นประโยชน์กับคุณมากที่สุดผมยังคงพูดได้คำตอบที่โทมัสเป็นผ้ากันเปื้อนที่ถูกต้องมากขึ้น ฉันอยากให้คุณยอมรับมันแทน
สัตว์ตัวน้อย

4
ในขณะที่ความตั้งใจของคุณที่จะละทิ้ง +15 เครดิตของคุณคุณ @cat ค่อนข้างถูกต้อง ไม่ว่าคำตอบนั้นจะได้รับการยอมรับหรือไม่ก็ไม่ใช่สิ่งบ่งชี้ว่าเป็น "คำตอบที่ถูกที่สุด" ของคำตอบที่โพสต์ นั่นหมายความว่าเป็นสิ่งที่ OP ต้องการเพราะเหตุผลส่วนตัว "ถูกต้องที่สุด" มักจะเป็น upvoted ที่สูงที่สุด การยอมรับคำตอบนั้นขึ้นอยู่กับความชอบส่วนตัวของคุณหาก OP ชอบใจคุณไม่มีเหตุผลที่จะไม่ยอมรับมัน
terdon

1
@terdon: โอเคฉันยืนแก้ไขแล้ว
สัตว์ที่กำหนด

30

เป็นหลัก "เพราะมันถูกทำอย่างนั้นตั้งแต่เครื่องพิมพ์ดีดแบบใช้มือ" จริงๆ.

เครื่องพิมพ์ดีดแบบแมนนวลมีแคร่พิมพ์บนกระดาษและมันเคลื่อนที่ไปข้างหน้าตามที่คุณพิมพ์ (โหลดสปริง) และมีคันโยกหรือกุญแจซึ่งจะปล่อยแคร่ให้สปริงกลับไปที่ขอบซ้าย

เมื่อป้อนข้อมูลทางอิเล็กทรอนิกส์ (โทรพิมพ์ ฯลฯ ) ได้รับการแนะนำพวกเขาดำเนินการไปข้างหน้าว่า ดังนั้นที่สำคัญในอาคารจำนวนมากจะมีข้อความระบุว่าEnterReturn

มีการป้อนบรรทัดเกิดขึ้น (ในกระบวนการแบบแมนนวล) หลังจากส่งคืนค่าขนส่งไปที่ระยะขอบด้านซ้าย อีกครั้งอุปกรณ์อิเล็กทรอนิกส์เลียนแบบอุปกรณ์ด้วยตนเองทำให้การline-feedทำงานแยกจากกัน

การดำเนินการทั้งสองจะถูกเข้ารหัส (เพื่อให้โทรพิมพ์เป็นมากกว่าอุปกรณ์แบบสแตนด์อโลนที่สร้างประเภทกระดาษ) ดังนั้นเราจึงมีCR(carriage-return) และLF(line-feed) ภาพจากข้อมูล Teletype ของ ASR 33 นี้จะแสดงแป้นพิมพ์โดยReturnอยู่ทางด้านขวาและLine-Feedไปทางซ้าย อยู่ทางขวามันเป็นกุญแจสำคัญ:

ป้อนคำอธิบายรูปภาพที่นี่

ยูนิกซ์มาในภายหลัง ผู้พัฒนาชอบที่จะทำให้สิ่งต่าง ๆ สั้นลง (ดูที่ตัวย่อทั้งหมดแม้creatสำหรับ "สร้าง") ต้องเผชิญกับกระบวนการสองส่วนที่เป็นไปได้พวกเขาตัดสินใจว่าการป้อนบรรทัดนั้นเหมาะสมเมื่อถูกนำหน้าด้วยการคืนค่าขนส่งเท่านั้น ดังนั้นพวกเขาจึงส่งคืน carriage ที่ชัดเจนจากไฟล์และแปลReturnคีย์ของเทอร์มินัลเพื่อส่ง line-feed ที่สอดคล้องกัน เพียงเพื่อหลีกเลี่ยงความสับสนพวกเขาเรียกไลน์ฟีดว่า "ขึ้นบรรทัดใหม่"

เมื่อเขียนข้อความบนเทอร์มินัล Unix จะแปลในทิศทางอื่น: ตัวดึงข้อมูลบรรทัดกลายเป็น carriage-return / line-feed

(นั่นคือ "ปกติ": ที่เรียกว่า "โหมดสุก" ตรงกันข้ามกับโหมด "ดิบ" ที่ไม่มีการแปลใด ๆ )

สรุป:

  • carriage-return / line-feed เป็นลำดับ 13 10
  • อุปกรณ์ส่ง 13 (ตั้งแต่ "ตลอดไป" ในแง่ของคุณ)
  • ระบบที่เหมือน Unix จะเปลี่ยนเป็น 13 10
  • ระบบอื่นไม่จำเป็นต้องเก็บเพียง 10 (Windows ส่วนใหญ่ยอมรับเพียง 10 หรือ 13 10 ขึ้นอยู่กับความเข้ากันได้ที่สำคัญ)

1
ฉันมองหาภาพที่ดีเพื่อแสดงคันโยกสำหรับเครื่องพิมพ์ดีดด้วยตนเอง แต่พบเฉพาะภาพความละเอียดต่ำ
Thomas Dickey

3
หากคุณต้องพิมพ์หนึ่งในนั้นคุณจะย่อทุกอย่างด้วย!
Michael Hampton

3
เกี่ยวกับส่วนของประวัติศาสตร์: เครื่องพิมพ์ดีดแบบแมนนวลที่ฉันใช้ในการใช้งานของฉันคล้ายกับคันนี้เพียงคันเดียว เมื่อคุณดึงมันก่อนมันจะหมุนลูกกลิ้ง (ตัวป้อนบรรทัด) จากนั้นมันก็จะดึงแคร่ไปตามนั้น และมันคือแรงดึงที่โหลดสปริง ตัวอักษรแต่ละตัวที่พิมพ์หรือแท็บที่กดจะปล่อยสปริงบ้างย้ายแคร่ไปที่ตำแหน่ง "ไม่โหลด" ซึ่งอยู่ที่ท้ายบรรทัดไม่ใช่เริ่มต้น
RealSkeptic

2
บนอินพุต CR ถูกแปล (โดยวินัยของ tty line) เป็น LF ไม่ใช่ CR LF มันอยู่ในการส่งออก (รวมถึงเสียงสะท้อนของการป้อนข้อมูล) ที่จะแปลให้LF CR LFเมื่อคุณพิมพ์foo<Return>ในโหมดการปรุงสุกแอปพลิเคชั่นจะอ่านfoo\nและfoo\r\nส่งกลับมาโดยระเบียบการสายเพื่อ echo ไปยังเครื่อง
Stéphane Chazelas

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.