Deve haver uma maneira melhor de substituir apenas novas linhas únicas?


27

Tenho o hábito de escrever uma linha por frase, porque normalmente compilo coisas para o LaTex ou estou escrevendo em outro formato no qual as quebras de linha são ignoradas. Eu uso uma linha em branco para indicar o início de um novo parágrafo.

Agora, eu tenho um arquivo escrito neste estilo que gostaria de enviar como texto sem formatação. Quero remover todas as quebras de linha únicas, mas deixar intactas as quebras de linha duplas. Isto é o que eu fiz:

sed 's/$^/NEWLINE/' file.txt | awk '{printf "%s ",$0}' | sed 's/NEWLINE/\n\n/g' > linebreakfile.txt

Isso substitui as linhas vazias por algum texto que, confiante, não apareça no arquivo: NEWLINEdepois, ele se livra de todas as quebras de linha com awk (achei esse truque em algum site) e, em seguida, substitui NEWLINEs com as duas quebras de linha necessárias .

Parece uma maneira muito complicada de fazer uma coisa bem simples. Existe uma maneira mais simples? Além disso, se houvesse uma maneira de substituir vários espaços (que às vezes se arrastam por algum motivo) por espaços únicos, isso também seria bom.

Eu uso o emacs, por isso, se houver algum truque específico do emacs que seja bom, mas prefiro ver uma versão pura sed ou awk pura.


Você quis dizer ^ $, não $ ^ no primeiro comando sed.
usuário desconhecido

@ usuário sim, sim eu fiz.
Seamus

Uma maneira mais fácil de remover todas as quebras de linha: tr -d "\n".
Jfg956

Respostas:


18

Você pode usar o awk assim:

$ awk ' /^$/ { print; } /./ { printf("%s ", $0); } ' test

Ou se você precisar de uma nova linha extra no final:

$ awk ' /^$/ { print; } /./ { printf("%s ", $0); } END { print ""; } ' test

Ou se você deseja separar os parágrafos por uma nova linha:

$ awk ' /^$/ { print "\n"; } /./ { printf("%s ", $0); } END { print ""; } ' test

Esses comandos do awk usam ações que são protegidas por padrões:

/regex/

ou

END

Uma ação a seguir será executada apenas se o padrão corresponder à linha atual.

E os ^$.caracteres têm um significado especial nas expressões regulares, onde ^corresponde ao início da linha, $ao final e a .um caractere arbitrário.


Isso é bom, embora eu prefira manter a linha vazia entre parágrafos. Suponho que você poderia fazer algo assim adicionando uma nova linha extra em algum lugar no primeiro comando de impressão? Além disso, o que está /./fazendo: parece estar agindo como e elsepara a /^$/correspondência de cordas, está certo?
Seamus

11
@Seamus, com certeza - basta substituir a primeira impressão (atualizada a resposta) - /./ corresponde a todas as linhas com pelo menos um caractere, ou seja, o complemento do padrão / ^ $ / que corresponde apenas às linhas vazias.
maxschlepzig

9

Use o modo de parágrafo do Awk ou do Perl para processar um arquivo, parágrafo por parágrafo, onde os parágrafos são separados por linhas em branco.

awk -vRS= '
  NR!=1 {print ""}      # print blank line before every record but the first
  {                     # do this for every record (i.e. paragraph):
    gsub(" *\n *"," "); # replace newlines by spaces, compressing spaces
    sub(" *$","");      # remove spaces at the end of the paragraph
    print
  }
'
perl -000 -pe '             # for every paragraph:
  print "\n" unless $.==1;  # print a blank line, except before the first paragraph
  s/ *\n *(?!$)/ /g;        # replace newlines by spaces, compressing spaces, but not at the end of the paragraph
  s/ *\n+\z/\n/             # normalize the last line end of the paragraph
'

Obviamente, como isso não analisa o (La) TeX, ele mutilará terrivelmente comentários, ambientes detalhados e outras sintaxes especiais. Você pode procurar no DeTeX ou em outros conversores (La) TeX para texto.


