¿Cómo puedes combinar todas las líneas que terminan con un carácter de barra diagonal inversa?


36

Usando una herramienta de línea de comando común como sed o awk, ¿es posible unir todas las líneas que terminan con un carácter dado, como una barra invertida?

Por ejemplo, dado el archivo:

foo bar \
bash \
baz
dude \
happy

Me gustaría obtener esta salida:

foo bar bash baz
dude happy

1
Pase el archivo a través cpp:)
imz - Ivan Zakharyaschev

¡Tantas respuestas maravillosas que desearía poder marcarlas todas como la respuesta! Gracias por la gran mirada a awk, sed y perl, estos fueron excelentes ejemplos.
Cory Klein

Respuestas:


27

Una solución sed más corta y simple:

sed  '
: again
/\\$/ {
    N
    s/\\\n//
    t again
}
' textfile

o una línea si usa GNU sed:

sed ':x; /\\$/ { N; s/\\\n//; tx }' textfile

1
bueno ... Inicialmente miré esto y no pude entenderlo (por lo que no estaba en la canasta demasiado dura) ... pero después de una mirada en profundidad a la respuesta de Gilles (que tomó bastante tiempo) Eché otro vistazo a su respuesta y pareció notablemente comprensible. Creo que estoy empezando a entender sed:) ... Usted está agregando cada línea directamente al espacio del patrón, y cuando aparece una línea "normalmente terminada", aparece todo el espacio del patrón se cae y se imprime automáticamente (porque no hay una opción -n) ... ¡limpio! .. +1
Peter.O

@fred: gracias, creo que también estoy empezando a entender sed, ofrece buenas herramientas para la edición multilínea, pero cómo mezclarlas para obtener lo que necesitas no es sencillo ni la legibilidad es lo más importante ...
neurino

Cuidado con los finales de línea de DOS, también conocido como el carro vuelve o \ r!
usuario77376

1
Lo que está mal consed -e :a -e '/\\$/N; s/\\\n//; ta'
Isaac

18

Posiblemente sea más fácil con perl (dado que perl es como sed y awk, espero que sea aceptable para usted):

perl -p -e 's/\\\n//'

corto y simple, me gusta ese +1 Y no pidió explícitamente sed o awk
rudolfson


2

Esta no es una respuesta como tal. Es un tema secundario sobresed .

Específicamente, necesitaba tomar Gilles sed separar el comando de pieza por pieza para entenderlo ... Comencé a escribir algunas notas sobre él, y luego pensé que podría ser útil aquí para alguien ...

así que aquí está ... el script sed de Gilles en formato documentado :


#!/bin/bash
#######################################
sed_dat="$HOME/ztest.dat"
while IFS= read -r line ;do echo "$line" ;done <<'END_DAT' >"$sed_dat"
foo bar \
bash \
baz
dude \
happy
yabba dabba 
doo
END_DAT

#######################################
sedexec="$HOME/ztest.sed"
while IFS= read -r line ;do echo "$line" ;done <<'END-SED' >"$sedexec"; \
sed  -nf "$sedexec" "$sed_dat"

  s/\\$//        # If a line has trailing '\', remove the '\'
                 #    
  t'Hold-append' # branch: Branch conditionally to the label 'Hold-append'
                 #         The condition is that a replacement was made.
                 #         The current pattern-space had a trailing '\' which  
                 #         was replaced, so branch to 'Hold-apend' and append 
                 #         the now-truncated line to the hold-space
                 #
                 # This branching occurs for each (successive) such line. 
                 #
                 # PS. The 't' command may be so named because it means 'on true' 
                 #     (I'm not sure about this, but the shoe fits)  
                 #
                 # Note: Appending to the hold-space introduces a leading '\n'   
                 #       delimiter for each appended line
                 #  
                 #   eg. compare the hex dump of the follow 4 example commands:  
                 #       'x' swaps the hold and patten spaces
                 #
                 #       echo -n "a" |sed -ne         'p' |xxd -p  ## 61 
                 #       echo -n "a" |sed -ne     'H;x;p' |xxd -p  ## 0a61
                 #       echo -n "a" |sed -ne   'H;H;x;p' |xxd -p  ## 0a610a61
                 #       echo -n "a" |sed -ne 'H;H;H;x;p' |xxd -p  ## 0a610a610a61

   # No replacement was made above, so the current pattern-space
   #   (input line) has a "normal" ending.

   x             # Swap the pattern-space (the just-read "normal" line)
                 #   with the hold-space. The hold-space holds the accumulation
                 #   of appended  "stripped-of-backslah" lines

   G             # The pattern-space now holds zero to many "stripped-of-backslah" lines
                 #   each of which has a preceding '\n'
                 # The 'G' command Gets the Hold-space and appends it to 
                 #   the pattern-space. This append action introduces another
                 #   '\n' delimiter to the pattern space. 

   s/\n//g       # Remove all '\n' newlines from the pattern-space

   p             # Print the pattern-space

   s/.*//        # Now we need to remove all data from the pattern-space
                 # This is done as a means to remove data from the hold-space 
                 #  (there is no way to directly remove data from the hold-space)

   x             # Swap the no-data pattern space with the hold-space
                 # This leaves the hold-space re-initialized to empty...
                 # The current pattern-space will be overwritten by the next line-read

   b             # Everything is ready for the next line-read. It is time to make 
                 # an unconditional branch  the to end of process for this line
                 #  ie. skip any remaining logic, read the next line and start the process again.

  :'Hold-append' # The ':' (colon) indicates a label.. 
                 # A label is the target of the 2 branch commands, 'b' and 't'
                 # A label can be a single letter (it is often 'a')
                 # Note;  'b' can be used without a label as seen in the previous command 

    H            # Append the pattern to the hold buffer
                 # The pattern is prefixed with a '\n' before it is appended

