Como remover várias linhas em branco de um arquivo?

14

Eu tenho alguns arquivos de texto que uso para fazer anotações - apenas texto sem formatação, geralmente apenas usando cat >> file. Ocasionalmente, uso uma ou duas linhas em branco (basta retornar - o caractere de nova linha) para especificar um novo assunto / linha de pensamento. No final de cada sessão, antes de fechar o arquivo com Ctrl+ D, normalmente adiciono lotes (5 a 10) de linhas em branco (tecla de retorno) apenas para separar as sessões.

Obviamente, isso não é muito inteligente, mas funciona para mim para esse fim. Eu faço no entanto acabar-se com lotes e lotes de linhas em branco desnecessários, por isso estou procurando uma maneira de remover (a maior parte) as linhas extras. Existe um comando Linux (recortar, colar, grep, ...?) Que possa ser usado diretamente com algumas opções? Como alternativa, alguém tem uma idéia para um script sed, awk ou perl (bem em qualquer linguagem de script, na verdade, embora eu prefira sed ou awk) que faça o que eu quero? Escrever algo em C ++ (o que eu realmente poderia fazer) parece apenas um exagero.

Caso 1: O que eu preciso é de um script / comando que remova mais de duas (3 ou mais) linhas em branco consecutivas e as substitua por apenas duas linhas em branco. Embora seria bom se também pudesse ser ajustado para remover mais de uma linha (2 ou mais) e / ou substituir várias linhas em branco por apenas uma linha em branco.

Caso 2: Eu também poderia usar um script / comando que removeria uma única linha em branco entre duas linhas de texto, mas deixaria várias linhas em branco como estão (embora a remoção de uma das linhas em branco também seja aceitável).

Baard Kopperud
fonte
2
@ l0b0, essa é uma pergunta completamente diferente (a outra era uma vime substituía as linhas em branco por uma linha em branco).
Stéphane Chazelas

Respostas:

14

Caso 1:

awk '!NF {if (++n <= 2) print; next}; {n=0;print}'

Caso 2:

awk '!NF {s = s $0 "\n"; n++; next}
     {if (n>1) printf "%s", s; n=0; s=""; print}
     END {if (n>1) printf "%s", s}'
Stéphane Chazelas
fonte
+1 para awk em vez de sed
Rob
Como esse caso de uso é repetido com frequência, sugiro a criação de um script.
ChuckCottrill
15

Você pode usar uniqpara recolher várias instâncias de linhas em branco em uma linha em branco, mas também recolherá linhas que contêm texto, se forem iguais e abaixo uma da outra.

Anthon
fonte
6

Caso 1:

perl -i -ane '$n=(@F==0) ? $n+1 : 0; print if $n<=2'

Caso 2:

perl -i -ane '$n=(@F==0) ? $n+1 : 0; print $n==2 ? "\n$_" : $n==1 ? "" : $_ '
Basharat Sialvi
fonte
+1 ftl perl! O Awk é (provavelmente) canônico para isso, mas (DRY) me obriga a escrever scripts para casos de uso que são repetidos assim.
ChuckCottrill
3

Você pode abordar o Caso 1 como este com o GNU sed:

sed -r ':a; /^\s*$/ {N;ba}; s/( *\n *){2,}/\n\n/'

Ou seja, colete linhas vazias no espaço padrão e, se houver mais de três ou mais linhas, reduza-o para duas linhas.

Para unir linhas com espaço único, como no Caso 2, você pode fazer o seguinte:

sed -r '/^ *\S/!b; N; /\n *$/!b; N; /\S *$/!b; s/\n *\n/\n/'

Ou no formato comentado:

sed -r '
  /^ *\S/!b        # non-empty line
  N                # 
  /\n *$/!b        # followed by empty line
  N                # 
  /\S *$/!b        # non-empty line
  s/\n *\n/\n/     # remove the empty line
'
Thor
fonte
1

Esta solução também cuida das últimas linhas em branco no arquivo:

sed -r -n '
  /^ *$/!{p;b}  # non-blank line - print and next cycle
  h             # blank line - save it in hold space
  :loop
  $b end        # last line - go to end
  n             # read next line in pattern space
  /^ *$/b loop  # blank line - loop to next one
  :end          # pattern space has non-blank line or last blank line
  /^ *$/{p;b}   # last blank line: print and exit
  H;x;p         # non-blank line: print hold + pattern space and next cycle
'
PJ_Finnegan
fonte
0

Seguindo a sugestão de Anthon de usar "uniq" ...

Remova linhas em branco iniciais, finais e duplicadas.

# Get large random string.
rand_str=; while [[ ${#rand_str} -lt 40 ]]; do rand_str=$rand_str$RANDOM; done

# Add extra lines at beginning and end of stdin.
(echo $rand_str; cat; echo $rand_str) |

# Convert empty lines to random strings.
sed "s/^$/$rand_str/" |

# Remove duplicate lines.
uniq |

# Remove first and last line.
sed '1d;$d' |

# Convert random strings to empty lines.
sed "s/$rand_str//"

Em uma linha longa:

(rand_str=; while [[ ${#rand_str} -lt 40 ]]; do rand_str=$rand_str$RANDOM; done; (echo $rand_str; cat; echo $rand_str) | sed "s/^$/$rand_str/" | uniq | sed '1d;$d' | sed "s/$rand_str//")

Ou apenas use "cat -s".

Eu mudei de parênteses para chaves, a fim de permanecer no contexto atual do shell, que suponho ser mais eficiente. Observe que os chavetas requerem ponto e vírgula após o último comando e precisam de um espaço para separação.

# Add extra blank lines at beginning and end.
# These will be removed in final step.
{ echo; cat; echo; } |

# Replace multiple blank lines with a single blank line.
cat -s |

# Remove first and last line.
sed '1d;$d'

Em uma única linha.

{ { echo; cat; echo; } | cat -s | sed '1d;$d'; }
JohnMudd
fonte
0

As soluções postadas pareciam um pouco enigmáticas para mim. Aqui está a solução no Python 3.6:

#!/usr/bin/env python3

from pathlib import Path                                                                                                                                                              
import sys                                                                                                                                                                            
import fileinput                                                                                                                                                                      


def remove_multiple_blank_lines_from_file(path, strip_right=True): 
    non_blank_lines_out_of_two_last_lines = [True, True] 
    for line in fileinput.input(str(path), inplace=True): 
        non_blank_lines_out_of_two_last_lines.pop(0) 
        non_blank_lines_out_of_two_last_lines.append(bool(line.strip())) 
        if sum(non_blank_lines_out_of_two_last_lines) > 0: 
            line_to_write = line.rstrip() + '\n' if strip_right else line 
            sys.stdout.write(line_to_write)


def remove_multiple_blank_lines_by_glob(rglob='*', path=Path('.'), strip_right=True): 
    for p in path.rglob(rglob): 
        if p.is_file(): 
            try:
                remove_multiple_blank_lines_from_file(p, strip_right=strip_right)
            except Exception as e:
                print(f"File '{p}' was not processed due the error: {e}")


if __name__ == '__main__':
    remove_multiple_blank_lines_by_glob(sys.argv[1], Path(sys.argv[2]), next(iter(sys.argv[3:]), None) == '--strip-right')

Você pode chamar as funções de um intérprete ou executá-lo a partir do shell, como:

$ ./remove_multiple_lines.py '*' /tmp/ --strip-right
rominf
fonte