如何在文件结尾处删除换行符?

181

我有一些文件,如果文件的最后一个字符是换行符,我希望将其删除。通过运行od -c命令,我可以看到该命令确实会在文件的末尾添加一个换行符:

0013600   n   t  >  \n

我尝试了几种用sed的技巧,但我能想到的最好的方法并没有起作用:

sed -e '$s/\(.*\)\n$/\1/' abc

有什么办法可以做到这一点吗?

4
Unix换行符只有一个字符,而DOS换行符则是两个字符。当然,字面上的“\ n”也是两个字符。你实际上要找哪个? - Dennis Williamson
3
尽管表示为“\n”,但在Linux中它只是一个字符。 - pavium
10
你能详细说明为什么想这么做吗?文本文件应该以行尾结束,除非它们完全为空。我觉得你想要这样一个被截断的文件很奇怪? - Thomas Padron-McCarthy
请勿删除以换行符结尾的文件中的最后一个换行符。这会导致各种问题。 - tchrist
10
“在计算机领域,每当有一个充分的理由去做某件事时,就存在着一个同样充分的理由不去做;反之亦然。” - 耶稣 -- “你不应该这样做”是一个可怕的回答,无论问题是什么。正确的格式是:[如何做] 但是 [为什么它可能是个坏主意]。 #亵渎 - Cory Mawhorter
显示剩余3条评论
23个回答

241
perl -pe 'chomp if eof' filename >filename2

或者,要原地编辑文件:

perl -pi -e 'chomp if eof' filename

[编辑注:原本是 -pie,但是多位评论者指出这个命令不可行,在 @hvd 的解释下得知此处应为 -pi -e。]

我在 awk 网站上看到过这被称为 'perl 渎神'。

但是,在测试中它起作用了。


11
使用 chomp 可以使其更安全。而且这比把文件 slurping 要好。 - Sinan Ünür
6
虽然这是亵渎,但它却非常有效。perl -i -pe 'chomp if eof' 文件名。谢谢。 - Todd Partridge 'Gen2ly'
14
亵渎和异端的有趣之处在于它通常因为正确而被憎恨。 :) - Ether
8
小修正:您可以使用 perl -pi -e 'chomp if eof' 文件名 来原地编辑文件,而无需创建临时文件。 - Romuald Brunet
8
perl -pie 'chomp if eof' filename 翻译为:无法打开 perl 脚本 "chomp if eof":没有这个文件或目录; perl -pi -e 'chomp if eof' filename 翻译为:有效。 - aditsu quit because SE is EVIL
显示剩余7条评论

68
你可以利用shell 命令替换 去掉尾部的换行符:
适用于bash,ksh,zsh的简单形式:
printf %s "$(< in.txt)" > out.txt

便携式(符合POSIX标准)替代方案(效率略低):

printf %s "$(cat in.txt)" > out.txt

注意:

其他答案指南:

如果有可用的Perl,那么选择accepted answer - 它是简单且内存高效的(不会一次性读取整个输入文件)。否则,请考虑ghostdog74的Awk答案 - 它很难懂,但也是内存高效的;一个更易读的等价物(符合POSIX标准)是:awk 'NR>1 {print prev} {prev=$0} END {ORS=""; print}' in.txt。打印被延迟一行,以便可以在END块中处理最后一行,在该块中由于将输出记录分隔符(OFS)设置为空字符串而不带有尾随的\n进行打印。如果您想要一个详细的、快速和健壮的解决方案,真正地进行原地编辑(而不是创建一个临时文件,然后替换原始文件),请考虑jrockway的Perl脚本

57

您可以使用GNU coreutils中的head命令来实现此操作,它支持相对于文件末尾的参数。因此,要去掉最后一个字节,请使用:

head -c -1

要测试是否存在结束换行符,可以使用tailwc。以下示例将结果保存到临时文件中,然后覆盖原始文件:

if [[ $(tail -c1 file | wc -l) == 1 ]]; then
  head -c -1 file > file.tmp
  mv file.tmp file