END-SED
#######

1
La solución de Neurino es bastante simple en realidad. Hablando de sed levemente complicado, esto puede interesarle .
Gilles 'SO- deja de ser malvado'

2

Sin embargo, sería otra herramienta de línea de comando común ed, que por defecto modifica los archivos en su lugar y, por lo tanto, deja los permisos de los archivos sin modificar (para obtener más información, edconsulte Edición de archivos con el editor de texto ed desde scripts )

str='
foo bar \
bash 1 \
bash 2 \
bash 3 \
bash 4 \
baz
dude \
happy
xxx
vvv 1 \
vvv 2 \
CCC
'

# We are using (1,$)g/re/command-list and (.,.+1)j to join lines ending with a '\'
# ?? repeats the last regex search.
# replace ',p' with 'wq' to edit files in-place
# (using Bash and FreeBSD ed on Mac OS X)
cat <<-'EOF' | ed -s <(printf '%s' "$str")
H
,g/\\$/s///\
.,.+1j\
??s///\
.,.+1j
,p
EOF

2

Usando el hecho de que readen el shell interpretará barras invertidas cuando se usa sin -r:

$ while IFS= read line; do printf '%s\n' "$line"; done <file
foo bar bash baz
dude happy

Tenga en cuenta que esto también interpretará cualquier otra barra diagonal inversa en los datos.


No No eliminará toda la barra invertida. Prueba cona\\b\\\\\\\\\\\c
Isaac

@ Isaac Ah, ¿tal vez debería haber dicho "interpretar cualquier otra barra invertida"?
Kusalananda

1

Una solución simple (r) que carga todo el archivo en la memoria:

sed -z 's/\\\n//g' file                   # GNU sed 4.2.2+.

O uno todavía corto que funciona entendiendo líneas (salida) (sintaxis GNU):

sed ':x;/\\$/{N;bx};s/\\\n//g' file

En una línea (sintaxis POSIX):

sed -e :x -e '/\\$/{N;bx' -e '}' -e 's/\\\n//g' file

O use awk (si el archivo es demasiado grande para caber en la memoria):

awk '{a=sub(/\\$/,"");printf("%s%s",$0,a?"":RS)}' file

0

La versión de Mac basada en la solución @Giles se vería así

sed ':x
/\\$/{N; s|\\'$'\\n||; tx
}' textfile

Donde la diferencia principal es cómo se representan las nuevas líneas, y la combinación de más en una línea lo divide


-1

Puede usar cpp, pero produce algunas líneas vacías donde fusionó la salida, y alguna introducción que elimino con sed; tal vez también se puede hacer con cpp-flags y opciones:

echo 'foo bar \
bash \
baz
dude \
happy' | cpp | sed 's/# 1 .*//;/^$/d'
foo bar bash baz
dude happy

¿Estás seguro de que cpp es una solución? En su ejemplo, la echocadena con comillas dobles ya genera texto enderezado, por lo que no cpptiene sentido. (Esto también se aplica a su sedcódigo). Si coloca la cadena entre comillas cppsimples , simplemente elimina las barras invertidas pero no concatena las líneas. (La concatenación con cppfuncionaría si no hubiera espacio antes de las barras diagonales inversas, pero luego las palabras separadas se unirían sin separadores.)
manatwork

@manatwork: ¡Outsch! :) Me sorprendió que el comando sed funcionara, pero por supuesto, no era el comando sed, sino que el bash mismo interpreta la barra diagonal inversa como la continuación de la línea anterior.
usuario desconocido

Usar cppasí todavía no concatena las líneas para mí. Y el uso de seddefinitivamente es innecesario. Uso cpp -P: " -PInhibir la generación de marcadores de línea en la salida del preprocesador". - man cpp
manatwork

Tu comando no funciona para mí: cpp: “-P: No such file or directory cpp: warning: '-x c' after last input file has no effect cpp: unrecognized option '-P:' cpp: no input filesA cpp --versionrevela cpp (Ubuntu 4.4.3-4ubuntu5.1) 4.4.3: ¿qué? Ubuntu está parcheando cpp? ¿Por qué? Hubiera esperado leer GNU ...
usuario desconocido

Interesante. De cpphecho , Ubuntu concatena las líneas y deja algunos espacios en blanco. Aún más interesante, la misma versión 4.4.3-4ubuntu5.1 aquí acepta -P. Sin embargo, solo elimina los marcadores de línea, las líneas vacías permanecen.
manatwork
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.