使用版本控制系统时,当差异显示No newline at end of file时,我会被噪音烦恼。

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

评论

另请参见so / q / 10082204/155090

下面的不错的解决方案可以递归地清理所有文件。 @Patrick Oscity的回答

也将字节回显到文件。

展望未来,文本编辑器通常会提供选项来确保您和您的合作者可以使用一条尾随的换行符来保持整洁。

#1 楼

为了递归地清理项目,我使用以下代码:

git ls-files -z | while IFS= read -rd '' f; do tail -c1 < "$f" | read -r _ || echo >> "$f"; done


说明:


git ls-files -z列出存储库中的文件。它采用可选模式作为附加参数,如果要将操作限制在某些文件/目录中,则在某些情况下可能会很有用。或者,您可以使用find -print0 ...或类似程序列出受影响的文件-只需确保它发出NUL分隔的条目即可。
while IFS= read -rd '' f; do ... done遍历条目,安全地处理包含空格和/或换行符的文件名。 > tail -c1 < "$f"从文件中读取最后一个字符。
如果缺少尾随换行符,则read -r _以非零退出状态退出。
如果先前命令的退出状态为非零,则|| echo >> "$f"会将换行符追加到文件中。


评论


如果只想清理文件的子集,也可以这样做:find -name \ *。java |读f时做尾巴-n1 $ f |读-r _ ||回声>> $ f;完成

– Per Lundberg
19年3月22日在9:54

@StéphaneChazelas很好的建议,将尝试将其纳入我的答案。

–帕特里克·奥斯奇(Patrick Oscity)
19年3月22日在12:57

@PerLundberg,您还可以将模式传递给git ls-files,这仍将使您免于编辑版本控制中未跟踪的文件。

–帕特里克·奥斯奇(Patrick Oscity)
19 Mar 22 '19 at 12:58

@StéphaneChazelas添加IFS =来取消分隔符的设置,可以很好地保留周围的空白。仅当您的文件或目录的名称中带有换行符时,以null结尾的条目才有意义,这似乎有些牵强,但是我认为这是处理一般情况的更正确方法。就像一个小警告:读取-d选项在POSIX sh中不可用。

–帕特里克·奥斯奇(Patrick Oscity)
19 Mar 22 '19 at 13:26

@AaronFranke用git grep -zIl替换git ls-files -z''

–帕特里克·奥斯奇(Patrick Oscity)
20-3-19在22:20



#2 楼

在这里,您可以进行以下操作:

sed -i -e '$a\' file


对于OS X sed也可以:

sed -i '' -e '$a\' file


这将\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


评论


@jwd:来自man sed:$匹配最后一行。但是也许它只是偶然地起作用。您的解决方案也可以。

–l0b0
2012年2月20日在11:54



您的解决方案也更加优雅,我已经测试并提交了它,但是它如何工作?如果$与最后一行匹配,为什么不将另一个换行符添加到已经包含换行符的字符串中呢?

–l0b0
2012-2-20在12:09



$有两种不同的含义。在正则表达式内部(例如,格式为/ /),它具有通常的“匹配行尾”的含义。否则,sed用作地址,它具有特殊的“文件的最后一行”含义。该代码之所以有效,是因为sed默认情况下会在输出中添加换行符(如果尚不存在的话)。代码“ $ a \”仅表示“匹配文件的最后一行,并且不添加任何内容”。但隐式地,如果sed尚不存在,则sed将换行符添加到它处理的每一行(例如$行)。

– jwd
2012-2-22在19:07



如果文件已经以换行符结尾,则不会对其进行更改,但会重写并更新其时间戳。这可能或可能不重要。

–基思·汤普森(Keith Thompson)
16年2月19日在19:01

@dosentmatter“不是sed'$ q'更清晰吗?q表示退出,而不是不添加任何内容。”我用GNU sed 4.4测试了sed'$ q',它没有用。 q只是不做任何事情而退出。 a \具有一些额外的逻辑,如果不存在,则会添加尾随换行符。

– Wisbucky
19-09-25在23:31



#3 楼

看看:

