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


162

我有一些文件要删除最后一个换行符(如果它是文件中的最后一个字符)。 od -c告诉我,我运行的命令确实以尾随新行写入文件:

0013600   n   t  >  \n

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

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

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


4
对于UNIX换行符,换行符仅是一个字符。DOS换行符是两个字符。当然,文字“ \ n”是两个字符。您实际上在寻找什么?
暂停,直到另行通知。

3
尽管表示可能是\n,但是在Linux中是一个字符
pavium

10
您能否详细说明为什么要这样做?文本文件以行尾结尾,除非它们完全为空。我想拥有这样一个被截断的文件对我来说似乎很奇怪?
Thomas Padron-McCarthy

通常的原因做一些像这样的是一个CSV文件的最后一行删除尾随逗号。Sed运作良好,但换行符必须区别对待。
pavium

9
@ ThomasPadron-McCarthy“在计算中,有充分的理由要做某事,但有充分的理由不这样做,反之亦然。” -耶稣-无论问题如何,“你都不应该那样做”是一个可怕的答案。正确的格式是:[如何执行],但是[为什么可能不是个好主意]。#sacrilege
Cory Mawhorter 2015年

Answers:


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

或者,就地编辑文件:

perl -pi -e 'chomp if eof' filename

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

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

但是,在测试中,它奏效了。


11
您可以使用来使其更安全chomp。并且击败文件。
锡南·努尔

6
虽然是亵渎神明,但效果很好。perl -i -pe'如果eof则加'chomp'文件名。谢谢。
Todd Partridge'Gen2ly'09-10-31

13
关于亵渎和异端的有趣的事情是它通常是讨厌的,因为它是正确的。:)
以太(Ether)

8
较小的更正:您可以使用perl -pi -e 'chomp if eof' filename来就地编辑文件,而不是创建临时文件
Romuald Brunet 2012年

7
perl -pie 'chomp if eof' filename->无法打开perl脚本“如果eof,则显示chomp”:没有这样的文件或目录;perl -pi -e 'chomp if eof' filename->
可行

56

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

适用于bash,ksh,zsh的简单形式:

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

便携式(兼容POSIX)(效率略低):

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

注意:


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

  • 如果Perl可用,请寻求公认的答案 -它简单且内存高效(不会一次读取整个输入文件)。

  • 否则,请考虑ghostdog74的Awk答案 - 晦涩难懂,但内存效率高;一个更可读的当量(POSIX兼容)为:

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


3
注意:如果文件末尾有多个换行符,则此命令将删除所有换行符。
Sparhawk

47

您可以head通过GNU coreutils使用此功能,它支持相对于文件末尾的参数。因此,离开最后一个字节使用:

head -c -1

要测试结尾的换行符,可以使用tailwc。下面的示例将结果保存到一个临时文件,然后覆盖原始文件:

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

您还可以使用spongefrom 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并支持就地编辑。


3
迄今为止最好的解决方案。使用实际上每个Linux发行版都具有的标准工具,简洁明了,没有任何sed或perl向导。
达卡龙2015年

2
不错的解决方案。一个变化是,我认为我会使用,truncate --size=-1而不是head -c -1因为它只是调整输入文件的大小,而不是读入输入文件,将其写到另一个文件,然后用输出文件替换原始文件。
Karl Wilbur

1
请注意,head -c -1无论最后一个字符是否为换行符,都将删除它,这就是为什么您必须在删除最后一个字符之前先检查它是否为换行符。
wisbucky

不幸的是在Mac上不起作用。我怀疑它不适用于任何BSD变体。
爱德华·福尔克

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

编辑2:

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

awk'{if(line)打印行;line = $ 0} END {printf $ 0}'abc


考虑它的好原始方法。谢谢丹尼斯。
Todd Partridge'Gen2ly'09-10-31

你是对的。我遵照您的awk版本。它需要两个偏移量(和另一个测试),而我只使用了一个。但是,您可以使用printf代替ORS
暂停,直到另行通知。

您可以使输出成为具有流程替换的管道:head -n -1 abc | cat <(tail -n 1 abc | tr -d '\n') | ...
BCoates 2012年

2
对于头和尾使用-c而不是-n应该更快。
rudimeier

