Trasforma l'elenco in riga singola con delimitatore


17

Devo prendere un elenco (carica) di indirizzi IP in questo formato:

 134.27.128.0
 111.245.48.0
 109.21.244.0

e trasformali in questo formato con una pipe in mezzo (IP creati)

134.27.128.0 | 111.245.48.0 | 109.21.244.0 | 103.22.200.0/22

Penso che sia un comando trova e sostituisci come sed ma non riesco a farlo funzionare.


3
Vuoi semplicemente trrispondere alle nuove righe in |pipe? Come <ipfile tr \\n \| >outfile?
Mikeserv,

Lo spazio intorno è |richiesto?
cuonglm

2
@uselesslinuxman - no. Avresti bisogno del reindirizzamento dell'input <. Così <mydoc tr \\n \| >mydoc2. Ma questo non ti darà gli spazi. Per quelli, probabilmente la soluzione più veloce èpaste -d' | ' mydoc /dev/null /dev/null >mydoc2
mikeserv

1
@mikeserv: non credo che funzionerà. pastescrive le righe corrispondenti a ciascun file. Senza -s, otterrai il numero di righe che hai nel file.
cuonglm

2
@ val0x00ff: ti invito a leggere unix.stackexchange.com/q/169716/38906
cuonglm

Risposte:


16

Usando sed, sulla base di famosi sed one-liners spiegato, Parte I: : 39. Aggiunge una riga alla successiva se finisce con un backslash "\" (tranne che qui si ignora la parte che riguarda il backslash, e sostituire le \nnuove righe con il |separatore richiesto ):

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

dovrebbe produrre in mydoc2

134.27.128.0 |  111.245.48.0 |  109.21.244.0

@don_crissti mi dispiace che sia stato un tipo - corretto, grazie
steeldriver

Questo in realtà non funziona in pratica, sfortunatamente. Almeno, non per flussi illimitati. Quando lo fai, devi ingoiare l'intero input una riga alla volta e non puoi scriverne nemmeno un singolo byte per l'output fino a quando non hai digerito tutto, trasformandolo in un'unica riga. È ingombrante e soggetto a segfault.
Mikeserv,

Un milione di IP è <16 milioni, avresti bisogno di un elenco terribilmente grande per superare i limiti qui. L'uso della ricerca per il rilevamento di eof è più problematico, poiché questo eseguirà O (N ^ 2) sulla dimensione del file di input. sed 'H;1h;$!d;x;s/\n/ | /g'è lineare.
jill

@jthill - POSIX garantisce solo uno sedspazio di pattern di 8K; questo è molto meno di 16 milioni.
Mikeserv,

9

Ero curioso di vedere come alcuni di questi (+ alcune alternative) funzionino in modo rapido con un file piuttosto grande ( 163MiB, uno IPper riga, ~ 13 milioni di righe):

wc -l < iplist
13144256

