¿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
Cory Klein
fuente
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
Tenga en cuenta que está en las sedPreguntas frecuentes
Stéphane Chazelas

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
neurino
fuente
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//'
camh
fuente
corto y simple, me gusta ese +1 Y no pidió explícitamente sed o awk
rudolfson
17

Aquí hay una solución awk. Si una línea termina con a \, retire la barra diagonal inversa e imprima la línea sin nueva línea de terminación; de lo contrario, imprima la línea con una nueva línea final.

awk '{if (sub(/\\$/,"")) printf "%s", $0; else print $0}'

Tampoco es tan malo en sed, aunque awk es obviamente más legible.

Gilles 'SO- deja de ser malvado'
fuente
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
#######
Peter.O
fuente
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
verdo
fuente
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.

Kusalananda
fuente
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
Isaac
fuente
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

Andy
fuente
-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
usuario desconocido
fuente
¿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