如果换行符是文件中的最后一个字符,如何删除换行符?

Tod*_*ly' 154 linux shell perl awk sed

我有一些文件,如果它是文件中的最后一个字符,我想删除最后一个换行符. od -c告诉我,我运行的命令确实用一个尾随的新行写了一个文件:

0013600   n   t  >  \n
Run Code Online (Sandbox Code Playgroud)

我已经尝试了一些与sed的技巧,但我能想到的最好的不是诀窍:

sed -e '$s/\(.*\)\n$/\1/' abc
Run Code Online (Sandbox Code Playgroud)

任何想法如何做到这一点?

pav*_*ium 213

perl -pe 'chomp if eof' filename >filename2
Run Code Online (Sandbox Code Playgroud)

或者,编辑文件:

perl -pi -e 'chomp if eof' filename
Run Code Online (Sandbox Code Playgroud)

[编者注:-pi -e最初是-pie,但正如几位评论者所指出并由@hvd解释,后者不起作用.]

这在我看到的awk网站上被描述为"perl blasphemy".

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

  • 关于亵渎和异端邪说的有趣之处在于它通常被人讨厌,因为它是正确的.:) (12认同)
  • 你可以使用`chomp`使它更安全.它打败了文件. (11认同)
  • 小修正:你可以使用`perl -pi -e'chomp if eof'filename`,就地编辑文件而不是创建一个临时文件 (8认同)
  • `perl -pie'chomp if eof'filename` - >无法打开perl脚本"chomp if eof":没有这样的文件或目录; `perl -pi -e'chomp if eof'filename` - >有效 (7认同)
  • 亵渎神明虽然如此,但效果很好.perl -i -pe'chomp if eof'文件名.谢谢. (6认同)
  • @KyleStrand我不知道"亵渎"部分,除了推荐perl的事实可能被认为是在awk网站上的亵渎,但是`-pie`和`-pi -e`的原因不起作用同样的方式是`-i`选项采用可选参数.`-pie`使用`e`作为`-i`的参数,指定备份后缀,然后将`'chomp if eof'`解释为文件名,因为它前面没有`-e`选项.`-pi -e`省略了`-i`的参数,并允许`-e`被视为一个选项. (3认同)
  • 不是所有的 Perl 都在亵渎吗?;) (2认同)

mkl*_*nt0 54

您可以利用shell 命令替换删除尾随换行符的事实:

在bash,ksh,zsh中工作的简单形式:

printf %s "$(< in.txt)" > out.txt
Run Code Online (Sandbox Code Playgroud)

便携式(POSIX兼容)替代方案(效率略低):

printf %s "$(cat in.txt)" > out.txt
Run Code Online (Sandbox Code Playgroud)

注意:


一个人工引导到另一个答案:

  • 如果Perl可用,请选择接受的答案 - 它简单且内存效率高(不会立即读取整个输入文件).

  • 否则,考虑一下ghostdog74的Awk答案 - 它模糊不清,而且内存效率高 ; 一个更可读的等效(POSIX标准)为:

    • awk 'NR > 1 { print prev } { prev=$0 } END { ORS=""; print }' in.txt
    • 打印延迟一行,以便最后一行可以在END块中处理,\n由于将输出记录separator(OFS)设置为空字符串,因此打印时没有尾随.
  • 如果你想要一个简单但快速而强大的解决方案,它可以就地编辑(而不是创建一个替换原始文件的临时文件),请考虑使用jrockway的Perl脚本.

  • 注意如果文件末尾有多个换行符,则此命令将删除所有换行符. (3认同)

Tho*_*hor 45

您可以使用headGNU coreutils 执行此操作,它支持相对于文件末尾的参数.所以要留下最后一个字节使用:

head -c -1
Run Code Online (Sandbox Code Playgroud)

要测试结束换行符,您可以使用tail和wc.以下示例将结果保存到临时文件,然后覆盖原始文件:

if [[ $(tail -c1 file | wc -l) == 1 ]]; then
  head -c -1 file > file.tmp
  mv file.tmp file
