vi เพิ่มบรรทัดใหม่ (LF) ในตอนท้ายของไฟล์อย่างเงียบ ๆ หรือไม่?


36

ฉันมีปัญหาในการทำความเข้าใจกับพฤติกรรมแปลก ๆ : vi ดูเหมือนว่าจะเพิ่มขึ้นบรรทัดใหม่ (ASCII: LF เนื่องจากเป็นระบบ Unix ( AIX )) ที่ท้ายไฟล์เมื่อฉันไม่ได้พิมพ์เฉพาะเจาะจง

ฉันแก้ไขไฟล์ดังกล่าวใน vi (ระวังที่จะไม่ป้อนบรรทัดใหม่ในตอนท้าย):

# vi foo   ## Which I will finish on the char "9" and not input a last newline, then `:wq`
123456789
123456789
123456789
123456789
~
~
  ## When I save, the cursor is just above the last "9", and no newline was added.

ฉันคาดหวังว่า vi จะบันทึก "ตามสภาพ" ดังนั้นต้องมี 39 ไบต์: 10 อักขระ ASCII ในแต่ละสามบรรทัดแรก (หมายเลข 1 ถึง 9 ตามด้วยบรรทัดใหม่ (LF ในระบบของฉัน)) และเพียง 9 รายการสุดท้าย บรรทัด (อักขระ 1 ถึง 9 ไม่ต้องยกเลิกบรรทัดใหม่ / LF)

แต่มันปรากฏขึ้นเมื่อฉันบันทึกมันเป็น40ไบต์ (แทน 39) และod แสดงการสิ้นสุด LF :

# wc foo
       4       4      40 foo  ## I expected 39 here! as I didn't add the last newline
# od -a toto
0000000    1   2   3   4   5   6   7   8   9  lf   1   2   3   4   5   6
0000020    7   8   9  lf   1   2   3   4   5   6   7   8   9  lf   1   2
0000040    3   4   5   6   7   8   9  lf
0000050
     ## An "lf" terminates the file?? Did vi add it silently?

ถ้าฉันสร้างไฟล์โดย printf ทำสิ่งที่ฉันทำใน vi มันทำงานได้ตามที่คาดไว้:

# ## I create a file with NO newline at the end:
# printf "123456789\n123456789\n123456789\n123456789" > foo2
# wc foo2  ## This one is as expected: 39 bytes, exactly as I was trying to do above with vi.
       3       4      39 foo  ## As expected, as I didn't add the last newline

  ## Note that for wc, there are only three lines!
  ## (So wc -l doesn't count lines; it counts the [newline] chars... Which is rather odd.)

# root@SPU0WMY1:~  ## od -a foo2
0000000    1   2   3   4   5   6   7   8   9  lf   1   2   3   4   5   6
0000020    7   8   9  lf   1   2   3   4   5   6   7   8   9  lf   1   2
0000040    3   4   5   6   7   8   9
0000047                                ## As expected, no added LF.