$ echo -n foo > foo 
$ cat foo
foo$
$ echo "" >> foo
$ cat foo
foo


所以echo "" >> noeol-file应该可以解决问题。 (或者您是要请求识别这些文件并进行修复?)

edit从""中删除了echo "" >> foo(请参见@yuyichao的评论)
edit2再次添加了""(但请参见@Keith)汤普森的评论)

评论


“”不是必需的(至少对于bash而言),并且尾巴-1 | wc -l可用于查找文件,末尾没有换行

– yuyichao
2012-02-17 14:42

@yuyichao:bash不需要“”,但是我已经看到回声实现在不带参数的情况下调用时不显示任何内容(尽管我现在找不到这些实现)。 echo“” >> noeol文件可能更健壮。 printf“ \ n” >> noeol文件甚至更是如此。

–基思·汤普森(Keith Thompson)
2012-2-17在17:17

@ KeithThompson,csh的回声是未传递任何参数时不输出任何内容的回声。但是然后,如果我们要支持非类Bourne的外壳,则应使其设为echo”而不是echo“”,因为echo“”将使用rc或es输出“”

–StéphaneChazelas
16-2-19在11:49

@StéphaneChazelas:与csh不同,tcsh在不带参数的情况下调用时将打印换行符-不管$ echo_style的设置如何。

–基思·汤普森(Keith Thompson)
16-2-19在18:55

这是否会导致额外的换行符添加到已经具有换行符的文件中?

–亚伦·弗兰克(Aaron Franke)
20 Mar 19 '21 at 21:21

#4 楼

使用ed的另一种解决方案。此解决方案仅在缺少\n的情况下影响最后一行:

ed -s file <<< w


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

LIMITATIONS
       (...)

       If  a  text (non-binary) file is not terminated by a newline character,
       then ed appends one on reading/writing it.  In the  case  of  a  binary
       file, ed does not append a newline on reading/writing.


评论


这不会为我添加换行符。

–奥尔霍夫斯基
13年4月12日在1:46

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

– Stefan Majewsky
2015年3月10日10:00

#5 楼

无论如何添加换行符:

echo >> filename


以下是使用Python来检查换行符是否在末尾存在的一种方法:

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


评论


由于Python启动时间较慢,因此我不会在任何循环中使用python版本。当然,您可以根据需要在python中进行循环。

–凯文·考克斯(Kevin Cox)
13年9月9日14:03

Python的启动时间为0.03秒。您真的认为这有问题吗?

–亚历山大
13年10月10日在11:48

如果在循环中调用python,启动时间很重要,这就是为什么我说考虑在python中进行循环。然后,您只需支付一次启动费用。对我来说,启动成本的一半是整个snipit时间的一半以上,我认为这是相当大的开销。 (同样,如果只处理少量文件,则无关紧要)

–凯文·考克斯(Kevin Cox)
13年11月11日在16:35

echo“”似乎比echo -n'\ n'更健壮。或者您可以使用printf'\ n'

–基思·汤普森(Keith Thompson)
16年2月19日在18:58

这对我来说很好

–丹尼尔·戈麦斯·里科(Daniel Gomez Rico)
19年1月18日在22:58

#6 楼

一种简单,可移植,与POSIX兼容的方法可以在文本文件中添加最后的换行符:文本文件:

[ -n "$(tail -c1 file)" ] && echo >> file


这种方法不需要读取整个文件;

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

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

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

如果文件为空,则命令替换的结果也是一个空字符串,并且echo不再运行。这是理想的,因为空文件不是无效的文本文件,也不等同于带有空行的非空文本文件。

评论


请注意,如果文件中的最后一个字符是多字节字符(例如,在UTF-8语言环境中),或者语言环境为C,并且文件中的最后一个字节设置了第8位,则它不适用于yash 。对于其他外壳程序(zsh除外),如果文件以NUL字节结尾,则不会添加换行符(但是再次声明,即使添加了换行符,输入也将是非文本的)。

–StéphaneChazelas
16-2-19在11:39



@StéphaneChazelas已添加yash解决方案。

–艾萨克
17年1月14日在23:25

是否可以为文件夹和子文件夹中的每个文件运行此命令?

– Qwerty
17 Mar 24 '17在11:28



#7 楼

最快的解决方案是:

[ -n "$(tail -c1 file)" ] && printf '\n' >>file 





真的很快。
在中等大小的文件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


可用于ash,bash,lksh,mksh,ksh93,attsh和zsh,但不能用于yash。 br />如果不需要添加换行符,则不更改文件时间戳。
这里介绍的所有其他解决方案都可以更改文件的时间戳。
以上所有解决方案都是有效的POSIX。

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

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


#8 楼

测试文件的最后一个字节是否为换行符的最快方法是仅读取该最后一个字节。可以使用tail -c1 file来完成。但是,一种简单的测试字节值是否为新行的方法,这取决于外壳程序,通常在命令扩展中删除尾随新行时(例如),在yash中,当文件中的最后一个字符为UTF-时,失败8值。

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

tail -c1 file | xxd -u -p
tail -c1 file | hexdump -v -e '/1 "%02X"'


然后,将上述输出与0A进行比较,将提供可靠的测试。
避免在新的空文件中添加新行非常有用。
当然,不能提供最后一个字符0A的文件:

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


又短又甜。这只需要花费很少的时间,因为它只读取最后一个字节(寻求EOF)。文件是否大无关紧要。然后仅在需要时添加一个字节。

不需要临时文件。没有硬链接受到影响。

如果此测试运行两次,则不会添加其他换行符。

评论


请注意,xxd和hexdump都不是POSIX实用程序。在POSIX工具箱中,使用od -An -tx1获取字节的十六进制值。

–StéphaneChazelas
18年5月15日在15:45

@StéphaneChazelas请张贴它作为答案;我来这里找这个评论太多了:)

–开尔文
19-09-19在21:15



@kelvin,我已经更新了我的答案

–StéphaneChazelas
19-09-20在8:27

请注意,POSIX不能保证LF的值为0x0a。仍然有POSIX系统不是(基于EBCDIC的),尽管这些天来极为罕见。

–StéphaneChazelas
19-09-20在8:29

#9 楼

您最好对上次编辑文件的用户的编辑器进行更正。如果您是最后一个编辑文件的人-您使用的是什么编辑器,我想是textmate ..?

评论


Vim是有关的编辑器。但是总的来说,您是对的,我不仅应该修复症状;)

–k0pernikus
2012年2月17日下午13:46

对于vim,您必须竭尽所能,执行保存二进制文件的操作,以使vim不在文件末尾添加新行-只是不要执行该操作。或者,仅需更正现有文件,即可在vim中打开它们并保存文件,然后vim会为您“修复”缺少的换行符(可以轻松编写多个文件的脚本)

– AD7six
2012-02-17 13:50



我的emacs不在文件末尾添加换行符。

– Enzotib
2012年2月20日在17:45

感谢@ AD7six的评论,当我提交东西时,我不断从diff中获取幻象报告,内容涉及原始文件末尾没有换行符。无论我如何用vim编辑文件,我都无法在其中不添加换行符。因此,这只是vim所做的。

–陆even
2013年6月21日19:39