fi
你也可以使用moreutils中的来进行“原地”编辑:
[[ $(tail -c1 file | wc -l) == 1 ]] && head -c -1 file | sponge file

你还可以将这个代码插入到你的.bashrc文件中,创建一个通用的可重复使用函数:

# Example:  remove-last-newline < multiline.txt
function remove-last-newline(){
    local file=$(mktemp)
    cat > $file
    if [[ $(tail -c1 $file | wc -l) == 1 ]]; then
        head -c -1 $file > $file.tmp
        mv $file.tmp $file
    fi
    cat $file
}

更新

正如评论中KarlWilbur所提到并在Sorentar的答案中使用的那样,truncate --size=-1可以替换head -c-1并支持原地编辑。


4
到目前为止最好的解决方案。使用标准工具,几乎每个Linux发行版都有,简洁明了,没有任何sed或perl的技巧。 - Dakkaron
3
不错的解决方案。我认为有一个改变,我会使用 truncate --size=-1 而不是 head -c -1,因为它只调整输入文件的大小而不是读取输入文件,将其写入另一个文件,然后用输出文件替换原始文件。 - Karl Wilbur
4
请注意,head -c -1 会删除最后一个字符,无论它是否为换行符,因此您需要在删除之前检查最后一个字符是否为换行符。 - wisbucky
1
不幸的是,在 Mac 上无法工作。我怀疑它在任何 BSD 变体上都无法工作。 - Edward Falk

19
head -n -1 abc > newfile
tail -n 1 abc | tr -d '\n' >> newfile

编辑2:

这里是一个已经纠正过的awk版本,它不会累积可能很大的数组:

awk '{if (line) print line; line=$0} END {printf $0}' abc


很好的原始思考方式。谢谢Dennis。 - Todd Partridge 'Gen2ly'
你可以使用进程替换将输出作为管道进行处理: head -n -1 abc | cat <(tail -n 1 abc | tr -d '\n') | ... - BCoates
这个在我1MB文件上比perl命令运行得更快。非常感谢! - hese
2
使用-c而不是-n来使用head和tail命令应该会更快。 - rudimeier
1
对我来说,head -n -1 abc 命令删除了文件的最后一行,但是留下了一个尾随的换行符;而 head -c -1 abc 命令似乎效果更好。 - ChrisV
显示剩余4条评论

11

gawk

awk '{q=p;p=$0}NR>1{print q}END{ORS = ""; print p}' file

对我来说,这仍然是很多字符...我正在慢慢学习它 :). 但它确实能胜任工作。谢谢ghostdog。 - Todd Partridge 'Gen2ly'
1
awk '{ prev_line = line; line = $0; } NR > 1 { print prev_line; } END { ORS = ""; print line; }' file 这样应该更容易阅读。 - Yevhen Pavliuk
如何使用以下命令:awk 'NR>1 {print p} {p=$0} END {printf $0}' file - user8017719
@sorontar printf 的第一个参数是“格式”参数。因此,如果输入文件中有类似于 %d 的格式说明符,就会出现错误。修复方法是将其更改为 printf "%s" $0 - Robin A. Meade

9

一种快速的解决方案是使用gnu实用程序truncate:

[ -z $(tail -c1 file) ] && truncate -s-1 file

如果文件末尾存在换行符,则测试结果为真。

删除非常快速,真正地就地进行,无需新文件,并且搜索也只读取一个字节的末尾(tail -c1)。


1
截断:缺少文件操作数。 - Brian Hannay
2
只是示例中缺少尾部文件名,即 [ -z $(tail -c1 filename) ] && truncate -s -1 filename(另外,针对其他评论,truncate 命令不能使用 stdin,需要提供文件名)。 - michael

8

针对单行文件的一种非常简单的方法,需要使用核心工具包中的GNU echo:

/bin/echo -n $(cat $file)