8

Solução Sed

$ sed -e ':a;N;$!ba;s/\(.\)\n/\1 /g' -e 's/\n/\n\n/' test.text

Observe que nesta solução :aestá criando um rótulo e não usando o acomando

Substituindo Vários Espaços

Use tr:$ tr -s ' ' <test.text


8

Se entendi corretamente, uma linha vazia implica duas novas linhas consecutivas \n\n,.

Nesse caso, uma solução possível seria eliminar todas as ocorrências singulares de novas linhas.

No Perl, uma asserção lookahead é uma maneira de conseguir isso:

$ perl -0777 -i -pe 's/\n(?=[^\n])//g' test
  • O -0777sinalizador efetivamente comporta o arquivo inteiro em uma única sequência
  • -p diz ao perl para imprimir a string em que está trabalhando por padrão
  • -i especifica a edição no local
  • A correspondência global garante que todas as ocorrências de nova linha sejam tratadas

Um problema que isso tem é que não há espaços entre as frases.
9119 Steven

6

(revivendo uma pergunta antiga)

Isso parece ser exatamente o que é fmte parserve para reformatar os parágrafos. Como você (e também como muitos programas), eles definem os limites dos parágrafos como uma (ou mais) linhas em branco. Tente canalizar seu texto através de um destes.

fmt é um utilitário unix padrão e pode ser encontrado no GNU Coreutils.

paré um fmttexto muito aprimorado escrito por Adam M. Costello, que pode ser encontrado em http://www.nicemice.net/par/ (também foi empacotado para várias distribuições, incluindo o debian - eu o empacotei para o debian em janeiro de 1996, embora exista um novo mantenedor para o pkg agora.).


6
sed -e'/./{H;$!d;}' -e'x;s/\n//g'

sedanexará qualquer linha ao Hespaço antigo que contenha pelo menos um único caractere. Ele imediatamente elimina dtodos aqueles, exceto talvez o último. As únicas linhas que podem permanecer são espaços em branco e é nessas linhas quando sede xaltera os espaços de espera e padrão e exclui todos os \ncaracteres de linha de ew acumulados .

Se você deseja que as linhas que contêm apenas <tabs> ou <spaces> sejam consideradas em branco, substitua o /./endereço acima por /[^[:blank:]]/. Para também espremer espaços, faça:

 sed -e'/./{H;$!d;}'    \
     -e'x;s/\n//g'      \
     -e's/\([[:blank:]]\)*/\1/g'

5

Depois de ver os exemplos compactos perl e awk de Gilles, relutei em postar isso, mas já havia passado pelo exercício, e é um script funcional, razoavelmente documentado; este ponto por si só pode ser de interesse para alguns .. (sed com comentários! :)

Esse script considera as linhas em branco em branco, mesmo que contenham espaços em branco.
Vários espaços no texto são condensados ​​em um único espaço.
O espaço em branco à direita é removido das linhas de texto. Linhas em branco consecutivas são recolhidas em uma única linha. O script deixa as linhas em branco superior e inferior intactas.

Para algo além dos scripts mais triviais, o sed pode ser escrito com muito mais facilidade em uma forma estruturada, como um arquivo de script separado. Aqui está um exemplo.

usando
chamada de sintaxe regex estendida : $ sed -rf script-file-text

  :first-empty-line
  #================
  /^[[:space:]]*$/ { # if pattern-space is empty...
      $q  # last line # flush-quit 
      n   # pattern-flush=nextline-continue

      :subsequent-empty-line
      #=====================
      /^[[:space:]]*$/ { # if pattern-space is empty...
          $d        # last line # pattern-delete-cycle
          N         # pattern+=nl+nextline
          s/.*\n//  # scrap the leading 'blank' line
          t subsequent-empty-line # branch-on-substitute
      }
  }

  :text-line
  #=========
  $q                       # last line # flush-quit 
  s/^(.*)[[:space:]]*/\1/  # trim trailing whitespace
  s/ +/ /g                 # condense mulltiple spaces
  N                        # pattern+=nl+nextline
  /^.*\n[[:space:]]*$/ { # if newly-read line is blank 
      P          # pattern-first-line-print
      s/^.*\n//  # remove the leading 'text' line
      t first-empty-line   # branch-on-substitute
  }
  # read line is text
  s/\n/ /      # replace \n with a space
  t text-line  # branch-on-substitute