Risultati (con sync; echo 3 > /proc/sys/vm/drop_cachesdopo ogni comando; ho ripetuto i test - in ordine inverso - dopo un paio d'ore ma le differenze erano trascurabili; nota anche che sto usando gnu sed):

steeldriver :
molto lento. Interrotto dopo due minuti di attesa ... quindi nessun risultato per questo.

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

e

val0x00ff :

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

real    3m4.321s

il che significa 184.321s. Non sorprende che questo sia 200 volte più lento della soluzione di Mikeserv .


Ecco alcuni altri modi 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 combinazione di testa + incolla + tr + gatto:

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

real    0m0.991s

Se hai GNU coreutilse se il tuo elenco di IP non è davvero enorme (diciamo fino a 50000 IP) puoi anche farlo con pr:

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

dove

-$(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

ad es. per un file a 6 righe:

134.28.128.0
111.245.28.0
109.245.24.0
128.27.88.0
122.245.48.0
103.44.204.0

il comando:

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

uscite:

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 - potresti aggiungere anche il suggerimento nella domanda di @ val0x00ff per il while ... readloop? Sono curioso di vedere cosa si traduce in un benchmark in 163k read()e write()chiamate. Ottima risposta, a proposito.
Mikeserv,

1
@mikeserv - nessun problema, lo farò (sarà comunque molto lento ).
don_crissti,

È un collegamento davvero interessante. Mi piace soprattutto che l'autore offra anche un link a un benchmark di 6 anni simile. Notate che sedsembra aver migliorato la sua posizione in quel momento (e probabilmente aveva solo pochissime modifiche al suo motore regexp) ma grepsembra essere drammaticamente in ritardo nelle sue prestazioni (specialmente per le linee più lunghe) ? Mi chiedo se le perlaggiunte al suo motore abbiano qualche influenza su quei risultati ... È anche bello che dashnon sia spaventoso . Il bashqui probabilmente sarebbe molto più lento con il comune IFS=anteposto.
Mikeserv,

hmm ... quel link è un altro indicatore forte che ho davvero bisogno di allacciare e imparare C in modo da poter finalmente iniziare a usare lexcorrettamente.
Mikeserv,

8

Puoi usare awk :

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

ORS=' | 'impostare il separatore del record di output su ' | 'anziché su newline.

o modifica sul posto con perl:

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

grazie uomo. Ho appena imparato come pastefunziona. molto apprezzato.
Mikeserv,

@mikeserv: Prego. come indicato da don_crissti nel suo benchmark, la pastesoluzione è la più veloce.
cuonglm

L'output non termina con una nuova riga. Potrebbe essere necessario sostituire ORS=""all'interno del ENDblocco in ORS="\n"modo da farlo.
phk,

4

Quindi ho sbagliato tutto, e questa domanda mi ha insegnato molto paste. Come osserva correttamente cuonglm, a meno che tu non abbia pasteun file in -serial, finirai sempre con l'ultima \newline della tua lista di file aggiunta all'output mentre viene scritta. Mi sbagliavo nella convinzione che il paste -scomportamento fosse la sua modalità predefinita - e questo è un malinteso che, a quanto pare, busybox pasteera felice di rafforzare. Il seguente comando funziona come pubblicizzato con busybox:

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

Tuttavia, non funziona secondo le specifiche. Una corretta implementazione pasteaggiungerebbe comunque una \newline finale per ogni sequenza scritta. Tuttavia, non è un grosso problema dopo tutto:

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

@don_crissti - dangit. tablet stupido. Immagino che la cosa ovvia da fare siano due paste.
Mikeserv,

1
Beh, avevo prin mente, ma a quanto pare si esaurisce il vapore con enormi file di input, quindi non potrei effettivamente testare la velocità ma con file di lunghezza ragionevole funziona bene. La tua soluzione è di gran lunga la più veloce (nessuna sorpresa - pasteè davvero veloce), vedi il mio post.
don_crissti,

4

one-liner con tr e sed:

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

Perché eliminare 2 pipe finali? Ci saranno solo 2 alla fine se l'input termina con una riga vuota (due nuove righe).
JigglyNaga,

3

Utilizzare vim:

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

Spiegazione:

-n disabilita il file di scambio

-u NONE viene utilizzato per saltare tutte le inizializzazioni.

-c {command} eseguire i comandi dopo aver letto il file.

1,$-1s/\n/ | /gè s/\n/ | /g(sostituisci newline con spazio tubo spazio) per l'intervallo 1,$-1s(dalla prima riga all'ultima riga - 1)

wq! forzare la scrittura e la chiusura


Nota:

A seconda della grandezza del tuo file, questa potrebbe essere una cattiva idea.


1
Ringrazio tutti voi, perché praticamente ognuno di questi comandi funziona per quello che devo ottenere. So dove venire ora se (quando) sono bloccato di nuovo. Grazie
uselesslinuxman

2

Attraverso il pitone.

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

gli spazi prima printerano molto importanti.


2

Eccone un altro che usa xxd

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

2

Per completezza, ecco un'altra awksoluzione basata su questo, questo non sta usando ORSaffatto:

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

Per una spiegazione, consultare il mio post su /unix//a/338121/117599 .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.