如果不太昂贵(重复),这是一种不错的方法。 - user4401178
\n存在时,这会出现问题。因为它会被转换成一个新行。 - Chris Stryczynski
似乎也适用于多行文件,只要引号中包含 $(...) - Thor
一定要引用这个... /bin/echo -n "$(cat infile)" 另外,我不确定 echo 或 shell 在不同的操作系统、shell 版本或发行版中的最大长度是多少(我只是在谷歌上搜索了一下,结果陷入了一个兔子洞),所以我不确定它除了小文件之外是否具有可移植性(或性能)--但对于小文件来说,非常好。 - michael

7
如果您想要做得正确,您需要像这样的东西:
use autodie qw(open sysseek sysread truncate);

my $file = shift;
open my $fh, '+>>', $file;
my $pos = tell $fh;
sysseek $fh, $pos - 1, 0;
sysread $fh, my $buf, 1 or die 'No data to read?';

if($buf eq "\n"){
    truncate $fh, $pos - 1;
}

我们打开文件以进行读取和追加;打开追加意味着我们已经使用seek移动到了文件的末尾。然后,我们使用tell获取文件末尾的数字位置。我们使用该数字向后移动一个字符,然后读取该字符。如果它是换行符,则将文件截断为该换行符之前的字符,否则不执行任何操作。
对于任何输入,此过程都在恒定的时间和空间内运行,并且不需要更多的磁盘空间。

2
但这种方法的缺点是不会重置文件的所有权/权限...等等... - ysth
1
冗长,但快速且强大 - 似乎是这里唯一真正的原地文件编辑答案(由于可能不是每个人都明显:这是一个Perl脚本)。 - mklement0

6
这里是一个整洁的Python解决方案。我没有尝试压缩代码。
这个解决方案直接修改文件,而不是复制一份该文件并从复制的最后一行中去掉换行符。如果文件很大,这将比选择为最佳答案的Perl解决方案快得多。
如果文件的最后两个字节是CR/LF,则它会将文件截断两个字节,如果最后一个字节是LF,则将文件截断一个字节。如果最后一个字节不是(CR)LF,则不会尝试修改文件。它可处理错误。在Python 2.6中进行了测试。
将此放入名为“striplast”的文件中,并使用命令 chmod + x striplast 赋予其执行权限。
#!/usr/bin/python

# strip newline from last line of a file


import sys

def trunc(filename, new_len):
    try:
        # open with mode "append" so we have permission to modify
        # cannot open with mode "write" because that clobbers the file!
        f = open(filename, "ab")
        f.truncate(new_len)
        f.close()
    except IOError:
        print "cannot write to file:", filename
        sys.exit(2)

# get input argument
if len(sys.argv) == 2:
    filename = sys.argv[1]
else:
    filename = "--help"  # wrong number of arguments so print help

if filename == "--help" or filename == "-h" or filename == "/?":
    print "Usage: %s <filename>" % sys.argv[0]
    print "Strips a newline off the last line of a file."
    sys.exit(1)


try:
    # must have mode "b" (binary) to allow f.seek() with negative offset
    f = open(filename, "rb")
except IOError:
    print "file does not exist:", filename
    sys.exit(2)


SEEK_EOF = 2
f.seek(-2, SEEK_EOF)  # seek to two bytes before end of file

end_pos = f.tell()

line = f.read()
f.close()

if line.endswith("\r\n"):
    trunc(filename, end_pos)
elif line.endswith("\n"):
    trunc(filename, end_pos + 1)

在“Perl高尔夫”的精神下,这是我最短的Python解决方案。它将整个文件从标准输入中读入内存,去掉末尾的所有换行符,并将结果写入标准输出流。虽然不如Perl那样简洁,但在处理这种小而棘手的任务时,你真的无法超越Perl。
从调用.rstrip()中删除“\n”,它将删除文件末尾的所有空格,包括多个空行。
将此放入“slurp_and_chomp.py”中,然后运行“python slurp_and_chomp.py outputfile”。
import sys

sys.stdout.write(sys.stdin.read().rstrip("\n"))

os.path.isfile() 会告诉你文件是否存在。使用 try/except 可能会捕获许多不同的错误 :) - Denis Barmenkov

5

又一个关于Perl的WTDI:

perl -i -p0777we's/\n\z//' filename

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接