ทั้งไฟล์ (foo (40 ตัวอักษร) และ foo2 (39 ตัวอักษร) จะเหมือนกันทุกประการถ้าฉันเปิดไฟล์ใหม่ด้วย vi ...

และถ้าฉันเปิด foo2 (39 ตัวอักษรไม่ต้องขึ้นบรรทัดใหม่) ใน vi และทำ:wqโดยไม่ต้องแก้ไขใด ๆมันบอกว่ามันเขียน 40 ตัวอักษรและ linefeed จะปรากฏขึ้น!

ฉันไม่สามารถเข้าถึง vi ที่ใหม่กว่าได้ (ฉันทำสิ่งนี้บน AIX, vi (ไม่ใช่Vim ) รุ่น 3.10 ฉันคิดว่า (ไม่มี "-version" หรือวิธีการอื่นที่รู้)

# strings /usr/bin/vi | grep -i 'version.*[0-9]'
@(#) Version 3.10

เป็นเรื่องปกติสำหรับ vi (และอาจไม่ใช่ในเวอร์ชันที่ใหม่กว่านี้หรือ Vim?) เพื่อเพิ่มบรรทัดใหม่ที่ส่วนท้ายของไฟล์อย่างเงียบ ๆ ? (ฉันคิดว่า ~ ระบุว่าบรรทัดก่อนหน้าไม่ได้ขึ้นบรรทัดใหม่)

-

แก้ไข:การอัปเดตเพิ่มเติมบางส่วนและการสรุปโดยขอขอบคุณคำตอบด้านล่าง:

  • vi เพิ่มบรรทัดใหม่ที่ต่อท้ายในขณะที่มันเขียนไฟล์ที่ขาดมัน (ยกเว้นว่าไฟล์นั้นว่างเปล่า)

  • มันจะทำในเวลาที่เขียนเท่านั้น! (กล่าวคือจนกว่าคุณจะ: คุณสามารถใช้: e เพื่อตรวจสอบว่าไฟล์ยังคงเป็นเมื่อคุณเปิดมัน ... (เช่น: มันยังคงแสดง "ชื่อไฟล์" [บรรทัดสุดท้ายยังไม่สมบูรณ์] N บรรทัด, อักขระ M) เมื่อคุณบันทึกจะมีการเพิ่มขึ้นบรรทัดใหม่โดยไม่แจ้งเตือนใด ๆ (มันบอกว่าบันทึกได้กี่ไบต์ แต่ในกรณีส่วนใหญ่ไม่เพียงพอที่จะรู้ว่ามีการเพิ่มบรรทัดใหม่) (ขอบคุณ @jiliagre ที่พูดกับฉันเกี่ยวกับ การเปิดข้อความ vi มันช่วยให้ฉันค้นหาวิธีที่จะรู้ว่าเมื่อการเปลี่ยนแปลงเกิดขึ้นจริง)

  • นี่คือการแก้ไขแบบPOSIX ! (ดู @ barefoot-io คำตอบสำหรับการอ้างอิง)


เพื่อความสมบูรณ์ซึ่งเป็นเวอร์ชันของ AIX (เวอร์ชันเต็ม)
EightBitTony

2
ฉันไม่ทราบว่า vi ของ AIX มีตัวเลือกนี้ - ปรากฏเป็นกลุ่มอย่างเดียว
Jeff Schaller

1
@JeffSchaller: ขอบคุณสำหรับลิงค์ น่าเสียดายเนทีฟ vi ไม่มี ": set noeol" หรือแม้แต่ตัวเลือก -b ที่จะเปิดในโหมดไบนารี ...
Olivier Dulac

1
คุณอาจได้รับviเวอร์ชันหรืออย่างน้อยก็มีเงื่อนงำเกี่ยวกับที่มาของมันโดยการรัน:veคำสั่ง
jlliagre

1
@ThomasDickey แน่นอน ด้วยเหตุผลบางอย่าง IBM ลอกexหน้าคู่มือซึ่ง:verปกติคำสั่งจะมีการจัดทำเป็นเอกสาร
jlliagre

คำตอบ:


28

นี่คือviพฤติกรรมที่คาดหวัง

ไฟล์ของคุณมีบรรทัดสุดท้ายที่ไม่สมบูรณ์ดังนั้นพูดอย่างเคร่งครัด (เช่นตามมาตรฐาน POSIX) ไม่ใช่ไฟล์ข้อความ แต่เป็นไฟล์ไบนารี

vi ซึ่งเป็นตัวแก้ไขไฟล์ข้อความไม่ใช่ไบนารีตัวหนึ่งจะแก้ไขอย่างสง่างามเมื่อคุณบันทึก

เครื่องมือนี้จะช่วยให้แฟ้มข้อความอื่น ๆ เช่นwc, sedและชอบที่จะให้การส่งออกที่คาดว่าจะ โปรดทราบviว่าไม่ได้เงียบเกี่ยวกับปัญหา:


$ printf "one\ntwo" >file     # Create a unterminated file
$ cat file                    # Note the missing newline before the prompt
one
two$ wc -l file               # wc ignores the incomplete last line
       1 file
$ sed '' file > file1
$ cat file1                   # so does a legacy sed
one
$ PATH=$(getconf PATH) sed  '' file
one                           # while a POSIX conformant sed warns you:
sed: Missing newline at end of file file.
two
$ vi file
one
two
~
~
~                             # vi tells you too about the issue
"file" [Incomplete last line] 2 lines, 7 characters

:w

"file" 2 lines, 8 characters  # and tells it writes two lines
                              # You'll even notice it writes one more
                              # character if you are a very shrewd observer :-)
:q
$ cat file                    # the file is now valid text
one
two
$ wc -l file                  # wc reports the expected number of lines
       2 file
$ sed '' file > file1         # sed works as expected
$ cat file1
one
two

หมายเหตุเพื่อรับเบาะแสบางอย่างเกี่ยวกับviรุ่นที่คุณกำลังใช้งานคุณสามารถใช้:veคำสั่ง มันแสดงให้เห็นที่นี่ฉันใช้ SVR4 แบบดั้งเดิมที่นี่ไม่ใช่vim:

:ve
Version SVR4.0, Solaris 2.5.0

เห็นได้ชัดว่าคุณกำลังระบุ:

:ve
Version 3.10

นั่นน่าจะหมายถึงว่า AIX viนั้นใช้ซอร์สโค้ด SVR3

ไม่ว่าในกรณีใดพฤติกรรมนี้และ[Incomplete last line]ข้อความเตือนจะอยู่ในviซอร์สโค้ดของ Bill Joy ตั้งแต่อย่างน้อยปี 1979 และ AFAIK ซึ่งเก็บรักษาไว้ในทุกสาขาที่สร้างจากซอร์สโค้ด System V ที่เผยแพร่ซึ่ง Unix ซึ่งเป็นกรรมสิทธิ์ของ AIX ถูกสร้างขึ้น

ตามลำดับเหตุการณ์พฤติกรรมนี้ไม่ได้เป็นผลมาจาก POSIX ที่สอดคล้องกัน แต่เป็นผลมาจากการตัดสินใจเดิมของ Bill Joy ที่จะเป็นประโยชน์กับผู้ใช้ที่แก้ไขไฟล์ข้อความปลอมและอีกสิบปีต่อมาคณะกรรมการ POSIX ก็ยังคงอดทนต่อไป

หากคุณใช้edแทนคุณviจะพบว่าอดีตนั้นมีความละเอียดมากกว่าเกี่ยวกับปัญหาอย่างน้อยถ้าคุณedมาจาก SVR3 หรือสาขาที่ใหม่กว่า:

$ ed file
'\n' appended
8
q

โปรดทราบว่าไฟล์ว่างเปล่าเป็นไฟล์ข้อความที่ถูกต้องที่เกิดขึ้นมีเส้นศูนย์ เนื่องจากไม่มีบรรทัดที่ไม่ถูกทำลายที่จะแก้ไขviจึงไม่ต้องเพิ่มบรรทัดใหม่เมื่อบันทึกไฟล์


1
ผมเชื่อว่าคุณผิดพลาดที่เป็นกลุ่มสำหรับ vi;) vi มรดกเป็นอย่างมาก verbose น้อยกว่านี้ ...
โอลิเวีย Dulac

@OlivierDulac ฉันไม่ได้สับสนพวกเขา การทดสอบนี้เสร็จสิ้นโดยใช้รุ่น SVR4 viเช่นเดียวกับ OP แม้ว่าในระบบปฏิบัติการยูนิกซ์อื่น นี่ไม่ใช่vimหรือโคลนอื่น ตอบปรับปรุงเพื่อชี้แจงนี้
jlliagre

@OlivierDulac อืมฉันเพิ่งสังเกตเห็นว่าคุณเป็น OP จริง ๆ ดูเหมือนว่า AIX กำลังใช้สาขา System V ที่เก่ากว่าสำหรับviการนำไปใช้งาน อาจจะเป็น SVR3 คุณแน่ใจหรือว่าไม่มี[Incomplete last line]ข้อความเมื่อคุณเปิดไฟล์
jlliagre

@OlivierDulac ดูเหมือนว่าลิงก์นี้จะบอกถึงข้อความเดียวกันนี้โดยviการใช้งานAIX : www-01.ibm.com/support/docview.wss?uid=isg1IZ27694
jlliagre

ฉันจะลองดูพรุ่งนี้พรุ่งนี้
Olivier Dulac

51

POSIX ต้องการพฤติกรรมนี้จึงไม่ผิดปกติ แต่อย่างใด

จากคู่มือPOSIX vi :

ไฟล์อินพุต

ดูที่ส่วนอินพุตไฟล์ของคำสั่ง ex สำหรับคำอธิบายของอินพุตไฟล์ที่สนับสนุนโดยคำสั่ง vi

ติดตามเส้นทางไปยังคู่มือPOSIX ex :

ไฟล์อินพุต

ไฟล์อินพุตจะเป็นไฟล์ข้อความหรือไฟล์ที่เป็นไฟล์ข้อความยกเว้นบรรทัดสุดท้ายที่ไม่สมบูรณ์ซึ่งมีความยาวไม่เกิน {LINE_MAX} -1 ไบต์และไม่มีอักขระ NUL โดยค่าเริ่มต้นบรรทัดสุดท้ายใด ๆ ที่ไม่สมบูรณ์จะได้รับการปฏิบัติเสมือนว่ามันมีส่วนท้าย <newline> การแก้ไขไฟล์ในรูปแบบอื่นอาจได้รับอนุญาตจากการใช้งานแบบ ex

ส่วน OUTPUT FILES ของคู่มือ vi ยังเปลี่ยนเส้นทางไปยังอดีต:

ไฟล์ผลลัพธ์

เอาต์พุตจาก ex จะเป็นไฟล์ข้อความ

คู่ของคำจำกัดความ POSIX:

3.397 ไฟล์ข้อความ

ไฟล์ที่มีตัวอักษรจัดเป็นศูนย์หรือมากกว่าบรรทัด บรรทัดไม่มีอักขระ NUL และไม่มีความยาวเกิน {LINE_MAX} ไบต์รวมถึงอักขระ <newline> แม้ว่า POSIX.1-2008 จะไม่แยกความแตกต่างระหว่างไฟล์ข้อความและไฟล์ไบนารี (ดูมาตรฐาน ISO C) แต่ยูทิลิตี้จำนวนมากจะสร้างเอาต์พุตที่สามารถคาดการณ์ได้หรือมีความหมายเมื่อทำงานกับไฟล์ข้อความ ยูทิลิตี้มาตรฐานที่มีข้อ จำกัด ดังกล่าวจะระบุ "ไฟล์ข้อความ" ในส่วน STDIN หรือ INPUT FILES เสมอ

3.206 สาย

ลำดับที่เป็นศูนย์หรือมากกว่าอักขระที่ไม่ใช่ <newline> รวมถึงอักขระ <newline> ที่ยกเลิก

คำจำกัดความเหล่านี้ในบริบทของข้อความที่ตัดตอนมาจากหน้าคู่มือเหล่านี้หมายความว่าในขณะที่การปฏิบัติตาม ex / vi ต้องยอมรับไฟล์ข้อความที่มีรูปแบบไม่ถูกต้องหากความผิดปกติเพียงอย่างเดียวของไฟล์นั้นคือการขึ้นบรรทัดใหม่ครั้งสุดท้าย

ในขณะที่โพสต์นี้ได้อ้างถึงมาตรฐาน POSIX รุ่น 2013 ข้อกำหนดที่เกี่ยวข้องยังปรากฏในรุ่น 1997 ที่เก่ากว่าด้วย

สุดท้ายหากคุณพบว่าบรรทัดใหม่ของอดีตไม่เป็นที่พอใจคุณจะรู้สึกละเมิดอย่างสุดซึ้งโดย Seventh Edition UNIX (1979) ซึ่งไม่ยอมแพ้ของเอ็ด จากคู่มือ :

เมื่ออ่านไฟล์ให้ละเว้นอักขระ ASCII NUL และอักขระทั้งหมดหลังจากบรรทัดใหม่สุดท้าย มันปฏิเสธที่จะอ่านไฟล์ที่มีอักขระที่ไม่ใช่ ASCII


ขอบคุณที่ตอบคำถามของฉัน ฉันจะรออีกสองสามวันในกรณีที่คำตอบดีกว่านี้ แต่ตอนนี้ฉันรู้สึกว่าคุณสามารถเป็นคำตอบที่ยอมรับได้
Olivier Dulac

ทำได้ดีมากในคำตอบที่มีเอกสารครบถ้วนตรงจากข้อกำหนด! :)
ไวด์การ์ด

1
@ Wildcard พฤติกรรมที่นำหน้ารายละเอียดว่า
jlliagre

@jlliagre ยกเว้นว่าคุณมี memoir จาก Bill Joy หรือผู้สร้างex(ไม่รู้ชื่อของเขา) ฉันคิดว่า POSIX spec นั้นดีเท่าที่ควร ;) ใกล้เคียงที่สุดกับ "แหล่งต้นฉบับ" ณ จุดนี้ถึงแม้ว่ามันจะเป็นความจริง
Wildcard

3
@ Wildcard exเขียนโดย Bill Joy และ Chuck Alley ( web.cecs.pdx.edu/~kirkenda/joy84.html ) ฉันไม่ได้ตั้งคำถาม POSIX และความจริงที่viปล่อยออกมาในปัจจุบันทำตามฉันเพียงแค่ระบุพฤติกรรม ถือกำเนิดมานาน
jlliagre

1

ฉันไม่จำพฤติกรรมอื่น ๆ ที่มีการเพิ่มบรรทัดใหม่ในตอนท้ายของไฟล์ (ใช้viตั้งแต่กลาง 80)

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


สิ่งที่ทำให้ฉันประหลาดใจคือการเพิ่มขึ้นบรรทัดใหม่ ... ฉันคาดหวังว่า vi จะไม่เพิ่มมันอย่างเงียบ ๆ แต่ดูเหมือนว่า ... ฉันกำลังมองหาคำอธิบายเกี่ยวกับทัศนคตินี้ (ความจริงที่น่าเป็นห่วงคือ: ฉันเปิด foo2 (โดยไม่มี ต่อท้าย LF) และเพียงแค่: wq มันเปลี่ยนเนื้อหาของมัน ... ดังนั้นมันจึงแสดงให้ฉันเห็น แต่ช่วยอีกอย่าง ... แปลกประหลาดที่จะพูดน้อย ^^
Olivier Dulac

ในรุ่นก่อน ( ed) คุณจะสร้างบรรทัดและแก้ไขบรรทัดเหล่านั้นไม่ใช่โดยการต่อท้ายอักขระ ฉันมักจะคิดถึง vi เป็นตัวแก้ไขบรรทัด แต่ฉันเข้าใจความประหลาดใจของคุณ
Anthon

1

ข้อความที่ขาด newline สุดท้ายที่ไม่ถูกต้องวิ่งผ่าน shell whileloop ส่งผลให้บรรทัดสุดท้ายถูกทิ้งอย่างเงียบ ๆ

$ (echo transaction 1; echo -n transaction 2) \
  | while read line; do echo $line; done
transaction 1
$ 

การทำให้มั่นใจว่ามีการขึ้นบรรทัดใหม่ขั้นสูงสุดคือสิทธิและสติปัญญาและการเริ่มต้นที่เหมาะสม ตัวเลือกอื่นเกี่ยวข้องกับการรู้และมีเวลาในการตรวจสอบเชลล์โค้ดทั้งหมดที่สัมผัสกับข้อความที่ไม่มีบรรทัดใหม่ขั้นสุดท้ายหรือเสี่ยงต่อการสูญเสียบรรทัดสุดท้ายของข้อความ

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