【发布时间】:2011-03-16 18:30:25
【问题描述】:
我有大量的源文件,最后都缺少换行符。
我如何自动在它们的末尾添加一个换行符?
有些可能已经有了换行符,所以只应在必要时添加。
我本身可能不是在寻找代码,而只是我可以在终端中运行以添加必要的换行符(或某种编程或开发工具)的东西。
【问题讨论】:
我有大量的源文件,最后都缺少换行符。
我如何自动在它们的末尾添加一个换行符?
有些可能已经有了换行符,所以只应在必要时添加。
我本身可能不是在寻找代码,而只是我可以在终端中运行以添加必要的换行符(或某种编程或开发工具)的东西。
【问题讨论】:
为方便起见,将 Norman 的答案转换为拆分单行。
for i in * ; do echo $i; \
if diff /dev/null "$i" | tail -1 | \
grep '^\\ No newline' > /dev/null; then echo >> "$i"; \
fi; done
用你想要的任何文件模式替换*,例如*.c
另一个只是告诉您哪些文件已损坏:
for i in * ; do \
if diff /dev/null "$i" | tail -1 | \
grep '^\\ No newline' > /dev/null; then echo $i; \
fi; done
【讨论】:
*换成$(find . -type f)或者$(find <dirname> -type f -name <filepattern>)
如果您可以使用 Unix 工具,则可以运行 diff 来找出哪些文件缺少换行符,然后追加:
#!/bin/sh
for i
do
if diff /dev/null "$i" | tail -1 | grep '^\\ No newline' > /dev/null
then
echo >> "$i"
fi
done
我依靠diff 生成第一列中带有\ 的消息,tail 给我diff 输出的最后一行,grep 告诉我是否最后一行是我正在寻找的消息。如果一切正常,那么echo 会生成一个换行符,>> 会将其附加到文件"$i"。 "$i" 周围的引号确保如果文件名中有空格,一切仍然有效。
【讨论】:
tail -1 $f | grep '\n' 作为条件(适用于我的盒子)。
对文件末尾“缺少”换行符的文件的简单修复是简单的 sed;以下修复了文件“就地”(使用“-i”选项):
find . -type f -exec sed -i -e '$a\' {} \; -print
解释:找到所有文件(-type f),运行sed,就地更改文件(-i),给定以下(-e)脚本/表达式,匹配文件末尾($),并执行“追加”操作 (a\),但实际上并没有指定要追加的任何文本(\ 之后没有任何内容),这将在文件末尾添加一个换行符,但前提是它丢失了。打印找到的所有文件(修复或未修复),这可能是不必要的。
主要警告是sed 的功能因平台而异,因此-i 和-e 可能支持也可能不支持/相同;例如较旧的 Unix 或 MacOS 的古怪可能需要稍微不同的语法。
【讨论】:
-prune 或 @ 987654333@ 到find 命令省略.git/ 目录。否则,你会破坏它。
sed。由于.git 是(通常)仅在根目录中找到的点文件,一个简单的解决方案是将find . -type f... 更改为find * -type f ...。但无论如何,如果有任何二进制文件的机会,要么根据需要定制find,要么根本不使用find。
好的,在cmets中抱怨之后,有我更好的解决方案。 首先,您想知道,哪些文件缺少换行符:
find -type f -exec sh -c "tail -1 {} | xxd -p | tail -1 | grep -v 0a$" ';' -print
不是超级快(为每个文件调用几个进程),但实际使用还可以。
现在,当你拥有它时,你也可以添加换行符,再加上另一个-exec:
find -type f -exec sh -c "tail -1 {} | xxd -p | tail -1 | grep -v 0a$" ';' -exec sh -c "echo >> {}" ';'
可能的陷阱:
如果文件名错误,例如他们有空格,你可能需要tail -1 \"{}\"。
还是 find 做对了?
您可能想要添加更多过滤来查找,例如-name \*py 等。
在使用前考虑可能的 DOS/Unix 换行符混乱(先解决这个问题)。
编辑:
如果您不喜欢这些命令的输出(回显一些十六进制),请将 -q 添加到 grep:
find -type f -exec sh -c "tail -1 {} | xxd -p | tail -1 | grep -q -v 0a$" ';' -print
find -type f -exec sh -c "tail -1 {} | xxd -p | tail -1 | grep -q -v 0a$" ';' -exec sh -c "echo >> {}" ';'
【讨论】:
尝试前路:
ex -s +"bufdo wq" *.c
并递归(启用a new globbing option):
ex -s +"bufdo wq" **/*.c
这相当于vi -es。将*.c 更改为您感兴趣的扩展名。
如果ex/vi 不存在,则会在保存时自动附加换行符。
【讨论】:
我很惊讶没有人提到像 Awk 这样的许多简单的文本处理工具会添加换行符作为副作用。这是一个简单的循环,只有在实际添加了换行符时才会覆盖文件。
for f in *; do
awk 1 "$f" >tmp
cmp -s tmp "$f" || mv tmp "$f"
done
rm -f tmp
(这个临时文件显然有点小问题。)
IDEone 演示:http://ideone.com/HpRHcx
【讨论】:
find -type f | while read f; do [[ `tail -c1 "$f"` ]] && echo >> "$f"; done
我使用find 而不是for f in *,因为它是递归的,问题是关于“大量源文件”。
出于性能原因,我使用while read 而不是find -exec 或xargs,它每次都节省了生成shell 进程。
我正在利用反引号运算符正在返回“删除任何尾随换行符”man bash 的命令输出这一事实,因此对于正确终止的文件,反引号将为空并且将跳过回显。
find | read 对包含换行符的文件名将失败,但如果需要,很容易修复:
find -type f -print0 | while read -d $'\0' f; do [[ `tail -c1 "$f"` ]] && echo >> "$f"; done
【讨论】:
以下是我的 bash 脚本解决方案。它首先检查文件是否为文本文件。然后,如果它是一个文本文件,它使用 tail 和 od(八进制转储)来查看最后一个字符是否是换行符。如果不是,则使用 echo 追加一个换行符:
item="$1"
if file "$item" | egrep '\btext\b' > /dev/null
then
if ! tail -c 1 "$item" | od -b -A n | egrep '\b012\b' > /dev/null
then
echo "(appending final newline to ${item})"
echo >> "$item"
fi
fi
【讨论】:
由于命令本地化,Tim 和 Norman 的回答应使用“LANG=C”前缀进行改进,以便有机会与具有任何区域参数的每个系统匹配“无换行符”模式
这确保了该脚本命令行中每个文件的结尾都是空行:
#!/bin/sh -f
for i in $* ; do echo $i; \
if LANG=C diff /dev/null "$i" | tail -1 | \
grep '^\\ No newline' > /dev/null; then echo >> "$i"; \
fi; done
这个脚本会检测到缺少它的文件:
#!/bin/sh -f
for i in $* ; do \
if LANG=C diff /dev/null "$i" | tail -1 | \
grep '^\\ No newline' > /dev/null; then echo $i; \
fi; done
【讨论】:
找到工具后,没有运气就完成这项工作。我决定自己写
这是我的 python 脚本来完成这项工作
它只在文件末尾附加(\r\n)而不包含(\n)
https://github.com/tranhuanltv/append_newline
用法:append_newline.py .c ./projects ./result_dir
如果你愿意,可以提出拉取请求
【讨论】:
pcregrep --recursive --exclude-dir=.git \
--files-without-match --multiline '\n\z' . |
while read k ; do echo >> "$k"; done
这里涉及几个步骤:
步骤 1 传统上使用 find 完成(遵循 Unix 的传统
“每个工具都做一件事并且做得很好”),但是由于 pcregrep 具有内置支持,所以我很乐意使用它。我小心避免弄乱 .git 文件夹。
步骤 2 使用多行正则表达式匹配 有最后一个换行符的文件,并打印 不 匹配的文件的名称。
第 3 步是使用 while/read 循环而不是 for/in 完成的,因为后者对于带有空格的文件名和极长的文件列表会失败。
第 4 步是一个简单的回声,遵循@norman-ramsey 的方法。
h/t @anthony-bush https://stackoverflow.com/a/20687956/577438 用于 pcregrep 建议。
【讨论】: