Liste mit Trennzeichen in eine einzelne Zeile umwandeln

16

Ich muss eine Liste (Ladungen) von IP-Adressen in diesem Format nehmen:

 134.27.128.0
 111.245.48.0
 109.21.244.0

und verwandle sie in dieses Format mit einem Pipe dazwischen (IPs gemacht)

134.27.128.0 | 111.245.48.0 | 109.21.244.0 | 103.22.200.0/22

Ich denke, es ist ein Befehl zum Suchen und Ersetzen, sedaber ich kann es nicht zum Laufen bringen.

uselesslinuxman
quelle
3
Sie möchten trnur Zeilenumbrüche in |Pfeifen umwandeln ? Wie <ipfile tr \\n \| >outfile?
mikeserv
Ist der Platz in der Nähe |erforderlich?
cuonglm
2
@uselesslinuxman - nein. Sie benötigen die Eingabeumleitung <. Also <mydoc tr \\n \| >mydoc2. Aber das bringt dir nicht die Räume. Für diejenigen, die wahrscheinlich die schnellste Lösung istpaste -d' | ' mydoc /dev/null /dev/null >mydoc2
mikeserv
1
@mikeserv: Ich glaube nicht, dass es funktionieren wird. pasteschreibt Zeilen aus jeder Datei. Ohne erhalten -sSie die Anzahl der Zeilen zurück, die Sie in der Datei haben.
Cuonglm
2
@ val0x00ff: Ich lade Sie ein, unix.stackexchange.com/q/169716/38906
cuonglm

Antworten:

15

Mit sed, basierend auf Famous Sed Einzeiler erklärt, Teil I: : 39. Anfügen eine Zeile zum nächsten , wenn es mit einem Backslash endet „\“ (außer wir hier den Teil über den umgekehrten Schrägstrich zu ignorieren, und ersetzen die\n neue Zeilen mit der erforderliches |Trennzeichen):

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

sollte in produzieren mydoc2

134.27.128.0 |  111.245.48.0 |  109.21.244.0
Stahlfahrer
quelle
@don_crissti sorry das war eine
typkorrektur
In der Praxis funktioniert das leider nicht. Zumindest nicht für unbegrenzte Streams. Wenn Sie dies tun, müssen Sie die gesamte Eingabe zeilenweise verschlucken und können nicht einmal ein einziges Byte davon für die Ausgabe schreiben, bis Sie alles verdaut haben - alles in eine einzige Zeile umgewandelt. Es ist unhandlich und anfällig für Segfault.
mikeserv
Eine Million IPs sind <16 Millionen, Sie brauchen eine furchtbar große Liste, um hier Grenzen zu sprengen. Die Suche nach eof-Erkennung ist problematischer, da hierdurch O (N ^ 2) für die Größe der Eingabedatei ausgeführt wird. sed 'H;1h;$!d;x;s/\n/ | /g'ist linear.
1.
@jthill - POSIX garantiert nur einen sedMusterbereich von 8 KB ; das sind viel weniger als 16 Millionen.
mikeserv
9

Ich war neugierig zu sehen, wie einige dieser (+ einige Alternativen) mit einer ziemlich großen Datei ( 163MiB, 1 ) schnell funktionierenIP pro Zeile, ~ 13 Millionen Zeilen) :

wc -l < iplist
13144256

Ergebnisse (mit sync; echo 3 > /proc/sys/vm/drop_caches nach jedem Befehl; ich wiederholte die Tests - in umgekehrter Reihenfolge - nach ein paar Stunden, aber die Unterschiede waren vernachlässigbar; beachte auch, dass ich verwende gnu sed):

Stahlfahrer :
Sehr langsam. Nach zwei Minuten Wartezeit abgebrochen ... also kein Ergebnis für diesen.

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

jdoch :

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

und

val0x00ff :

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

real    3m4.321s

was bedeutet 184.321s. Es überrascht nicht, dass dies 200 Mal langsamer ist als die Lösung von mikeserv .


Hier sind einige andere Möglichkeiten mit
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

eine Kombination von Kopf + Paste + Tr + Katze:

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

real    0m0.991s

Wenn Sie GNU coreutilsIPs haben und Ihre IP-Liste nicht sehr umfangreich ist (sagen wir bis zu 50000 IPs), können Sie dies auch tun mit pr:

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

wo

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

zB für eine 6-zeilige Datei:

134.28.128.0
111.245.28.0
109.245.24.0
128.27.88.0
122.245.48.0
103.44.204.0

der Befehl:

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

