关于Unix:为什么文本文件应该以换行符结尾?

Why should text files end with a newline?

我想这里的每个人都熟悉这样一句话:所有文本文件都应该以换行符结尾。我知道这个"规则"很多年了,但我一直在想——为什么?


因为POSIX标准就是这样定义一行的:

3.206 Line
A sequence of zero or more non- characters plus a terminating character.

因此,不以换行符结尾的行不被视为实际行。这就是为什么有些程序在处理文件的最后一行时遇到问题,如果它不是换行终止的话。

在使用终端仿真器时,这一准则至少有一个硬优势:所有的UNIX工具都期望这种约定,并使用它。例如,当将文件与cat连接时,以换行符结尾的文件与没有换行符的文件具有不同的效果:

1
2
3
4
5
6
7
8
$ more a.txt
foo
$ more b.txt
bar$ more c.txt
baz
$ cat {a,b,c}.txt
foo
barbaz

而且,正如前面的示例所示,在命令行上显示文件时(例如,通过more显示),以换行符结尾的文件会导致正确的显示。不正确终止的文件可能会被篡改(第二行)。

为了保持一致性,遵循这条规则非常有帮助——否则,在处理默认的UNIX工具时,做这件事会带来额外的工作。

换个角度考虑:如果行不是以换行符结尾,那么使诸如cat之类的命令很难使用:如何使命令连接文件,以便

  • 它将每个文件的开始都放在一个新的行上,这是95%的时候您想要的;但是
  • 它允许合并两个文件的最后一行和第一行,如上面的示例中,在b.txtc.txt之间?
  • 当然,这是可以解决的,但是您需要使cat的使用更加复杂(通过添加位置命令行参数,例如cat a.txt --no-newline b.txt c.txt),现在命令而不是每个单独的文件控制它如何与其他文件粘贴在一起。这几乎肯定不方便。

    …或者你需要引入一个特殊的哨兵角色来标记一条应该继续而不是终止的线。好吧,现在你和posix的情况是一样的,除了倒转(换行符而不是换行符)。

    现在,在不符合POSIX的系统(现在大部分是Windows)上,要点是无意义的:文件通常不会以换行符结尾,例如,行的(非正式)定义可能是"用换行符分隔的文本"(注意重点)。这是完全有效的。然而,对于结构化数据(例如编程代码),它使解析变得最简单:它通常意味着必须重写解析器。如果一个解析器最初是在考虑POSIX定义的情况下编写的,那么修改令牌流可能更容易,而不是解析器-换句话说,在输入的末尾添加一个"人工换行"标记。


    每行应以换行符结尾,包括最后一行。如果文件的最后一行未被换行终止,则某些程序在处理该行时遇到问题。

    GCC发出警告不是因为它不能处理文件,而是因为它必须作为标准的一部分。

    The C language standard says
    A source file that is not empty shall end in a new-line character, which shall not be immediately preceded by a backslash character.

    Since this is a"shall" clause, we must emit a diagnostic message for a violation of this rule.

    This is in section 2.1.1.2 of the ANSI C 1989 standard. Section 5.1.1.2 of the ISO C 1999 standard (and probably also the ISO C 1990 standard).

    参考:GCC/GNU邮件档案。


    这个答案是一个技术性的答案,而不是观点。好的。

    如果我们想成为POSIX纯粹主义者,我们将一条线定义为:好的。

    A sequence of zero or more non- characters plus a terminating character.

    Ok.

    来源:http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/v1_chap03.html tag_03_206好的。

    不完整的行,如:好的。

    A sequence of one or more non- characters at the end of the file.

    Ok.

    来源:http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/v1_chap03.html tag_03_195好的。

    文本文件为:好的。

    A file that contains characters organized into zero or more lines. The lines do not contain NUL characters and none can exceed {LINE_MAX} bytes in length, including the character. Although POSIX.1-2008 does not distinguish between text files and binary files (see the ISO C standard), many utilities only produce predictable or meaningful output when operating on text files. The standard utilities that have such restrictions always specify"text files" in their STDIN or INPUT FILES sections.

    Ok.

    来源:http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/v1_chap03.html tag_03_397好的。

    字符串为:好的。

    A contiguous sequence of bytes terminated by and including the first null byte.

    Ok.

    来源:http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/v1_chap03.html tag_03_396好的。

    由此,我们可以得出,我们唯一可能遇到任何类型问题的时间是,如果我们将一个文件或一个文件的一行作为文本文件处理(即,一个文本文件是一个零行或多行的组织,并且我们知道的一行必须以一个结束)。好的。

    实例:wc -l filename。好的。

    wc的手册中,我们看到:好的。

    A line is defined as a string of characters delimited by a character.

    Ok.

    JavaScript、HTML和CSS文件的含义是什么?它们是文本文件?好的。

    在浏览器、现代IDE和其他前端应用程序中,在EOF中跳过EOL没有问题。应用程序将正确分析文件。因为并非所有的操作系统都符合POSIX标准,所以对于非OS工具(如浏览器)来说,根据POSIX标准(或任何OS级标准)处理文件是不切实际的。好的。

    因此,我们可以相对地确信,EOF的EOL在应用程序级别几乎不会产生负面影响——无论它是否在UNIX操作系统上运行。好的。

    此时,我们可以自信地说,在客户端处理JS、HTML、CSS时,在EOF跳过EOL是安全的。实际上,我们可以声明缩小这些文件中的任何一个,包含no都是安全的。好的。

    我们可以更进一步说,就nodejs而言,它也不能遵循POSIX标准,因为它可以在不符合POSIX的环境中运行。好的。

    那我们还剩下什么?系统级工具。好的。

    这意味着可能出现的唯一问题是,工具要努力使其功能符合POSIX的语义(例如,wc中所示的行的定义)。好的。

    即便如此,并不是所有的shell都会自动粘附到posix上。例如,bash并不默认为posix行为。有一个开关可以启用它:POSIXLY_CORRECT。好的。

    关于EOL价值的思考食物:http://www.rfc-editor.org/eolstory.txt好的。

    在工具轨道上,出于所有实际目的和目的,让我们考虑一下:好的。

    我们来处理一个没有EOL的文件。在编写本文时,本例中的文件是一个没有eol的小型JavaScript。好的。

    1
    2
    3
    4
    5
    6
    7
    8
    curl http://cdnjs.cloudflare.com/ajax/libs/AniJS/0.5.0/anijs-min.js -o x.js
    curl http://cdnjs.cloudflare.com/ajax/libs/AniJS/0.5.0/anijs-min.js -o y.js

    $ cat x.js y.js > z.js

    -rw-r--r--  1 milanadamovsky   7905 Aug 14 23:17 x.js
    -rw-r--r--  1 milanadamovsky   7905 Aug 14 23:17 y.js
    -rw-r--r--  1 milanadamovsky  15810 Aug 14 23:18 z.js

    注意,cat文件大小正好是其各个部分的总和。如果Javascript文件的串联是JS文件的问题,那么更合适的问题是用分号启动每个Javascript文件。好的。

    正如这个线程中的另一个人所提到的那样:如果您希望cat两个文件的输出变成一行而不是两行,该怎么办?换句话说,cat做了它应该做的。好的。

    catman只提到到eof时的读取输入,而不是。注意,cat-n开关也将打印出一条非结束行(或不完整行),即计数从1开始(根据man)。好的。

    -n Number the output lines, starting at 1.

    Ok.

    现在我们了解了posix是如何定义行的,这种行为变得模棱两可,或者说是不兼容的。好的。

    了解给定工具的目的和遵从性将有助于确定使用EOL结束文件的关键性。在C、C++、Java(JAR)等中…一些标准将规定一条新的有效性线——JS、HTML、CSS不存在这样的标准。好的。

    例如,如果不使用wc -l filename,可以执行awk '{x++}END{ print x}' filename,并确保任务的成功不会受到我们可能希望处理的未写入文件(例如,第三方库,如minified js we curl)的危害,除非我们的目的是真正按照posix兼容的意义计算行。好的。

    结论好的。

    对于某些文本文件(如JS、HTML和CSS),在EOF中跳过EOL将产生负面影响(如果有的话),这将是非常少的实际用例。如果我们依赖于的存在,那么我们只将工具的可靠性限制在我们编写的文件上,并将自己暴露在第三方文件引入的潜在错误中。好的。

    故事的寓意:工程师的工具,不具有依赖EOL在EOF的弱点。好的。

    请随意发布应用于JS、HTML和CSS的用例,在这些用例中,我们可以检查跳过EOL是如何产生不利影响的。好的。好啊。


    这可能与以下两者之间的差异有关:

    • 文本文件(每行应以行结尾)
    • 二进制文件(没有真正的"行",必须保留文件的长度)

    如果每一行都以行尾结尾,这就避免了,例如,连接两个文本文件将使第一行的最后一行进入第二行的第一行。

    另外,编辑器可以在加载时检查文件是否以行尾结尾,将其保存在本地选项"eol"中,并在写入文件时使用该选项。

    几年前(2005年),许多编辑(zde,eclipse,scite,…)确实"忘记"了最后的EOL,这并不是很受欢迎。不仅如此,他们还错误地将最终的EOL解释为"开始新行",并实际开始显示另一行,就好像它已经存在一样。与在上面的某个编辑器中打开文本文件相比,使用"正确的"文本文件和像Vim这样的行为良好的文本编辑器,这是非常明显的。它在文件的实际最后一行下面显示了一行多余的内容。你看到这样的东西:

    1
    2
    3
    4
    1 first line
    2 middle line
    3 last line
    4


    一些工具对此有所期待。例如,wc预计:

    1
    2
    3
    4
    $ echo -n"Line not ending in a new line" | wc -l
    0
    $ echo"Line ending with a new line" | wc -l
    1


    基本上,有许多程序如果不能得到最终的EOL EOF,就不能正确地处理文件。

    GCC警告您这一点,因为它是C标准的一部分。(第5.1.1.2节显然)

    "文件结尾没有换行"编译器警告


    这源于早期使用简单终端的时代。换行符用于触发传输数据的"刷新"。

    今天,不再需要换行符了。当然,如果新线不在的话,很多应用程序仍然存在问题,但我认为这是这些应用程序中的一个缺陷。

    但是,如果您有一个需要换行的文本文件格式,那么您可以得到非常便宜的简单数据验证:如果文件以一个末尾没有换行的行结尾,那么您就知道该文件被破坏了。每行只有一个额外的字节,您可以高精度地检测损坏的文件,几乎没有CPU时间。


    除了上述实际原因之外,如果Unix的创建者(Thompson,Ritchie,et al.)或他们的multics前辈意识到使用行终止符而不是行分隔符有一个理论上的原因,我也不会感到惊讶:使用行终止符,您可以对所有可能的行文件进行编码。使用行分隔符,零行文件和包含单个空行的文件之间没有区别;它们都被编码为包含零字符的文件。

    所以,原因是:

  • 因为这就是posix定义它的方式。
  • 因为有些工具期待它或者没有它就"行为不端"。例如,如果最后一个"行"没有以换行符结尾,那么wc -l将不计算在内。
  • 因为它简单方便。在Unix上,cat只起作用,而且不复杂。它只复制每个文件的字节,不需要任何解释。我不认为有一个dos等同于cat。使用copy a+b c将导致文件a的最后一行与文件b的第一行合并。
  • 因为零行的文件(或流)可以与一个空行的文件区分开来。

  • 一个单独的用例:当你的文本文件被版本控制时(在本例中,特别是在Git下,尽管它也适用于其他人)。如果将内容添加到文件结尾,则之前最后一行的行将被编辑为包含换行符。这意味着blame通过查看文件来确定最后一次编辑该行的时间,将显示文本添加,而不是实际希望看到的提交。


    还有一个实际的编程问题,即文件末尾缺少新行:readbash内置(我不知道其他read实现)无法按预期工作:

    1
    2
    3
    4
    5
    printf $'foo
    bar' | while read line
    do
        echo $line
    done

    这只打印foo!原因是,当read遇到最后一行时,它将内容写入$line但返回退出代码1,因为它达到了EOF。这打破了while循环,因此我们永远无法到达echo $line部分。如果要处理这种情况,必须执行以下操作:

    1
    2
    3
    4
    5
    while read line || [ -n"${line-}" ]
    do
        echo $line
    done < <(printf $'foo
    bar')

    也就是说,如果read由于文件末尾的一个非空行而失败,则执行echo。当然,在这种情况下,输出中会有一个额外的换行符,而不是输入中的换行符。


    可能只是一些解析代码期望它存在。

    我不确定我是否会认为这是一个"规则",而且这肯定不是我信奉的宗教。最合理的代码将知道如何逐行分析文本(包括编码)(任何选择的行尾),最后一行是否有换行符。

    事实上,如果你以一条新的线结束:在EOL和EOF之间有(理论上)一条空的最后一条线吗?想一想……


    Why should (text) files end with a newline?

    因为:

  • 许多程序表现不好,或者没有它就会失败。

  • 即使能够很好地处理文件的程序也缺少结束的'
    '
    ,该工具的功能可能无法满足用户的期望——在这个角落的案例中,这可能不清楚。

  • 程序很少禁止最终的'
    '
    (我不知道有)。

  • 然而,这引出了下一个问题:

    What should code do about text files without a newline?

  • 最重要的是,不要编写假定文本文件以换行符结尾的代码。假设一个文件符合某种格式,就会导致数据损坏、黑客攻击和崩溃。例子:

    1
    2
    3
    4
    5
    6
    7
    8
    // Bad code
    while (fgets(buf, sizeof buf, instream)) {
      // What happens if there is no
    , buf[] is truncated leading to who knows what
      buf[strlen(buf) - 1] = '\0';  // attempt to rid trailing

      ...
    }
  • 如果需要最后一个尾随的'
    '
    ,请提醒用户其不在,并采取措施。Lows,验证文件的格式。注意:这可能包括对最大行长度、字符编码等的限制。

  • 明确定义,文档,代码对缺失的最终'
    '
    的处理。

  • 尽可能不要生成缺少结尾'
    '
    的文件。


  • 多年来我一直在思考这个问题。但我今天遇到了一个很好的理由。

    想象一个每行都有记录的文件(例如:一个csv文件)。电脑在文件的末尾写记录。但它突然坠毁了。最后一行完成了吗?(情况不太好)

    但是如果我们总是终止最后一行,那么我们就会知道(只需检查最后一行是否终止)。否则,为了安全起见,我们可能每次都要丢弃最后一行。


    现在已经很晚了,但是我在文件处理中遇到了一个错误,那是因为文件没有以空换行符结尾。我们使用sed处理文本文件,sed省略了输出的最后一行,这导致无效的json结构,并将进程的其余部分发送到失败状态。

    我们所做的就是:

    有一个示例文件称:foo.txt,其中包含一些json内容。

    1
    2
    3
    4
    5
    6
    [{
        someProp: value
    },
    {
        someProp: value
    }] <-- No newline here

    该文件是在寡妇机器中创建的,窗口脚本正在使用powershall命令处理该文件。一切都好。

    当我们使用sed命令sed 's|value|newValue|g' foo.txt > foo.txt.tmp处理相同的文件时新生成的文件是

    1
    2
    3
    4
    5
    [{
        someProp: value
    },
    {
        someProp: value

    然后,由于无效的JSON,它使其余的进程失败了。

    所以用空行结束文件总是一个好的做法。


    我一直觉得,这条规则来自于分析一个没有结尾换行的文件时的困难。也就是说,如果行尾是由eol字符或eof定义的,那么您将最终编写代码。假设一条线以EOL结束是简单的。

    不过,我相信这个规则是从需要换行的C编译器派生出来的。正如"文件结尾没有换行"编译器警告中指出的那样,include不会添加换行。


    假设正在处理文件,而另一个进程仍在生成该文件。

    可能跟那有关?指示文件已准备好处理的标志。


    我个人喜欢源代码文件末尾的新行。

    因此,它可能起源于Linux或所有UNIX系统。我记得这里有编译错误(如果我没弄错的话是gcc),因为源代码文件没有以一个空的新行结尾。为什么这样做让人疑惑。


    嗯,这是个人风格和意见的问题。

    在过去,我没有把这句话放在新词上。保存的字符意味着通过14.4K调制解调器的速度更快。

    稍后,我将换行,这样使用shift+downarrow可以更容易地选择最后一行。