Nota: flushnos comentários, significa: envie o espaço do padrão para o tratamento stdout interno do sed. Isso não significa uma impressão definitiva para o stdout. A saída depende da -nopção do sed . por exemplo. o qcomando significa liberar e sair ... Compare esses dois trechos: echo x |sed -e qimprime x, echo x |sed -ne qimprime nada, enquanto o uso do pcomando imprime 'x' duas ou uma vez, dependendo da -nopção.


+1 para bons comentários. Eu já vi muitos programas sem comentários.
David Cary

4

Aqui está outra sedsolução que concatena todas as linhas no sed"espaço de espera" de modo a obter uma sequência longa que finalmente é copiada para o "espaço padrão" para a correspondência de padrões.

Como as novas linhas serão preservadas na sequência longa final no sed"espaço padrão", as linhas vazias em termos de quebras de linha duplas [^\n]\n\n[^\n]podem ser correspondidas e modificadas para [^\n]\n[^\n].

Para obter mais informações, consulte, por exemplo, sed e pesquisa e substituição de várias linhas .

text='
line 1

line 2
line 3





line 4


line     5



line 6
line 7

line 8
'

# FreeBSD sed
# first sed deletes first / last line if empty and squeezes multiple spaces
printf '%s' "$text" |
sed -e '1{/^$/d;}' -e '${/^$/d;}' -e '/[[:space:]]\{2,\}/s// /g' | 
sed -n -e '1h;1!H;${;g;/\([^[:cntrl:]]\)\n\n\([^[:cntrl:]]\)/s//\1\
\2/g;p;}' |
nl -b a


# GNU sed
# alternative using ...;x;... instead of ...;g;...
# cf. man sed | less -p '\]x'
printf '%s' "$text" |
gsed -e '1{/^$/d;}' -e '${/^$/d;}' -e '/[[:space:]]\{2,\}/s// /g' | 
gsed -E -n '1h;1!H;${;x;/([^\n])\n\n([^\n])/s//\1\
\2/g;p;}' | 
nl -b a


# remove all the single linebreaks but leave the double linebreaks intact
printf '%s' "$text" | 
   sed -n -e '1h;1!H;${;g;/\([^[:cntrl:]]\)\n\([^[:cntrl:]]\)/s//\1 \2/g;p;}' | 
   nl -b a

3

Pode ser a velha escola:

(echo ".pl 1" ; echo ".ll 80" ; echo ".ad l" ; cat your_file) | nroff

Isso produzirá o texto alinhado à esquerda ( .ad l), com o comprimento da linha de 80 ( .ll 80). A opção de comprimento da página ( .pl) informa ao processador de texto que faça preenchimento de página com o comprimento de página 1, portanto, sem preenchimento de página.

Se você quiser todos os parágrafos em uma única linha, poderá usar um número grande para .ll:

(echo ".pl 1" ; echo ".ll 1000000" ; echo ".ad l" ; cat your_file) | nroff

man 7 groff para mais opções de formatação.


1

No Emacs, às vezes uso isso regex:

^J\([^^J]\) -> \1

Significa:

substitua cada nova linha que é seguida por algo que NÃO é uma nova linha apenas com a coisa que seguiu a nova linha. Dessa forma, eu me livre de todas as novas linhas em um parágrafo, mas mantenho parágrafos (linhas duplas)


0

Acontece que, com auto-fill-modeon, o emacs faz um bom trabalho nos meus casos de uso simples com apenas M-q...


Os detalhes do que auto-fill-modedepende de qual modo principal você está ativo.
precisa saber é o seguinte
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.