Ausgänge:

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_crissti
quelle
don - könntest du auch den vorschlag in der frage von @ val0x00ff für die while ... readschleife hinzufügen ? Ich bin gespannt, was 163k read()und write()Calls in einem Benchmark bedeuten . Tolle Antwort übrigens.
mikeserv
1
@mikeserv - kein Problem, ich mache es (es wird allerdings sehr langsam sein ).
don_crissti
Das ist ein wirklich cooler Link. Besonders gefällt mir, dass der Autor dort auch einen Link zu einem ähnlichen 6 Jahre alten Benchmark anbietet. Ist Ihnen aufgefallen, dass sedsich das Ansehen in dieser Zeit verbessert hat (und wahrscheinlich nur sehr wenige Änderungen an der Regexp-Engine vorgenommen wurden) , grepdie Leistung jedoch dramatisch nachgelassen zu haben scheint (insbesondere bei längeren Warteschlangen ) ? Ich frage mich, ob die perlErgänzungen des Motors einen Einfluss auf diese Ergebnisse haben ... Es ist auch ordentlich, dass dashes nicht miserabel ist . Das bashhier wäre wahrscheinlich weitaus langsamer w / das Gemeinsame IFS=vorangestellt.
mikeserv
hmm ... dieser link ist ein weiterer starker indikator dafür, dass ich mich erst mal anschnallen und C lernen muss, damit ich endlich anfangen kann, es lexrichtig zu benutzen .
mikeserv
8

Sie können awk verwenden :

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

ORS=' | 'Setzen Sie das Trennzeichen für Ausgabedatensätze auf ' | 'anstelle von Newline.

oder direkt bearbeiten mit perl:

perl -pe 's/\n/ | / unless eof' file
cuonglm
quelle
Danke, Mann. Ich habe gerade gelernt, wie es pastefunktioniert. sehr geschätzt.
mikeserv
@mikeserv: Gern geschehen. wie don_crissti in seinem benchmark gezeigt hat, ist die pastelösung die schnellste.
Cuonglm
Die Ausgabe endet nicht mit einem Zeilenumbruch. Möglicherweise müssen Sie ORS=""innerhalb des ENDBlocks durch ersetzen ORS="\n", damit dies funktioniert.
Phk
4

Ich hatte also alles falsch gemacht - und diese Frage hat mir viel beigebracht paste. Wie cuonglm richtig feststellt, wird immer die letzte Zeile aus Ihrer Infile-Liste an die Ausgabe angehängt , solange sie nicht in pasteeiner Datei -sgespeichert \nist. Ich habe mich in der Annahme geirrt, dass das paste -sVerhalten der Standardmodus ist - und dies ist ein Missverständnis, das sich anscheinend busybox pastegerne verstärkt hat. Der folgende Befehl funktioniert wie angekündigt mit busybox:

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

Es funktioniert jedoch nicht nach Spezifikation. Eine korrekt implementierte pastewürde immer noch eine \nnachfolgende ewline für jede geschriebene Sequenz anhängen . Trotzdem ist das keine große Sache:

paste -d\  - infile - </dev/null | paste -sd\| - >outfile
mikeserv
quelle
@don_crissti - verdammt. dumme Tablette. Ich schätze, es liegt auf der Hand, zwei Pasten zu verwenden.
mikeserv
1
Nun, ich hatte prvor, aber anscheinend geht es mit riesigen Eingabedateien zur Neige, so dass ich die Geschwindigkeit nicht testen konnte, aber mit Dateien von angemessener Länge funktioniert es OK. Deine Lösung ist bei weitem die schnellste (keine Überraschung - pasteist wirklich schnell), siehe meinen Beitrag.
don_crissti
3

Nutzen Sie vim:

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

Erläuterung:

-n Auslagerungsdatei deaktivieren

-u NONE wird verwendet, um alle Initialisierungen zu überspringen.

-c {command} Befehle ausführen, nachdem die Datei gelesen wurde.

1,$-1s/\n/ | /gis s/\n/ | /g(Zeilenvorschub durch Leerzeichen ersetzen) für den Bereich 1,$-1s(1. Zeile bis letzte Zeile - 1)

wq! Erzwinge das Schreiben und beende es


Hinweis:

Je nachdem, wie groß Ihre Datei wirklich ist, ist dies möglicherweise eine schlechte Idee.

FloHimself
quelle
1
Ich danke Ihnen allen, denn im Grunde funktioniert fast jeder dieser Befehle für das, was ich erreichen muss. Ich weiß jetzt, wo ich hingehen soll, wenn ich wieder feststecke. Vielen Dank
uselesslinuxman
3

Einzeiler mit tr und sed:

cat file | tr '\n' '|' | sed 's/||$/\n/'
134.27.128.0|111.245.48.0|109.21.244.0
user5337995
quelle
Warum 2 nachlaufende Rohre löschen? Am Ende steht nur 2, wenn die Eingabe mit einer Leerzeile (zwei Zeilenumbrüche) endet.
JigglyNaga
2

Durch Python.

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

Leerzeichen vorher printwar sehr wichtig.

Avinash Raj
quelle
2

Hier ist eine andere Verwendung xxd

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

Der Vollständigkeit halber ist hier eine andere awkLösung, die ORSüberhaupt nicht verwendet wird :

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

Eine Erklärung finden Sie in meinem Beitrag unter /unix//a/338121/117599 .

phk
quelle