1
对我而言,head -n -1 abc删除了文件的最后实际行,并留下了结尾的换行符;头-c -1 abc似乎工作得更好
ChrisV 2014年

10

高克

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

对我来说,仍然看起来像很多角色...慢慢学习:)。虽然工作。谢谢鬼狗。
Todd Partridge'Gen2ly'09-10-31

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
艾萨克(Isaac)

@sorontar的第一个参数printfformat参数。因此,如果输入文件中的内容可以解释为格式说明符,例如%d,则会出现错误。解决方法是将其更改为printf "%s" $0
Robin A. Meade

9

一种单行文件的非常简单的方法,需要coreutils的GNU回显:

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

如果它不太昂贵(重复),这是一种不错的方法。

\n存在时会出现问题。随着它转换为新行。
克里斯·斯特里钦斯基

似乎也适用于多行文件,它$(...)被引用
Thor

绝对需要引用...。/bin/echo -n "$(cat infile)" 另外,我不确定echoos / shell版本/ distros 的最大len 或shell 的最大长度(我只是在搜索它,这是一个兔子洞),所以我不确定除小文件以外的其他东西的便携性(或性能),但是对于小文件来说,太好了。
迈克尔

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。我们使用该数字查找一个字符,然后读取该字符。如果是换行符,则将文件截断为该换行符之前的字符,否则,我们什么也不做。

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


2
但这有一个缺点,就是不
重置

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)

PS:本着“ Perl golf”的精神,这是我最短的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"))

os.path.isfile()会告诉您有关文件存在的信息。使用try / except可能会捕获很多不同的错误:)
Denis Barmenkov

5

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

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

如果文件的末尾有换行符,则测试为true。

删除速度非常快,确实就位,不需要任何新文件,并且搜索也从结尾仅读取了一个字节(tail -c1)。


1
截断:缺少文件操作数
Brian Hannay

2
它只是在示例中缺少结尾的文件名,即,[ -z $(tail -c1 filename) ] && truncate -s -1 filename(另外,作为对其他评论的答复,该truncate命令不适用于stdin,必须使用文件名)
michael

4

另一个perl WTDI:

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

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

另请参见匹配sed中的任何字符(包括换行符)


1
这样就消除了所有换行符。等效于tr -d '\n'
已暂停,直至另行通知。

这也很好用,可能不如鸦片的亵渎神灵。
Todd Partridge'Gen2ly'09/

思南,尽管Linux和Unix可能会定义文本文件以换行符结尾,但Windows却没有这样的要求。例如,记事本将仅写入您键入的字符,而不会在末尾添加任何其他字符。C编译器可能需要源文件以换行符结尾,但是C源文件不是“仅仅是”文本文件,因此它们可能有其他要求。
罗伯·肯尼迪

按照这种方式,大多数javascript / css简化程序会删除结尾的换行符,但仍会生成文本文件。
2009年

@Rob Kennedy和@ysth:关于这样的文件为什么实际上不是文本文件等等,有一个有趣的论点。
SinanÜnür'09

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

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

与接受的答案有效相同,但对于非Perl用户而言,在概念上可以说更清晰。请注意,不需要使用g或括号eofperl -pi -e 's/\n$// if eof' your_file
mklement0 2015年


1

FTR的另一个答案(也是我的最爱!):回显/捕捉要剥离的内容,并通过反引号捕获输出。最后的换行符将被删除。例如:

# 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

1
我偶然发现了cat-printf组合(试图得到相反的行为)。请注意,这将删除所有尾随的换行符,而不仅仅是最后一行。
technosaurus

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.

我认为这只会在最后一行为空白的情况下将其删除。如果最后一行不是空白,它将不会删除尾随的换行符。例如,echo -en 'a\nb\n' | sed '${/^$/d}'将不会删除任何内容。echo -en 'a\nb\n\n' | sed '${/^$/d}'将删除,因为整个最后一行都是空白。
wisbucky

1

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

# 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


0

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


中途;完整的方法在这里
mklement0


0

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

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

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

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


0

红宝石:

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

要么:

ruby -ane 'q=p;p=$_;puts q if $.>1;END{print p.strip!}'
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.