@enzotib:我的.emacs中有(setq require-final-newline'ask)

–基思·汤普森(Keith Thompson)
16年2月19日在18:59

#10 楼

如果您只想在处理某些管道时快速添加换行符,请使用以下命令:

outputting_program | { cat ; echo ; }


它也符合POSIX。

当然,您可以将其重定向到文件。

评论


我可以在管道中使用它的事实很有帮助。这使我可以计算CSV文件中的行数(不包括标题)。它有助于在不以换行符或回车结尾的Windows文件上获得准确的行数。猫file.csv | tr“ \ r”“ \ n” | { 猫;回声; } | sed“ / ^ [[:: space:]] * $ / d” |尾-n +2 | wc -l

–凯尔·托勒(Kyle Tolle)
2015年12月22日在16:54



#11 楼

假设输入中没有空值:

paste - <>infile >&0


...只要只在一个infile的尾部附加一个换行符就足够了已经。它只需要读一次输入文件就可以正确处理。

评论


那样就行不通了,因为stdin和stdout共享相同的打开文件描述(因此光标位于文件中)。您需要粘贴infile 1 <> infile。

–StéphaneChazelas
17年8月18日在14:03

#12 楼

尽管它不能直接回答问题,但这是我编写的一个相关脚本,用于检测未以换行符结尾的文件。这非常快。

find . -type f | # sort |        # sort file names if you like
/usr/bin/perl -lne '
   open FH, "<", $_ or do { print " error: $_"; next };
   $pos = sysseek FH, 0, 2;                     # seek to EOF
   if (!defined $pos)     { print " error: $_"; next }
   if ($pos == 0)         { print " empty: $_"; next }
   $pos = sysseek FH, -1, 1;                    # seek to last char
   if (!defined $pos)     { print " error: $_"; next }
   $cnt = sysread FH, $c, 1;
   if (!$cnt)             { print " error: $_"; next }
   if ($c eq "\n")        { print "   EOL: $_"; next }
   else                   { print "no EOL: $_"; next }
'


perl脚本从stdin读取(可选排序的)文件名列表,并为每个文件读取最后一个字节以确定是否为文件是否以换行符结尾。它非常快,因为它避免了读取每个文件的全部内容。对于读取的每个文件,它输出一行,如果发生某种错误,则以“ error:”为前缀,如果文件为空(不以换行符结尾!),则以“ empty:”开头;“ EOL:”(“行”),如果文件以换行符结尾,则为“ no EOL:”,如果文件没有以换行符结尾。

注意:脚本不处理包含换行符的文件名。如果您使用的是GNU或BSD系统,则可以通过添加-print0进行查找,-z进行排序以及-0进行perl处理所有可能的文件名,如下所示:

find . -type f -print0 | sort -z |
/usr/bin/perl -ln0e '
   open FH, "<", $_ or do { print " error: $_"; next };
   $pos = sysseek FH, 0, 2;                     # seek to EOF
   if (!defined $pos)     { print " error: $_"; next }
   if ($pos == 0)         { print " empty: $_"; next }
   $pos = sysseek FH, -1, 1;                    # seek to last char
   if (!defined $pos)     { print " error: $_"; next }
   $cnt = sysread FH, $c, 1;
   if (!$cnt)             { print " error: $_"; next }
   if ($c eq "\n")        { print "   EOL: $_"; next }
   else                   { print "no EOL: $_"; next }
'


当然,您仍然必须想出一种在输出中用换行符编码文件名的方法(留给读者练习)。

输出可能会被过滤(如果需要的话),以将换行符添加到不包含换行符的文件中,最简单的方法是使用

 echo >> "$filename"


缺少最终换行符可能会导致错误脚本,因为某些版本的Shell和其他实用程序在读取此类文件时将无法正确处理缺少的最终换行符。

根据我的经验,缺少最终换行符是由使用各种Windows实用程序进行编辑引起的文件。我从未见过vim在编辑文件时会导致缺少最后一个换行符,尽管它会报告此类文件。

最后,有很多较短(但较慢)的脚本可以循环其文件名输入打印不以换行结尾的文件,例如:

/usr/bin/perl -ne 'print "$ARGV\n" if /.\z/' -- FILE1 FILE2 ...


#13 楼

vi / vim / ex编辑器会在EOF上自动添加<EOL>,除非文件已包含它。

因此请尝试:

vi -ecwq foo.txt


to:

ex -cwq foo.txt


测试:

$ printf foo > foo.txt && wc foo.txt
0 1 3 foo.txt
$ ex -scwq foo.txt && wc foo.txt
1 1 4 foo.txt


要更正多个文件,请检查:如何修复'No文件末尾的换行符”以获取许多文件?在SO

为什么这么重要?为了使我们的文件与POSIX兼容。

#14 楼

至少在GNU版本中,仅grep ''awk 1规范化了其输入,并添加了最后的换行符(如果尚不存在的话)。他们确实在复制文件的过程中,如果文件很大,则会花费一些时间(但是源文件应该不会太大而无法读取吗?)并更新modtime,除非您执行类似的操作

 mv file old; grep '' <old >file; touch -r old file


(尽管在您签入的文件中,因为修改了文件,这样可能没问题)
,除非您更加小心,否则它将丢失硬链接,非默认权限和ACL等。

评论


或者只是grep''file 1 <> file,尽管那样仍然可以完全读写该文件。

–StéphaneChazelas
17年8月18日在14:08

#15 楼

要将接受的答案应用于当前目录(以及子目录)中的所有文件,请执行以下操作:

$ find . -type f -exec sed -i -e '$a\' {} \;


这在Linux(Ubuntu)上有效。在OS X上,您可能必须使用-i ''(未经测试)。

评论


注意查找。列出所有文件,包括.git中的文件。排除:找到。 -type f -not -path'./.git/*'-exec sed -i -e'$ a \'{} \;

–friederbluemle
15年7月14日在7:59

希望我在运行它之前已经读过此评论/想法。那好吧。

–kstev
2015年11月9日在4:02



#16 楼

在AIX ksh中有效:

lastchar=`tail -c 1 *filename*`
if [ `echo "$lastchar" | wc -c` -gt "1" ]
then
    echo "/n" >> *filename*
fi


对于我来说,如果文件缺少换行符,则wc命令返回值2,我们编写换行符。 br />

评论


反馈将以增票或减票的形式出现,或者将在评论中要求您概述您的答案/问题,而在答案正文中毫无意义地提出要求。保持重点,欢迎加入stackexchange!

–k0pernikus
2015年2月12日在2:45

#17 楼

添加到Patrick Oscity的答案中,如果您只想将其应用到特定目录,则还可以使用:换行符。

#18 楼

echo $'' >> <FILE_NAME>将在文件末尾添加一个空白行。

echo $'\n\n' >> <FILE_NAME>将在文件末尾添加3个空白行。

评论


StackExchange有一个有趣的格式,我为您修复了它:-)

