¿Debe haber una mejor manera de reemplazar solo nuevas líneas?

27

Tengo la costumbre de escribir una línea por oración porque normalmente compilo cosas en LaTex, o escribo en algún otro formato donde los saltos de línea se ignoran. Yo uso una línea en blanco para indicar el comienzo de un nuevo párrafo.

Ahora, tengo un archivo escrito en este estilo que me gustaría enviar como texto sin formato. Quiero eliminar todos los saltos de línea individuales pero dejar intactos los saltos de línea dobles. Esto es lo que he hecho:

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

Esto reemplaza las líneas vacías con algún texto que estoy seguro no aparece en el archivo: NEWLINEy luego elimina todos los saltos de línea con awk (encontré ese truco en algún sitio web) y luego reemplaza la NEWLINEs con los dos saltos de línea necesarios .

Esto parece una forma larga y sin aliento de hacer algo bastante simple. ¿Hay alguna forma más simple? Además, si hubiera una manera de reemplazar múltiples espacios (que a veces se arrastran por alguna razón) con espacios individuales, eso también sería bueno.

Uso emacs, así que si hay algún truco específico de emacs que sea bueno, pero preferiría ver una versión pura de sed o awk puro.

Seamus
fuente
Querías decir ^ $, no $ ^ en el primer comando sed.
usuario desconocido
@usuario sí, sí lo hice.
Seamus
Una forma más fácil de eliminar todos los saltos de línea: tr -d "\n".
jfg956

Respuestas:

18

Puedes usar awk así:

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

O si necesita una nueva línea adicional al final:

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

O si desea separar los párrafos por una nueva línea:

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

Estos comandos awk hacen uso de acciones que están protegidas por patrones:

/regex/

o

END

La siguiente acción solo se ejecuta si el patrón coincide con la línea actual.

Y los ^$.caracteres tienen un significado especial en las expresiones regulares, donde ^coincide con el comienzo de la línea, $el final y .un carácter arbitrario.

maxschlepzig
fuente
Esto es bueno, aunque preferiría mantener la línea vacía entre párrafos. ¿Supongo que podría hacer algo como esto agregando una nueva línea adicional en algún lugar del primer comando de impresión? Además, qué está /./haciendo: parece estar actuando como y elsepara la /^$/coincidencia de cuerdas, ¿es así?
Seamus
1
@Seamus, claro, simplemente reemplace la primera impresión (la respuesta actualizada) - /./ coincide con todas las líneas que tienen al menos un carácter de largo, es decir, el complemento del patrón / ^ $ / que coincide solo con las líneas vacías.
maxschlepzig
9

Use el modo de párrafo Awk o Perl para procesar un archivo párrafo por párrafo, donde los párrafos están separados por líneas en blanco.

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
'

Por supuesto, dado que esto no analiza el (La) TeX, mutilará horriblemente los comentarios, los entornos textuales y otras sintaxis especiales. Es posible que desee buscar DeTeX u otros convertidores (La) TeX a texto.

Gilles 'SO- deja de ser malvado'
fuente
8

Solución Sed

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

Tenga en cuenta que en esta solución :ase crea una etiqueta y no se usa el acomando.

Sustitución de espacios múltiples

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

Steven D
fuente
8

Si he entendido bien, una línea vacía implica dos nuevas líneas consecutivas, \n\n.

Si es así, una posible solución sería eliminar todas las ocurrencias singulares de las nuevas líneas.

En Perl, una afirmación anticipada es una forma de lograr esto:

$ perl -0777 -i -pe 's/\n(?=[^\n])//g' test
  • La -0777bandera efectivamente absorbe todo el archivo en una sola cadena
  • -p le dice a Perl que imprima la cadena en la que está trabajando por defecto
  • -i especifica la edición en el lugar
  • La coincidencia global garantiza que se traten todas las ocurrencias de nueva línea
Zaid
fuente
Un problema que esto tiene es que no hay espacios entre las oraciones.
Steven D
6

(reviviendo una antigua pregunta)

