Como remover várias novas linhas no EOF?

22

Eu tenho arquivos que terminam em uma ou mais novas linhas e devem terminar em apenas uma nova linha. Como posso fazer isso com ferramentas Bash / Unix / GNU?

Exemplo de arquivo inválido:

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

Exemplo de arquivo corrigido:

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

Em outras palavras: deve haver exatamente uma nova linha entre o EOF e o último caractere não pertencente à nova linha do arquivo.

Implementação de referência

Leia o conteúdo do arquivo, corte uma nova linha até que não haja mais duas novas linhas no final, escreva de volta:

#! /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)

Esclarecimento: Claro, a tubulação é permitida, se isso for mais elegante.

    
por Bengt 04.07.2013 / 02:20

10 respostas

15
awk '/^$/ {nlstack=nlstack "\n";next;} {printf "%s",nlstack; nlstack=""; print;}' file
    
por 04.07.2013 / 02:40
16

Desde que você já tem respostas com as ferramentas mais adequadas sed e awk; você pode aproveitar o fato de que $(< file) retira linhas em branco.

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

Esse truque barato não funcionaria para remover linhas em branco à direita que podem conter espaços ou outros caracteres não imprimíveis, apenas para remover linhas vazias à direita. Também não funcionará se o arquivo contiver bytes nulos.

Em shells que não sejam bash e zsh, use $(cat file) em vez de $(<file) .

    
por 04.07.2013 / 02:47
15

De scripts úteis de uma linha para sed .

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

Você pode usar esse truque com cat & printf :

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

Por exemplo

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

O $ indica o final de uma linha.

Referências

por 04.07.2013 / 04:30
2

Aqui está uma solução Perl que não requer a leitura de mais de uma linha na memória por vez:

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

ou, como um one-liner:

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

Isto lê o arquivo uma linha de cada vez e verifica cada linha para ver se contém um caractere não-nova linha. Se isso não acontecer, incrementa um contador; em caso afirmativo, imprime o número de novas linhas indicadas pelo contador, seguido pela própria linha e redefine o contador.

Tecnicamente, mesmo o buffer de uma única linha na memória é desnecessário; Seria possível resolver esse problema usando uma quantidade constante de memória lendo o arquivo em pedaços de comprimento fixo e processando-o caractere por caractere usando uma máquina de estado. No entanto, suspeito que seria desnecessariamente complicado para o caso de uso típico.

    
por 04.07.2013 / 12:16
2

Esta questão está marcada com ed , mas ninguém propôs uma solução ed (pergunto-me porquê?).

Aqui está uma:

ed file <<ED_END
a

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

ed colocará você na última linha do buffer de edição, por padrão, na inicialização.

O primeiro comando ( a ) adiciona uma linha vazia ao final do buffer (a linha vazia no script de edição é essa linha, e o ponto ( . ) é apenas para voltar ao modo de comando) .

O segundo comando ( ? ) procura a linha anterior mais próxima que contém algo (até mesmo caracteres de espaço em branco) e, em seguida, exclui tudo para o final do buffer a partir da próxima linha.

O terceiro comando ( w ) grava o arquivo de volta no disco.

    
por 08.07.2016 / 18:12
1

Se o seu arquivo é pequeno o suficiente para entrar na memória, você pode usar isso

perl -e 'local($/);$f=<>; $f=~s/\n*$/\n/;print $f;' file
    
por 04.07.2013 / 02:51
0

Em python (eu sei que não é o que você quer, mas é muito melhor, pois é otimizado, e um prelúdio para a versão bash) sem reescrever o arquivo e sem ler todo o arquivo (o que é bom se o arquivo é muito grande):

#!/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()

Observe que ele não funciona em arquivos em que o caractere EOL não é '\ n'.

    
por 09.07.2013 / 12:19
0

Uma versão bash, implementando o algoritmo python, mas menos eficiente, pois precisa de muitos processos:

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

Este é rápido para escrever e, se você souber sed, é fácil de lembrar:

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

Ele usa o script sed para excluir as linhas em branco scripts úteis de uma linha para o sed >, referenciado por Alexey, acima, e tac (gato reverso).

Em um teste rápido, em um arquivo de 64.000 linhas com 18MB, a abordagem de Alexey foi mais rápida (0.036 versus 0.046 segundos).

    
por 31.08.2018 / 12:02

Tags