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

时间:2009-10-31 10:42:19

标签: linux perl shell awk sed

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

0013600   n   t  >  \n

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

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

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

22 个答案:

答案 0 :(得分:206)

perl -pe 'chomp if eof' filename >filename2

或者,要编辑文件:

perl -pi -e 'chomp if eof' filename

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

在我看到的awk网站上,这被描述为“perl blasphemy”。

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

答案 1 :(得分:52)

您可以利用 shell command substitutions删除尾随换行符的事实:

在bash,ksh,zsh中运行的简单形式:

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

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

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

注意:


其他答案的指南

  • 如果 Perl 可用,请转到accepted answer - 简单且内存效率高(不读取整个输入文件一次)。

  • 否则,请考虑ghostdog74's Awk answer - 它是模糊,但也是内存效率;一个更具可读性的等价物(POSIX兼容)是:

    • awk 'NR > 1 { print prev } { prev=$0 } END { ORS=""; print }' in.txt
    • 打印延迟一行,以便在END块中处理最后一行,由于设置了输出记录分隔符({{1},因此在打印时没有尾随\n }})到一个空字符串。
  • 如果您想要一个详细但快速且强大的解决方案真正就地编辑 (而不是创建一个临时文件,然后替换原文),请考虑jrockway's Perl script

答案 2 :(得分:44)

您可以使用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

您还可以使用sponge中的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's answer中使用,truncate --size=-1可以取代head -c-1并支持就地编辑。

答案 3 :(得分:16)

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

答案 4 :(得分:10)

gawk的

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

答案 5 :(得分: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;
}

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

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

答案 6 :(得分:8)

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

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

答案 7 :(得分: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)

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

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

将其放入“slurp_and_chomp.py”,然后运行python slurp_and_chomp.py < inputfile > outputfile

import sys

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

答案 8 :(得分:4)

另一个perl WTDI:

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

答案 9 :(得分:3)

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

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

如果文件确实有一个尾随的新行,则测试将为真。

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

答案 10 :(得分:3)

$  perl -e 'local $/; $_ = <>; s/\n$//; print' a-text-file.txt

另见Match any character (including newlines) in sed

答案 11 :(得分:2)

使用dd:

file='/path/to/file'
[[ "$(tail -c 1 "${file}" | tr -dc '\n' | wc -c)" -eq 1 ]] && \
    printf "" | dd  of="${file}" seek=$(($(stat -f "%z" "${file}") - 1)) bs=1 count=1
    #printf "" | dd  of="${file}" seek=$(($(wc -c < "${file}") - 1)) bs=1 count=1

答案 12 :(得分:2)

假设Unix文件类型,你只想要最后一个换行符。

sed -e '${/^$/d}'

它不适用于多个换行符......

* 仅当最后一行为空行时才有效。

答案 13 :(得分:2)

perl -pi -e 's/\n$// if(eof)' your_file

答案 14 :(得分:1)

另一个答案FTR(和我最喜欢的!):echo / cat你要剥离的东西并通过反引号捕获输出。最终换行将被删除。例如:

# Sadly, outputs newline, and we have to feed the newline to sed to be portable
echo thingy | sed -e 's/thing/sill/'

# No newline! Happy.
out=`echo thingy | sed -e 's/thing/sill/'`
printf %s "$out"

# Similarly for files:
file=`cat file_ending_in_newline`
printf %s "$file" > file_no_newline

答案 15 :(得分:1)

POSIX SED:

'${/^$/d}'

$ - match last line


{ COMMANDS } - A group of commands may be enclosed between { and } characters. This is particularly useful when you want a group of commands to be triggered by a single address (or address-range) match.

答案 16 :(得分:0)

sed -n "1 x;1 !H
$ {x;s/\n*$//p;}
" YourFile

应删除文件中最后一次出现的\ n。不处理大文件(由于发送缓冲区限制)

答案 17 :(得分:0)

红宝石:

ruby -ne 'print $stdin.eof ? $_.strip : $_'

或:

ruby -ane 'q=p;p=$_;puts q if $.>1;END{print p.strip!}'

答案 18 :(得分:0)

我有类似的问题,但是正在使用Windows文件,需要保留那些CRLF - 我的解决方案在linux上:

sed 's/\r//g' orig | awk '{if (NR>1) printf("\r\n"); printf("%s",$0)}' > tweaked

答案 19 :(得分:0)

sed ':a;/^\n*$/{$d;N;};/\n$/ba' file

答案 20 :(得分:0)

我唯一想要这样做的是代码高尔夫,然后我只是将我的代码复制出文件并粘贴到echo -n 'content'>file语句中。

答案 21 :(得分:0)

如果您需要它与管道/重定向一起使用,而不是从文件中读取/输出到文件,则这是一个很好的解决方案。这适用于单行或多行。无论是否有尾随换行符,它都有效。

# with trailing newline
echo -en 'foo\nbar\n' | sed '$s/$//' | head -c -1

# still works without trailing newline
echo -en 'foo\nbar' | sed '$s/$//' | head -c -1

# read from a file
sed '$s/$//' myfile.txt | head -c -1

详细信息:

  • head -c -1会截断字符串的最后一个字符,无论该字符是什么。因此,如果字符串不以换行符结尾,那么您将丢失一个字符。
  • 因此,为解决该问题,我们添加了另一个命令,如果没有一个命令,该命令将添加尾随换行符:sed '$s/$//'。前$表示仅将命令应用于最后一行。 s/$//表示将“行尾”替换为“什么都不做”,这实际上什么也没做。但是,如果没有尾随的换行符,则会产生副作用。

注意:Mac的默认head不支持-c选项。您可以执行brew install coreutils,而改用ghead