Convertir la lista en una sola línea con delimitador


17

Tengo que tomar una lista (carga) de direcciones IP en este formato:

 134.27.128.0
 111.245.48.0
 109.21.244.0

y convertirlos a este formato con una tubería intermedia (IP compuestas)

134.27.128.0 | 111.245.48.0 | 109.21.244.0 | 103.22.200.0/22

Creo que es un comando para buscar y reemplazar, sedpero no puedo hacer que funcione.


3
¿Solo quieres trcolocar nuevas líneas en |tuberías? Al igual que <ipfile tr \\n \| >outfile?
mikeserv 01 de

¿Se |requiere el espacio alrededor ?
cuonglm 01 de

2
@uselesslinuxman - no. Necesitaría la redirección de entrada <. Por lo tanto <mydoc tr \\n \| >mydoc2. Pero eso no te conseguirá los espacios. Para aquellos, probablemente la solución más rápida espaste -d' | ' mydoc /dev/null /dev/null >mydoc2
mikeserv

1
@mikeserv: No creo que funcione. pasteescribe líneas correspondientes a cada archivo. Sin -s, obtendrá el número de líneas que tiene en el archivo.
Cuonglm 01 de

2
@ val0x00ff: los invito a leer unix.stackexchange.com/q/169716/38906
cuonglm el

Respuestas:


16

El uso de sed, sobre la base de famosos Sed de una sola línea explicado, Parte I: : 39. Adjuntar una línea a la siguiente si termina con una barra invertida "\" (salvo que aquí no hacemos caso de la parte de la barra invertida, y reemplazar las \nnuevas líneas con el |separador requerido ):

sed -e :a -e '$!N; s/\n/ | /; ta' mydoc > mydoc2

debe producir en mydoc2

134.27.128.0 |  111.245.48.0 |  109.21.244.0

@don_crissti lo siento, fue un tipo - corregido, gracias
steeldriver

Esto en realidad no funciona en la práctica, desafortunadamente. Al menos, no para transmisiones ilimitadas. Cuando hace esto, tiene que tragar la totalidad de su entrada una línea a la vez y no puede escribir ni un solo byte de la misma hasta que haya digerido todo, todo transformado en una sola línea. Es difícil de manejar y propenso a la falta de seguridad.
mikeserv

Un millón de IP es <16M, necesitaría una lista terriblemente grande para superar los límites aquí. Usar la búsqueda de detección de eof es más problemático, ya que esto ejecutará O (N ^ 2) en el tamaño del archivo de entrada. sed 'H;1h;$!d;x;s/\n/ | /g'es lineal
2015

@jthill: POSIX solo garantiza un sedespacio de patrón de 8K; eso es mucho menos de 16 millones.
mikeserv

9

Tenía curiosidad por ver cómo algunos de estos (+ algunas alternativas) funcionan de manera rápida con un archivo bastante grande ( 163MiB, uno IPpor línea, ~ 13 millones de líneas):

wc -l < iplist
13144256

Resultados (con sync; echo 3 > /proc/sys/vm/drop_cachesdespués de cada comando; repetí las pruebas, en orden inverso, después de un par de horas, pero las diferencias fueron insignificantes; también tenga en cuenta que estoy usando gnu sed):

conductor de acero :
muy lento. Cancelado después de dos minutos de espera ... así que no hay resultado para este.

cuonglm :

awk 'FNR!=1{print l}{l=$0};END{ORS="";print l}' ORS=' | ' iplist

real    0m3.672s

perl -pe 's/\n/ | / unless eof' iplist

real    0m12.444s

mikeserv :

paste -d\  /dev/null iplist /dev/null | paste -sd\| - 

real    0m0.983s

jthill :

sed 'H;1h;$!d;x;s/\n/ | /g' iplist

real    0m4.903s

Avinash Raj :

time python2.7 -c'
import sys
with open(sys.argv[1]) as f:
    print " | ".join(line.strip() for line in f)' iplist

real    0m3.434s

y

val0x00ff :

while read -r ip; do printf '%s | ' "$ip"; done < iplist

real    3m4.321s

cuyos medios 184.321s. Como era de esperar, esto es 200 veces más lento que la solución de mikeserv .


Aquí hay otras formas con
awk:

awk '$1=$1' RS= OFS=' | ' iplist

real    0m4.543s

awk '{printf "%s%s",sep,$0,sep=" | "} END {print ""}' iplist

real    0m5.511s

perl:

perl -ple '$\=eof()?"\n":" | "' iplist

real    0m9.646s

xargs:

xargs <iplist printf ' | %s' | cut -c4-

real    0m6.326s

una combinación de head + paste + tr + cat:

{ head -n -1 | paste -d' |' - /dev/null /dev/null | tr \\n \ ; cat ; } <iplist

real    0m0.991s

Si tiene GNU coreutilsy si su lista de IP no es realmente enorme (digamos hasta 50000 IP), también puede hacer esto con pr:

pr -$(wc -l infile) -tJS' | ' -W1000000 infile >outfile

dónde

-$(wc -l infile)         # no. of columns (= with no. of lines in your file)
-t                       # omit page headers and trailers
-J                       # merge lines
-S' | '                  # separate columns by STRING
-W1000000                # set page width

