如何在文件末尾添加换行符?

k0p*_*kus 237 shell bash text-processing newlines

使用版本控制系统时,当差异显示为 时,我对噪音感到恼火No newline at end of file

所以我想知道:如何在文件末尾添加换行符以摆脱这些消息?

l0b*_*0b0 254

给你

sed -i -e '$a\' file
Run Code Online (Sandbox Code Playgroud)

或者对于 OS X sed

sed -i '' -e '$a\' file
Run Code Online (Sandbox Code Playgroud)

\n文件尚未以换行符结尾时,才会在文件末尾添加。所以如果你运行它两次,它不会添加另一个换行符:

$ cd "$(mktemp -d)"
$ printf foo > test.txt
$ sed -e '$a\' test.txt > test-with-eol.txt
$ diff test*
1c1
< foo
\ No newline at end of file
---
> foo
$ echo $?
1
$ sed -e '$a\' test-with-eol.txt > test-still-with-one-eol.txt
$ diff test-with-eol.txt test-still-with-one-eol.txt
$ echo $?
0
Run Code Online (Sandbox Code Playgroud)

  • `$` 有两种不同的含义。在正则表达式中,例如形式`/&lt;regex&gt;/`,它具有通常的“匹配行尾”的含义。否则,用作地址时,sed 赋予它特殊的“文件中的最后一行”含义。该代码有效是因为 sed 默认情况下会在其输出中附加一个换行符(如果它不存在)。代码“$a\”只是说“匹配文件的最后一行,不添加任何内容”。但隐含地,如果换行符不存在,sed 会将换行符添加到它处理的每一行(例如这个 `$` 行)。 (34认同)
  • 如果文件已经以换行符结尾,这不会改变它,但它会重写它并更新它的时间戳。这可能重要也可能无关紧要。 (4认同)
  • @dosentmatter *“sed '$q' 不是更清晰吗?q 的意思是退出,而不是不附加任何内容。”* 我用 `GNU sed 4.4` 测试了 `sed '$q'`,它没有用。`q` 只是不做任何事情就退出了。```a\``` 有一些额外的逻辑,如果它不存在,将添加一个尾随换行符。 (2认同)
  • 在 OS X 13.4 版本的 sed 上不适用于我。没有错误,但不附加换行符。 (2认同)

Pat*_*ity 74

为了递归地清理一个项目,我使用了这个 oneliner:

git ls-files -z | while IFS= read -rd '' f; do tail -c1 < "$f" | read -r _ || echo >> "$f"; done
Run Code Online (Sandbox Code Playgroud)

解释:

  • git ls-files -z列出存储库中的文件。它采用可选模式作为附加参数,如果您想将操作限制在某些文件/目录中,这在某些情况下可能很有用。作为替代方案,您可以使用find -print0 ...或类似的程序来列出受影响的文件 - 只需确保它发出NUL-delimited 条目。

  • while IFS= read -rd '' f; do ... done 遍历条目,安全地处理包含空格和/或换行符的文件名。

  • tail -c1 < "$f" 从文件中读取最后一个字符。

  • read -r _ 如果缺少尾随换行符,则以非零退出状态退出。

  • || echo >> "$f" 如果前一个命令的退出状态非零,则向文件追加一个换行符。

  • 如果您只想清理文件的子集,您也可以这样做:`find -name \*.java | 当读 f 时;做尾-n1 $f | 读-r _ || 回声 &gt;&gt; $f; 完成` (2认同)

sr_*_*sr_ 44

看一看:

$ echo -n foo > foo 
$ cat foo
foo$
$ echo "" >> foo
$ cat foo
foo
Run Code Online (Sandbox Code Playgroud)

所以echo "" >> noeol-file应该做到这一点。(或者您的意思是要求识别这些文件修复它们?)

编辑删除""echo "" >> foo(见@ yuyichao的评论) EDIT2增加""再次(看到@Keith汤普森的评论)

  • `""` 不是必需的(至少对于 bash)和 `tail -1 | wc -l` 可用于查找文件末尾没有新行的文件 (5认同)
  • @yuyichao:bash 不需要`""`,但我已经看到`echo` 实现在不带参数的情况下调用时不打印任何内容(尽管我现在找不到任何一个能做到这一点)。`echo "" &gt;&gt; noeol-file` 可能稍微健壮一些。`printf "\n" &gt;&gt; noeol-file` 更是如此。 (5认同)
  • @KeithThompson,`csh` 的 `echo` 是已知的,在不传递任何参数时不输出任何内容。但是如果我们要支持非 Bourne-like shell,我们应该把它做成 `echo''` 而不是 `echo ""` 因为 `echo ""` 会输出 `""&lt;newline&gt;` 和 `rc ` 或 `es` 例如。 (2认同)

enz*_*tib 19

使用ed. 此解决方案仅影响最后一行,并且仅当\n缺少时:

ed -s file <<< w
Run Code Online (Sandbox Code Playgroud)

它基本上可以通过脚本打开文件进行编辑,脚本是w将文件写回磁盘的单个命令。它基于ed(1)手册页中的这句话:

限制
       (……)

       如果文本(非二进制)文件没有以换行符结尾,
       然后 ed 在读/写它时附加一个。在二进制的情况下
       文件,ed 不会在读/写时附加换行符。

  • 为我工作;它甚至会打印“附加换行符”(Arch Linux 上的 ed-1.10-1)。 (4认同)

Bar*_* IO 19

一种简单的、可移植的、符合 POSIX 标准的向文本文件添加不存在的最终换行符的方法是:

[ -n "$(tail -c1 file)" ] && echo >> file
Run Code Online (Sandbox Code Playgroud)

这种方法不需要读取整个文件;它可以简单地寻找 EOF 并从那里开始工作。

这种方法也不需要在背后创建临时文件(例如 sed -i),因此硬链接不受影响。

只有当命令替换的结果是非空字符串时,echo 才会在文件中附加一个换行符。请注意,只有在文件不为空且最后一个字节不是换行符时才会发生这种情况。

如果文件的最后一个字节是换行符,则 tail 返回它,然后命令替换将其剥离;结果是一个空字符串。-n 测试失败并且 echo 不运行。

如果文件为空,命令替换的结果也是一个空字符串,再次 echo 不会运行。这是可取的,因为空文件不是无效的文本文件,也不等同于具有空行的非空文本文件。

  • 请注意,如果文件中的最后一个字符是多字节字符(例如在 UTF-8 语言环境中),或者如果语言环境是 C 并且文件中的最后一个字节具有第 8 个字符,则它不适用于 `yash`位设置。对于其他 shell(zsh 除外),如果文件以 NUL 字节结尾,它不会添加换行符(但话说回来,这意味着即使添加了换行符,输入也将是非文本的)。 (2认同)

Ale*_*der 16

无论如何添加换行符:

echo >> filename
Run Code Online (Sandbox Code Playgroud)

这是一种在添加换行符之前检查末尾是否存在换行符的方法,使用 Python:

f=filename; python -c "import sys; sys.exit(open(\"$f\").read().endswith('\n'))" && echo >> $f
Run Code Online (Sandbox Code Playgroud)

  • 如果你在循环中调用 python,启动时间很重要,这就是为什么我说考虑在 python 中执行循环。*然后*您只需支付一次启动费用。对我来说,启动成本的一半是整个 snipit 时间的一半以上,我认为这是一笔可观的开销。(同样,如果只做少量文件则无关紧要) (4认同)
  • Python 的启动时间在这里是 0.03 秒。你真的认为这有问题吗? (3认同)
  • `echo ""` 似乎比 `echo -n '\n'` 更健壮。或者你可以使用 `printf '\n'` (3认同)
  • 这对我来说很好用 (2认同)

ImH*_*ere 9

最快的解决方案是:

[ -n "$(tail -c1 file)" ] && printf '\n' >>file 
Run Code Online (Sandbox Code Playgroud)
  1. 真的很快。
    在中等大小的文件上,seq 99999999 >file这需要几毫秒。
    其他解决方案需要很长时间:

    [ -n "$(tail -c1 file)" ] && printf '\n' >>file  0.013 sec
    vi -ecwq file                                    2.544 sec
    paste file 1<> file                             31.943 sec
    ed -s file <<< w                             1m  4.422 sec
    sed -i -e '$a\' file                         3m 20.931 sec
    
    Run Code Online (Sandbox Code Playgroud)
  2. 适用于 ash、bash、lksh、mksh、ksh93、attsh 和 zsh,但不适用于 yash。

  3. 如果不需要添加换行符,则不更改文件时间戳。
    此处介绍的所有其他解决方案都会更改文件的时间戳。
  4. 以上所有解决方案都是有效的 POSIX。

如果您需要一个可移植到 yash(以及上面列出的所有其他 shell)的解决方案,它可能会变得更加复杂:

f=file
if       [ "$(tail -c1 "$f"; echo x)" != "$(printf '\nx')" ]
then     printf '\n' >>"$f"
fi
Run Code Online (Sandbox Code Playgroud)


ImH*_*ere 5

测试文件的最后一个字节是否是换行符的最快方法是只读取最后一个字节。这可以通过tail -c1 file. 但是,当文件中的最后一个字符是 UTF- 8 值。

查找文件的最后一个字节是否为新行的正确的、符合 POSIX 标准的所有(合理的)shell 方法是使用 xxd 或 hexdump:

tail -c1 file | xxd -u -p
tail -c1 file | hexdump -v -e '/1 "%02X"'
Run Code Online (Sandbox Code Playgroud)

然后,比较上面的输出0A将提供一个可靠的测试。
避免在其他空文件中添加新行很有用。
将无法提供最后一个字符的文件0A,当然:

f=file
a=$(tail -c1 "$f" | hexdump -v -e '/1 "%02X"')
[ -s "$f" -a "$a" != "0A" ] && echo >> "$f"
Run Code Online (Sandbox Code Playgroud)

简短而甜蜜。这需要很少的时间,因为它只读取最后一个字节(寻找 EOF)。文件大也没关系。然后只在需要时添加一个字节。

不需要或使用临时文件。硬链接不受影响。

如果此测试运行两次,则不会添加另一个换行符。

  • 请注意,`xxd` 和 `hexdump` 都不是 POSIX 实用程序。在 POSIX 工具箱中,有 `od -An -tx1` 来获取一个字节的十六进制值。 (2认同)