–peterh-恢复莫妮卡
17年8月18日在13:49

#19 楼

如果您的文件以Windows行尾\r\n终止,并且您在Linux中,则可以使用此sed命令。如果尚未将\r\n添加到最后一行:

sed -i -e '$s/\([^\r]\)$/\r\n/'


说明:

-i    replace in place
-e    script to run
$     matches last line of a file
s     substitute
\([^\r]\)$    search the last character in the line which is not a \r
\r\n    replace it with itself and add \r\n


如果最后一行已经包含\r\n,则搜索正则表达式将不匹配,因此将不会发生任何事情。

#20 楼

您可以编写一个fix-non-delimited-line脚本,例如:
#! /bin/zsh -
zmodload zsh/system || exit
ret=0
for file do
  if sysopen -rwu0 -- "$file"; then
    if sysseek -w end -1; then
      read -r x || print -u0
    else
      syserror -p "Can't seek in $file before the last byte: "
      ret=1
    fi
  else
    ret=1
  fi
done
exit $ret

与这里给出的一些解决方案相反,它

应该高效,因为它不会派生任何进程,只能读取每个文件一个字节,并且不会重写文件(仅添加换行符)
不会破坏符号链接/硬链接或影响元数据(此外,ctime / mtime仅在添加换行符时更新)
即使最后一个字节是NUL或多字节字符的一部分,也应该可以正常工作。
文件名可能包含什么字符或非字符,都可以正常工作
应该处理正确的不可读或不可写或无法查找的文件(并相应地报告错误)
不应在空文件中添加换行符(但在这种情况下报告有关无效查找的错误)

您可以使用它例如:
that-script *.txt

git ls-files -z | xargs -0 that-script

POSIXly,您可以在功能上等效于
export LC_ALL=C
ret=0
for file do
  [ -s "$file" ] || continue
  {
    c=$(tail -c 1 | od -An -vtc)
    case $c in
      (*'\n'*) ;;
      (*[![:space:]]*) printf '\n' >&0 || ret=$?;;
      (*) ret=1;; # tail likely failed
    esac
  } 0<> "$file" || ret=$? # record failure to open
done