awk '/^$/ {nlstack=nlstack "\n";next;} {printf "%s",nlstack; nlstack=""; print;}' file
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.
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.
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)
.
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
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.
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.
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
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'.
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"
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).