por ejemplo, para un archivo de 6 líneas:

134.28.128.0
111.245.28.0
109.245.24.0
128.27.88.0
122.245.48.0
103.44.204.0

El comando:

pr -$(wc -l <infile) -tJS' | ' -W1000 infile

salidas:

134.28.128.0 | 111.245.28.0 | 109.245.24.0 | 128.27.88.0 | 122.245.48.0 | 103.44.204.0

don: ¿podría agregar también la sugerencia en la pregunta de @ val0x00ff para el while ... readbucle? Tengo curiosidad por ver a qué se traduce 163k read()y write()llamadas en un punto de referencia. Gran respuesta, por cierto.
mikeserv

1
@mikeserv: no hay problema, lo haré (aunque será muy lento ).
don_crissti

Ese es un enlace realmente genial. Me gusta especialmente que el autor también ofrezca un enlace a un punto de referencia similar de 6 años. ¿Te das cuenta de que sedparece haber mejorado su posición en ese momento (y probablemente solo hubo unos pocos cambios en su motor regexp) pero grepparece haberse retrasado drásticamente en su rendimiento (especialmente para las líneas más largas) ? Me pregunto si las perladiciones a su motor tienen alguna relación con esos resultados ... También es bueno que dashno sea abismal . El bashaquí probablemente sería mucho más lento con lo común IFS=antepuesto.
mikeserv

hmm ... ese enlace es otro indicador fuerte de que realmente necesito abrocharme y aprender C para que finalmente pueda comenzar a usarlo lexcorrectamente.
mikeserv

8

Puedes usar awk :

awk 'FNR!=1{print l}{l=$0};END{ORS="";print l}' ORS=' | ' file > new_file

ORS=' | 'establezca el separador de registro de salida en ' | 'lugar de nueva línea.

o editar en el lugar con perl:

perl -pe 's/\n/ | / unless eof' file

gracias hombre. Acabo de aprender cómo pastefunciona. muy apreciado.
mikeserv 01 de

@mikeserv: De nada. Como don_crissti muestra en su punto de referencia, la pastesolución es la más rápida.
Cuonglm 02 de

La salida no termina con una nueva línea. Es posible que deba reemplazar ORS=""dentro del ENDbloque ORS="\n"para que así sea.
phk

4

Así que me equivoqué todo, y esta pregunta me ha enseñado mucho paste. Como cuonglm señala correctamente, a menos que esté pasteen un archivo in -serial, siempre terminará con la última línea \nelectrónica de su lista de archivos añadida a la salida a medida que se escribe. Me equivoqué al creer que el paste -scomportamiento era su modo predeterminado, y este es un concepto erróneo que, aparentemente, busybox pasteestaba feliz de reforzar. El siguiente comando funciona como se anuncia con busybox:

paste -d'|  ' - - infile </dev/null >outfile

Sin embargo, no funciona según las especificaciones. Una implementación correcta pasteaún \nagregaría una línea electrónica final para cada secuencia escrita. Aún así, eso no es gran cosa después de todo:

paste -d\  - infile - </dev/null | paste -sd\| - >outfile

@don_crissti - dangit. estúpida tableta Supongo que lo más obvio es hacer dos pastas.
mikeserv 01 de

1
Bueno, tenía pren mente, pero aparentemente se queda sin vapor con grandes archivos de entrada, por lo que no pude probar la velocidad, pero con archivos de longitud razonable funciona bien. Su solución es, con mucho, la más rápida (no es de extrañar, pastees realmente rápida), vea mi publicación.
don_crissti

4

one-liner con tr y sed:

cat file | tr '\n' '|' | sed 's/||$/\n/'
134.27.128.0|111.245.48.0|109.21.244.0

¿Por qué eliminar 2 tuberías finales? Solo habrá 2 al final si la entrada terminó con una línea en blanco (dos líneas nuevas).
JigglyNaga

3

Utilizar vim:

vim -n -u NONE -c '1,$-1s/\n/ | /g|wq!' data

Explicación:

-n desactivar el archivo de intercambio

-u NONE se usa para omitir todas las inicializaciones.

-c {command} ejecutar comandos después de que se haya leído el archivo.

1,$-1s/\n/ | /ges s/\n/ | /g(reemplace nueva línea con espacio de tubería) para el rango 1,$-1s(1ra línea a la última línea - 1)

wq! forzar la escritura y salir


Nota:

Dependiendo de qué tan grande sea realmente su archivo, esto puede ser una mala idea.


1
Les agradezco a todos, porque básicamente cada uno de estos comandos funciona para lo que necesito lograr. Sé a dónde ir ahora si (cuando) estoy atrapado nuevamente. Gracias
uselesslinuxman

2

A través de pitón.

$ python -c '
import sys
with open(sys.argv[1]) as f:
    print " | ".join(line.strip() for line in f)' file

espacios antes printera muy importante.


2

Aquí hay otro usando xxd

xxd -c1 -ps data | sed '$!s/0a/207c20/' | xxd -r -ps

2

Para completar, aquí hay otra awksolución basada en esta, esta no está usando el ORS:

awk 'BEGIN { ORS="" } { print p$0; p=" | " } END { print "\n" }' file > new_file

Para obtener una explicación, consulte mi publicación en /unix//a/338121/117599 .

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.