fi
Run Code Online (Sandbox Code Playgroud)

您还可以使用spongefrom moreutils进行"就地"编辑:

[[ $(tail -c1 file | wc -l) == 1 ]] && head -c -1 file | sponge file
Run Code Online (Sandbox Code Playgroud)

您还可以通过将其填充到.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
}
Run Code Online (Sandbox Code Playgroud)

更新

正如KarlWilbur在评论中所指出并用于Sorentar的回答,truncate --size=-1可以替换head -c-1并支持就地编辑.

  • 迄今为止最好的解决方案.使用一个真正的每个Linux发行版都具有的标准工具,并且简洁明了,没有任何sed或perl魔法. (3认同)
  • 请注意,`head -c -1` 将删除最后一个字符,不管它是否是换行符,这就是为什么你必须在删除它之前检查最后一个字符是否是换行符。 (3认同)
  • 不错的解决方案。一个变化是,我认为我将使用`truncate --size = -1`而不是`head -c -1`,因为它只是调整输入文件的大小,而不是读入输入文件,将其写到另一个文件中,然后将原始文件替换为输出文件。 (2认同)

Pau*_*ce. 16

head -n -1 abc > newfile
tail -n 1 abc | tr -d '\n' >> newfile
Run Code Online (Sandbox Code Playgroud)

编辑2:

这是一个awk版本(已更正),不会累积一个潜在的巨大数组:

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

  • 使用-c代替头部和尾部的-n应该更快. (2认同)

gho*_*g74 10

呆子

   awk '{q=p;p=$0}NR>1{print q}END{ORS = ""; print p}' file
Run Code Online (Sandbox Code Playgroud)


ImH*_*ere 9

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

[ -z $(tail -c1 file) ] && truncate -s-1 file
Run Code Online (Sandbox Code Playgroud)

如果文件有尾随换行符,则测试为真。

删除非常快,真正到位,不需要新文件,搜索也是从末尾读取一个字节 ( tail -c1)。

  • 它只是缺少示例中的尾随文件名,即`[ -z $(tail -c1 filename) ] &amp;&amp; truncate -s -1 filename`(另外,在回复其他评论时,`truncate` 命令不起作用使用标准输入,需要文件名) (2认同)

jro*_*way 8

如果你想做得对,你需要这样的东西:

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;
}
Run Code Online (Sandbox Code Playgroud)

我们打开文件进行阅读和追加; 打开附加意味着我们已经seek编辑到文件的末尾.然后我们得到文件末尾的数字位置tell.我们使用该数字来寻找一个字符,然后我们读取一个字符.如果是换行符,我们会在该换行符之前将文件截断为字符,否则,我们什么都不做.

对于任何输入,它都以恒定的时间和恒定的空间运行,并且不需要任何更多的磁盘空间.

  • 但是这样做的缺点是不能重置文件的所有权/权限......错误,等等...... (2认同)

小智 8

单行文件的一种非常简单的方法,需要来自coreutils的GNU echo:

/bin/echo -n $(cat $file)
Run Code Online (Sandbox Code Playgroud)


yst*_*sth 5

另一个 perl WTDI:

perl -i -p0777we's/\n\z//' filename
Run Code Online (Sandbox Code Playgroud)


ste*_*eha 5

这是一个漂亮,整洁的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)
Run Code Online (Sandbox Code Playgroud)

PS本着"Perl高尔夫"的精神,这是我最短的Python解决方案.它将整个文件从标准输入篡改到内存中,从最后删除所有换行符,并将结果写入标准输出.不像Perl那样简洁; 你只是无法击败Perl这样的小巧琐事.

从调用中删除"\n" .rstrip(),它将从文件末尾删除所有空格,包括多个空行.

把它放入"slurp_and_chomp.py"然后运行python slurp_and_chomp.py < inputfile > outputfile.

import sys

sys.stdout.write(sys.stdin.read().rstrip("\n"))
Run Code Online (Sandbox Code Playgroud)