使用版本控制系统,当差异说出来时我感到很烦恼No newline at end of file
。
所以我想知道:如何在文件末尾添加换行符以消除这些消息?
使用版本控制系统,当差异说出来时我感到很烦恼No newline at end of file
。
所以我想知道:如何在文件末尾添加换行符以消除这些消息?
Answers:
为了递归清理项目,我使用以下单件纸:
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 | while read f; do tail -n1 $f | read -r _ || echo >> $f; done
git ls-files
该模式仍将使您免于编辑版本控制中未跟踪的文件。
IFS=
以取消设置分隔符可以很好地保留周围的空白。仅当您的文件或目录的名称中带有换行符时,以null结尾的条目才有意义,这似乎有些牵强,但是我认为这是处理一般情况的更正确方法。只是一个小警告:POSIX sh中不提供-d
to选项read
。
tail -n1 < "$f"
来避免以-
(tail -n1 -- "$f"
不适用于-
)开头的文件名出现问题。您可能需要澄清答案,现在特定于zsh / bash。
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
man sed
:$ Match the last line.
但也许它只是偶然的作品。您的解决方案也可以。
$
与最后一行匹配,为什么不将另一个换行符添加到已经包含换行符的字符串中呢?
$
。在正则表达式内,例如在形式中/<regex>/
,它具有通常的“匹配行尾”的含义。否则,sed用作地址,它具有特殊的“文件的最后一行”含义。该代码之所以有效,是因为sed默认情况下会在输出中添加换行符(如果尚不存在的话)。代码“ $ a \”仅表示“匹配文件的最后一行,不添加任何内容。” 但隐含地,如果sed $
不存在,则sed会将换行符添加到它处理的每一行(例如此行)。
/regex/
其中具有不同的含义。我认为FreeBSD的联机帮助页内容更丰富:freebsd.org/cgi/man.cgi?
看一看:
$ echo -n foo > foo
$ cat foo
foo$
$ echo "" >> foo
$ cat foo
foo
所以echo "" >> noeol-file
应该做到这一点。(或者您是要请求识别并修复这些文件?)
编辑删除""
从echo "" >> foo
(见@ yuyichao的评论)
EDIT2增加""
再次(但看到@Keith汤普森的评论)
""
不是必需的(至少对于bash而言),并且tail -1 | wc -l
可以用来查找文件而无需在末尾添加新行
""
bash并不是必须的,但是我已经看到echo
没有参数调用时实现不打印任何内容的实现(尽管我现在找不到这些实现)。 echo "" >> noeol-file
可能会更健壮。 printf "\n" >> noeol-file
更是如此。
csh
的echo
是一个当没有通过任何参数已知输出什么。不过,如果我们要支持非类似Bourne外壳,我们应该让echo ''
不是echo ""
为echo ""
将输出中""<newline>
与rc
或es
为实例。
tcsh
,csh
与之不同的是,不带任何参数调用时,它会打印换行符-与的设置无关$echo_style
。
使用的另一种解决方案ed
。此解决方案仅在\n
缺少最后一行时才起作用:
ed -s file <<< w
它实际上是通过脚本打开文件进行编辑的工作,该脚本是单个w
命令,它将文件写回到磁盘。它基于ed(1)
手册页中的以下句子:
局限性 (...) 如果文本(非二进制)文件未以换行符终止, 然后ed在读/写时附加一个。在二进制情况下 文件,ed不会在读/写时添加换行符。
一种简单的,可移植的,与POSIX兼容的方式,可以在文本中添加缺少的最终换行符,即文本文件:
[ -n "$(tail -c1 file)" ] && echo >> file
这种方法不需要读取整个文件;它可以简单地寻求EOF并从那里开始工作。
这种方法也不需要在背后创建临时文件(例如sed -i),因此硬链接不会受到影响。
仅当命令替换的结果为非空字符串时,echo才会在文件中添加换行符。请注意,只有在文件不为空并且最后一个字节不是换行符时,才会发生这种情况。
如果文件的最后一个字节是换行符,tail将其返回,然后命令替换将其删除;结果是一个空字符串。-n测试失败,并且echo无法运行。
如果文件为空,则命令替换的结果也是空字符串,并且再次运行echo。这是理想的,因为空文件不是无效的文本文件,也不等同于带有空行的非空文本文件。
yash
如果文件中的最后一个字符是多字节字符(例如,在UTF-8语言环境中),或者语言环境为C并且文件中的最后一个字节设置了第8位,则该功能将不起作用。对于其他外壳程序(zsh除外),如果文件以NUL字节结尾,则不会添加换行符(但是再次声明,即使添加了换行符,输入也将是非文本的)。
添加换行符,无论:
echo >> filename
这是一种使用Python检查添加新行之前是否存在换行符的方法:
f=filename; python -c "import sys; sys.exit(open(\"$f\").read().endswith('\n'))" && echo >> $f
echo ""
似乎比echo -n '\n'
。或者您可以使用printf '\n'
最快的解决方案是:
[ -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。
如果您需要一个可移植的解决方案(以及上面列出的所有其他外壳),它可能会变得更加复杂:
f=file
if [ "$(tail -c1 "$f"; echo x)" != "$(printf '\nx')" ]
then printf '\n' >>"$f"
fi
测试文件的最后一个字节是否为换行符的最快方法是仅读取该最后一个字节。那可以用完成tail -c1 file
。但是,测试字节值是否为新行的简单方法取决于外壳程序,通常在命令扩展中删除尾随新行时(例如),如果文件中的最后一个字符为UTF-,则在yash中失败。 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工具箱中,od -An -tx1
需要获取一个字节的十六进制值。
您最好对上次编辑该文件的用户的编辑器进行更正。如果您是最后一个编辑文件的人-您使用的是什么编辑器,我猜是textmate ..?
emacs
不要在文件末尾添加换行符。
(setq require-final-newline 'ask)
在.emacs
假设输入中没有空值:
paste - <>infile >&0
...只要总是在一个infile的末尾追加一个换行符就足够了,如果它还没有一个。它只需要读一次输入文件就可以正确处理它。
paste infile 1<> infile
替代。
尽管它不能直接回答问题,但这是我编写的一个相关脚本,用于检测未以换行符结尾的文件。非常快。
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 ...
在vi
/ vim
/ ex
编辑器自动添加<EOL>
在EOF除非文件已经有它。
因此,请尝试:
vi -ecwq foo.txt
等效于:
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
要更正多个文件,请检查:对于许多文件,如何解决“文件末尾没有换行符”的问题?在SO
为什么这如此重要?为了使我们的文件与POSIX兼容。
要将接受的答案应用于当前目录(加上子目录)中的所有文件:
$ find . -type f -exec sed -i -e '$a\' {} \;
这适用于Linux(Ubuntu)。在OS X上,您可能必须使用-i ''
(未试用)。
find .
列出了所有文件,包括中的文件.git
。不包括:find . -type f -not -path './.git/*' -exec sed -i -e '$a\' {} \;
至少在GNU版本中,简单地grep ''
或awk 1
规范化其输入,并添加最后的换行符(如果尚不存在的话)。他们确实会在此过程中复制文件,如果文件很大,则会花费一些时间(但是源代码应该不会太大而无法读取吗?)并更新modtime,除非您执行类似的操作
mv file old; grep '' <old >file; touch -r old file
(尽管在您签入的文件上,因为修改了文件,这可能没问题),除非您更加小心,否则它将丢失硬链接,非默认权限和ACL等。
grep '' file 1<> file
,尽管那样仍然可以完全读写文件。
这在AIX ksh中有效:
lastchar=`tail -c 1 *filename*`
if [ `echo "$lastchar" | wc -c` -gt "1" ]
then
echo "/n" >> *filename*
fi
就我而言,如果文件缺少换行符,该wc
命令将返回值,2
然后我们编写一个换行符。
添加到Patrick Oscity的答案中,如果您只想将其应用于特定目录,则还可以使用:
find -type f | while read f; do tail -n1 $f | read -r _ || echo >> $f; done
在您要添加换行符的目录中运行此命令。
如果文件以Windows行结尾终止,\r\n
并且您在Linux中,则可以使用此sed
命令。\r\n
如果尚未添加到最后一行,它只会添加到最后一行:
sed -i -e '$s/\([^\r]\)$/\1\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
\1\r\n replace it with itself and add \r\n
如果最后一行已经包含a,\r\n
则搜索正则表达式将不匹配,因此将不会发生任何事情。
您可以编写如下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
与这里给出的一些解决方案相反,它
您可以将其用作例如:
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