如何在EOF上删除多个换行符?


25

我的文件以一个或多个换行符结尾,并且应该仅以一个换行符结尾。如何使用Bash / Unix / GNU工具做到这一点?

错误文件示例:

1\n
\n
2\n
\n
\n
3\n
\n
\n
\n

示例更正的文件:

1\n
\n
2\n
\n
\n
3\n

换句话说:EOF和文件的最后一个非换行符之间应该只有一个换行符。

参考实施

读取文件内容,切掉一个换行符,直到末尾再没有两个换行符,将其写回:

#! /bin/python

import sys

with open(sys.argv[1]) as infile:
    lines = infile.read()

while lines.endswith("\n\n"):
    lines = lines[:-1]

with open(sys.argv[2], 'w') as outfile:
    for line in lines:
        outfile.write(line)

澄清:当然,如果更优雅,则可以使用管道。

Answers:


16
awk '/^$/ {nlstack=nlstack "\n";next;} {printf "%s",nlstack; nlstack=""; print;}' file

2
+1:awk的解决方案(几乎)总是优雅且易读!
奥利维尔·杜拉克

@OlivierDulac确实。当我看到sed提案时,我只是想到OMG ...
Hauke Laging

1
使用Homebrew的最新可用awk,这在OSX Mavericks上不起作用。错误awk: illegal statementbrew install mawk并将命令更改为mawk有效。
tjmcewan 2014年

@noname我什至都不明白这个问题……
Hauke Laging,

脚本无法运行的任何awk都严重损坏了awk-停止使用它并获取一个新的awk,因为如果它不能执行此操作,那么谁知道它还有什么其他损坏。
Ed Morton

21

有用的sed单行脚本开始

# Delete all trailing blank lines at end of file (only).
sed -e :a -e '/^\n*$/{$d;N;};/\n$/ba' file

4
谢谢,我使用以下方法对多个文件进行了处理: find . -type f -name '*.js' -exec sed --in-place -e :a -e '/^\n*$/{$d;N;};/\n$/ba' {} \;
jakub.g 2013年

@ jakub.g到位并且递归正是我需要的。谢谢。
Buttle Butkus 2015年

要在@ jakub.g中添加出色的注释,您可以在OS X上调用以下命令:find . -type f -name '*.js' -exec sed -i '' -e :a -e '/^\n*$/{$d;N;};/\n$/ba' {} \;
davejagoda

18

由于您已经有了使用sed和awk的更合适工具的答案;您可以利用$(< file)去除尾随空白行这一事实。

a=$(<file); printf '%s\n' "$a" > file

这种廉价的技巧无法删除可能包含空格或其他非打印字符的空白行尾,而只能删除空白行尾。如果文件包含空字节,它也将不起作用。

在除bash和zsh之外的shell中,使用$(cat file)代替$(<file)


+1指出对我来说似乎是个错误:$(<file)并不是真正在读取文件吗?为什么放弃尾随换行符?(确实如此,我刚刚测试过,感谢您指出!)
Olivier Dulac

2
@OlivierDulac $()丢弃尾随换行符。那是设计决定。我认为这将使在其他字符串中的集成更容易:echo "On $(date ...) we will meet."换行符几乎是每个shell命令末尾输出的换行符,这是邪恶的。
Hauke Laging

@HaukeLaging:好点,这可能是这种行为的根源
Olivier Dulac

我添加了一种特殊情况,以避免在空文件后附加“ \ n” [[ $a == '' ]] || printf '%s\n' "$a" >"$file"
davidchambers

要从文件开头剥离多个换行符,请将tac插入到进程中(我在Mac上使用gnu coreutils,所以对我来说是gtac):a=$(gtac file.txt); printf '%s\n' "$a" | gtac > file.txt
r_alex_hall

5

您可以通过cat&来使用此技巧printf

$ printf '%s\n' "`cat file`"

例如

$ printf '%s\n' "`cat ifile`" > ofile
$ cat -e ofile
1$
$
2$
$
$
3$

$表示线的末尾。

参考文献


4

这个问题用标记,但是没有人提出ed解决方案。

这是一个:

ed -s file <<'ED_END'
a

.
?^..*?+1,.d
w
ED_END

或者,等效地,

printf '%s\n' a '' . '?^..*?+1,.d' w | ed -s file

ed 启动时默认情况下会将您放置在编辑缓冲区的最后一行。

第一个命令(a)在缓冲区的末尾添加一个空行(编辑脚本中的空行是该行,而点(.)仅用于返回命令模式)。

第二个命令(?)寻找包含某些内容(甚至是空格字符)的最近的前一行,然后从下一行开始删除缓冲区末尾的所有内容。

第三个命令(w)将文件写回到磁盘。

如果原始文件末尾没有任何空行,则添加的空行可以防止删除文件的其余部分。


3

这是一个Perl解决方案,不需要一次将多行读入内存:

my $n = 0;
while (<>) {
    if (/./) {
        print "\n" x $n, $_;
        $n = 0;
    } else {
        $n++;
    }
}

或者,单线:

perl -ne 'if (/./) { print "\n" x $n, $_; $n = 0 } else { $n++ }'

这一次读取文件一行,并检查每一行以查看是否包含非换行符。如果没有,它将增加一个计数器。如果是这样,它将打印计数器指示的换行数,然后是行本身,然后重置计数器。

从技术上讲,甚至不需要在内存中缓冲一行。通过以固定长度的块读取文件并使用状态机逐个字符地处理文件,可以使用恒定数量的内存来解决此问题。但是,我怀疑对于典型的用例而言,这将不必要地复杂。


1

如果您的文件足够小,可以进入内存,则可以使用此文件

perl -e 'local($/);$f=<>; $f=~s/\n*$/\n/;print $f;' file

0

在python中(我知道这不是您想要的,但是它经过优化后要好得多,并且是bash版本的前奏),而无需重写文件且无需读取所有文件(如果文件是很大):

#!/bin/python
import sys
infile = open(sys.argv[1], 'r+')
infile.seek(-1, 2)
while infile.read(1) == '\n':
  infile.seek(-2, 1)
infile.seek(1, 1)
infile.truncate()
infile.close()

请注意,它不适用于EOL字符不是'\ n'的文件。


0

一个bash版本,实现了python算法,但是效率较低,因为它需要许多过程:

#!/bin/bash
n=1
while test "$(tail -n $n "$1")" == ""; do
  ((n++))
done
((n--))
truncate -s $(($(stat -c "%s" "$1") - $n)) "$1"

0

这是一种快速键入的内容,并且,如果您知道sed,则容易记住:

tac < file | sed '/[^[:blank:]]/,$!d' | tac

它使用sed脚本从有用的sed一行脚本中删除前导空白行,上面的Alexey引用了sed和tac(反向目录)。

在一个18MB,64,000行文件的快速测试中,Alexey的方法更快(0.036 vs 0.046秒)。

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.