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

How can I delete a newline if it is the last character in a file?

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

1
0013600   n   t  >

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

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

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


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

或者,编辑文件:

1
perl -pi -e 'chomp if eof' filename

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

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

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


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

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

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

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

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

注意:

  • 如果in.txt以多个换行符结尾,则命令替换将删除所有这些 - 谢谢,@ Sparhawk。 (除了尾随换行符之外,它不会删除空白字符。)
  • 由于此方法将整个输入文件读入内存,因此仅建议使用较小的文件。
  • printf %s确保没有新行附加到输出(它是符合POSIX的非标准echo -n的替代方法;请参阅http://pubs.opengroup.org/onlinepubs/009696799/utilities/echo.html和https: //unix.stackexchange.com/a/65819)

其他答案的指南:

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

  • 否则,考虑一下ghostdog74的Awk答案 - 它模糊不清,而且内存效率高;更具可读性的等价物(POSIX兼容)是:

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


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

1
head -c -1

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

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

您还可以使用moreutils中的sponge进行"就地"编辑:

1
[[ $(tail -c1 file | wc -l) == 1 ]] && head -c -1 file | sponge file

您还可以通过将其填入.bashrc文件来创建一般的可重用函数:

1
2
3
4
5
6
7
8
9
10
# 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并支持就地编辑。


1
2
3
head -n -1 abc > newfile
tail -n 1 abc | tr -d '
'
>> newfile

编辑2:

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

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


呆子

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


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

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


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

1
2
3
4
5
6
7
8
9
10
11
12
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"
"
){
    truncate $fh, $pos - 1;
}

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

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


这是一个漂亮,整洁的Python解决方案。我没有试图在这里简洁。

这会就地修改文件,而不是复制文件并从副本的最后一行剥离换行符。如果文件很大,这将比选择作为最佳答案的Perl解决方案快得多。

如果最后两个字节是CR / LF,它会将文件截断两个字节,如果最后一个字节是LF,则截断一个字节。如果最后一个字节不是(CR)LF,它不会尝试修改文件。它处理错误。在Python 2.6中测试过。

把它放在一个名为"striplast"和chmod +x striplast的文件中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#!/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("

"
):
    trunc(filename, end_pos)
elif line.endswith("
"
):
    trunc(filename, end_pos + 1)

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

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

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

1
2
3
4
import sys

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


另一个perl WTDI:

1
2
perl -i -p0777we's/
\z//'
filename


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

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

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

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


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

另请参阅在sed中匹配任何字符(包括换行符)。


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

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

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

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


使用dd:

1
2
3
4
5
file='/path/to/file'
[["$(tail -c 1"${file}" | tr -dc '
' | 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

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


POSIX SED:

'$ {/ ^ $ / d}'

1
2
3
4
$ - 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.


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

1
2
3
4
5
6
7
8
9
10
# 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
2
3
sed ':a;/^
*$/{$d;N;};/
$/ba'
file


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

1
2
3
4
5
6
7
8
9
10
11
# with trailing newline
echo -en 'foo
bar
'
| sed '$s/$//' | head -c -1

# still works without trailing newline
echo -en 'foo
bar'
| 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


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

1
2
3
4
sed 's/
//g'
orig | awk '{if (NR>1) printf("

"); printf("%s",$0)}'
> tweaked


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


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

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


红宝石:

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

要么:

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