Esto parece ser exactamente lo que fmty parson para - apartado cambio de formato. Al igual que usted (y también como muchos programas), definen los límites de los párrafos como una (o más) líneas en blanco. Intenta pasar el texto por uno de estos.

fmt es una utilidad estándar de Unix y se puede encontrar en GNU Coreutils.

pares una gran mejora fmtescrita por Adam M. Costello que se puede encontrar en http://www.nicemice.net/par/ (también se ha empaquetado para varias distribuciones, incluida debian - la empaqueté para debian en enero de 1996, aunque ahora hay un nuevo mantenedor para el paquete).

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

sedagregará cualquier línea al Hespacio antiguo que contenga al menos un solo carácter. Inmediatamente después, delige a todos, excepto quizás el último. Las únicas líneas que pueden permanecer son espacios en blanco, y es en estas líneas cuando sede xcambia los espacios de espera y de patrón y elimina todos los \ncaracteres acumulados de línea de hilo.

Si desea que las líneas que contienen solo <tabs> o <spaces> se consideren en blanco, reemplace la /./dirección anterior con /[^[:blank:]]/. Para exprimir espacios también:

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

Después de ver los ejemplos compactos de Perles y Awk de Gilles, era reacio a publicar esto, pero ya había realizado el ejercicio y es un guión funcional, que está razonablemente documentado; este punto solo puede ser de interés para algunos ... (sed con comentarios! :)

Este script considera que las líneas en blanco están en blanco, incluso si contienen espacios en blanco.
Múltiples espacios en el texto se condensan en un solo espacio.
El espacio en blanco al final se elimina de las líneas de texto. Las líneas en blanco consecutivas se contraen en una sola línea. El script deja intactas las líneas en blanco superior e inferior.

Para algo más que los scripts más triviales, sed se puede escribir mucho más fácilmente en una forma estructurada, como un archivo de script separado. Aquí hay tal ejemplo.

usando la
llamada de sintaxis regex extendida : $ sed -rf script text-file

  :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: flushen los comentarios, significa: enviar el espacio del patrón al manejo interno de sed de stdout. No significa una impresión definitiva a stdout. La salida depende de la -nopción de sed . p.ej. el qcomando significa enjuagar y salir ... Compare estos dos fragmentos: echo x |sed -e qimprime x, echo x |sed -ne qno imprime nada, mientras que usar el pcomando imprimiría 'x' dos o una vez, dependiendo de la -nopción.

Peter.O
fuente
+1 para buenos comentarios. He visto demasiados programas sin ningún comentario.
David Cary
4

Aquí hay otra sedsolución que concatena todas las líneas en sedel "espacio de retención" para que obtengamos una cadena larga que finalmente se copia en el "espacio del patrón" para la coincidencia de patrones.

Como las líneas nuevas se conservarán en la cadena larga final en sedel "espacio de patrón", las líneas vacías en términos de saltos de línea dobles [^\n]\n\n[^\n]se pueden combinar y modificar [^\n]\n[^\n].

Para obtener más información, consulte, por ejemplo, sed y Búsqueda y reemplazo de varias líneas .

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
deso
fuente
3

Esto podría ser de la vieja escuela:

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

Esto generará el texto alineado a la izquierda ( .ad l), con una longitud de línea de 80 ( .ll 80). La opción de longitud de página ( .pl) le dice al procesador de texto que complete el relleno de página para una longitud de página de 1, por lo que no hay relleno de página.

Si quiere todos sus párrafos en una sola línea, puede usar un número grande para .ll:

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

man 7 groff para más opciones de formato.

jfg956
fuente
1

En Emacs, a veces uso esto regex:

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

Medio:

reemplace cada nueva línea seguida de algo que NO sea una nueva línea con solo la cosa, que siguió a la nueva línea De esa manera me deshago de todas las nuevas líneas dentro de un párrafo pero mantengo los párrafos (líneas dobles nuevas)

emacs-user
fuente
0

Resulta que con auto-fill-modeon, emacs hace un trabajo bastante bueno para mis casos de uso simples con solo M-q...

Seamus
fuente
Los detalles de lo auto-fill-modeque depende de qué modo principal tiene activo.
dmckee