Klicken Sie auf einen Einzeiler, um eine Liste wie "1: 2, 3, 4, 5" in "1.2, 1.3, 1.4, 1.5" zu konvertieren.

7

Angenommen, ich habe eine Datei, die ungefähr so ​​aussieht:

23: a, b, c, d
24: b, d, f
25: c, g

und ich möchte folgende Ausgabe erhalten:

23.a
23.b
23.c
23.d
24.b
24.d
24.f
25.c
25.g

Natürlich ist es nicht allzu schwer, einfach etwas rauszuschlagen, aber ich habe mich gefragt, ob es einen glatten Einzeiler gibt, der so etwas wie awk verwendet.

Daniel McLaury
quelle

Antworten:

19

Vielleicht so etwas wie:

sed 's/: /./;s/\(\([^.]*\.\)[^,]*\), /\1\
\2/;P;D'

Das sind zwei Zeilen ( \<LF>können durch \neinige sedImplementierungen ersetzt werden).

Der DBefehl ist eine Möglichkeit , zu implementieren , während Schleifen in sed. Es entfernt die erste Zeile des Musterraums und solange noch etwas im Musterraum übrig ist, beginnt alles von vorne mit dem, was noch übrig ist. Das Obige kann also wie folgt gelesen werden:

do {
  - change ": " to "." so we start with "23.a, b, c"
  - change "23.x, y, z" to "23.x\n23.y, z"
  - print the first line ("23.x"): P
  - remove it
} while (pattern space is not empty)

Wir brauchen nicht den ersten sBefehl, um Teil der Schleife zu sein, aber um dies zu vermeiden, müssten wir einen ausführlicheren Schleifentyp verwenden, wie die Verwendung von Beschriftungen ( :) und Verzweigungsbefehlen ( b, t).

Stéphane Chazelas
quelle
3
Schön, aber wir mögen Erklärungen dafür, dass der Code ...
Bananguin
Dies gewinnt für schiere Klugheit.
Daniel McLaury
10

Egal, ich habe mich gerade an die awk split Funktion erinnert, was dies ziemlich einfach macht.

awk -F ":" '{
  split($2, ps, ",");
  for (i in ps) {
    gsub(" ", "",ps[i]);
    print $1 "." ps[i];
  }
}'

(Der gsub entfernt fremde Leerzeichen.)

Vielen Dank für die anderen Antworten.

Daniel McLaury
quelle
Ich denke das gleiche wie @Stephane Chazelas, aber stumpfer: awk -F ':' '{gsub (/ [^ az] /, ",", $ 2); gsub (/, + /, "\ n" $ 1 " . ", $ 2); gsub (/ ^ \ n /," ", $ 2); print $ 2} '
XzKto
1
Normalerweise bevorzuge ich FSin solchen Fällen eine komplexere : awk -F '[:,]' '{for(i=2;i<=NF;i++)printf"%s%s\n",$1,$i}'.
Manatwork
1
Beachten Sie, dass nicht alle awkImplementierungen garantieren, dass Ihr i in psAusdruck zu einer Schleifenreihenfolge durch das Array führt. Zum Beispiel mawk, aber gawknicht.
Manatwork
Das ist bizarr ... welchen möglichen Vorteil hat das Durchlaufen in einer anderen Reihenfolge?
Daniel McLaury
1
awk's Arrays sind assoziative Arrays und assoziative Arrays funktionieren normalerweise so (zum Beispiel HashMapin Java, Hash in Perl, diktieren in Python, Hashin Ruby vor 1.9.2, Array in Tcl). Das liegt an der internen Darstellung der Daten. Software Engineering hat eine verwandte Frage: Ist ein assoziatives Array bestellt? ,
Manatwork
10

Hier ist ein Perl:

 perl -nle '/(.+?):\s*(.+)/; print "$1.$_" for split(/[,\s]+/,$2);' foo.txt

ERLÄUTERUNG:

  • perl -nle: Dies weist Perl an, die Eingabedatei zeilenweise zu analysieren ( -n), das als Argument angegebene Skript auszuführen -eund \njeder gedruckten Zeichenfolge ( -l) eine neue Zeile ( ) hinzuzufügen .

  • /(.+?):\s*(.+)/: Ordnen Sie die ersten Zeichen bis zum ersten Doppelpunkt zu, gefolgt von 0 oder mehr Leerzeichen ( :\s*) und dem Rest der Zeile. Die Klammern sind Perl-Syntax zum Erfassen von Mustern, die beiden Übereinstimmungen werden als $1und gespeichert $2.

  • split(/[,\s]*/,$2);: Dies teilt $2(das zweite übereinstimmende Muster aus der obigen Übereinstimmungsoperation) an ,und / oder Leerzeichen auf und erstellt ein anonymes Array.

  • print "$1.$_" for split(): Durchlaufen Sie das anonyme Array, das durch die obige Aufteilung erstellt wurde, speichern Sie jedes Array-Mitglied als $_und drucken Sie es zusammen mit $1(dem ersten im ersten Schritt erfassten Muster) und einem Punkt ..

terdon
quelle
Ich empfehle print "$1.$_\n" for ..."statt map { print "$1.$_\n" } ....
Christoffer Hammarström
Auch mit -ldir nicht brauchen "\n". Könnte aber noch besser zu bedienen sein -Eund say.
Christoffer Hammarström
@ ChristofferHammarström, interessante Empfehlung. Was ist der Grund?
Manatwork
maperstellt und gibt eine Liste von Werten zurück. Hier wird es als foroder verwendet foreach.
Christoffer Hammarström
@ ChristofferHammarström nicht vergessen, dass saydas neu ist (perl> = 5.10 denke ich) und möglicherweise nicht immer verfügbar ist. Ich habe verwendet, mapweil dies ein Einzeiler ist und ich wollte es kürzer. Mir wurde klar, dass es in einer CompSci-Abteilung nicht für die Straße legal ist, aber es macht in diesem Zusammenhang wirklich keinen Unterschied.
Terdon
5

Hier ist ein Ruby:

ruby -ane '$F.drop(1).each{|f| puts $F.first.gsub(":",".")+f.chomp(",")}' <file.txt

Erläuterung

  • ruby -ane: Dies weist Ruby an, adie Zeilen einzeln zu teilen nund edas Argument als Skript auszuführen.

  • In einer Auto-Split-Datei $Fbefindet sich ein Array des Split-Ergebnisses.

  • drop(1)Überspringt das erste Feld (die Zeilennummer) und .eachdurchläuft die folgenden Felder.

  • gsubErsetzt das :und chompentfernt ein nachfolgendes Trennzeichen aus der Zeichenfolge.

Jonas Elfström
quelle
4

Ein awk-Einzeiler, den ich für etwas eleganter halte als die andere awk-Lösung:

awk -F'[:, ]+' '{for(i=2;i<=NF;i++)printf $1"."$i"\n"}' file.in

Es nutzt die Tatsache aus, dass der awk-Feldtrenner ein regulärer Ausdruck ist.

Kevin
quelle
2

Perl:

perl -nE '($first,$rest)=split ": "; say "$first.$_" for split ", ", $rest'

Teilt die Zeile in die erste Zahl und den Rest auf und druckt dann "$first.$_"für jeden der Buchstaben.

Christoffer Hammarström
quelle
2

Wie wäre es mit einem einfachen Bourne-Shell-Skript (meistens):

tr -d ':,' file.txt | while read p r; do for i in $r; do echo "$p.$i"; done; done

Der Befehl "tr" bereinigt nur die Doppelpunkte (:) und Kommas (,) - diese Antwort hängt davon ab, dass die Daten Leerzeichen enthalten (die in den Beispieldaten enthalten sind). Andernfalls müssen Sie sed verwenden, um: und stattdessen in Leerzeichen zu konvertieren von tr).

Die Ausgabe von "tr" wird in die äußere Schleife "beim Lesen ...; do ...; done" geleitet, die beim ersten Auftreten von Leerzeichen (oder besser gesagt beim Inhalt von "$ IFS" Zeilen liest und in zwei Teile zerlegt "- das Shell-Eingabefeldtrennzeichen (standardmäßig Leerzeichen), wobei das Präfix in" $ p "und der Rest der Zeile in" $ r "belassen wird.

Die innere Schleife "for i in ...; do ...; done" bricht dann den Inhalt von "$ r" im Leerzeichen ("$ IFS") auf und setzt jedes Element in "$ i", bevor der Echo-Befehl ausgeführt wird .

BEARBEITEN: siehe Kommentare - Sie brauchen überhaupt kein "tr" ... die Doppelpunkte und Kommas können bereinigt werden, indem Sie sie wie folgt in die IFS-Variable aufnehmen:

OIFS="$IFS"; IFS=":,       "; while read p r; do 
 for i in $r; do echo "$p.$i"; done; done <file.txt; IFS="$OIFS"

alles innerhalb der Shell erledigt - keine Aufrufe externer Programme ... (es sei denn, Echo ist nicht eingebaut). Beachten Sie, dass IFS = oben ein Leerzeichen und ein Tabulatorzeichen enthält. Beachten Sie auch, dass das $ r in der zweiten for-Schleife keine Anführungszeichen enthält - dies ist absichtlich so, dass die Shell es in Leerzeichen aufteilt.

Murray Jensen
quelle
Sie könnten tr ':,' ' ' | tr -s ' '...
vonbrand
ja - wäre wahrscheinlich billiger als sed, aber du brauchst nicht das zweite tr - der Lesebefehl der Shell behandelt Sequenzen von Leerzeichen als ein einziges Trennzeichen ... und das hat mich nur zum Nachdenken gebracht - wir brauchen kein "tr" überhaupt! Hier ist eine vollständig Bourne-Shell-Skriptlösung: Sie OIFS="$IFS"; IFS=":, "; while read p r; do for i in $r; do echo "$p.$i"; done; done; IFS="$OIFS"müssen die Shell niemals verlassen ... yay!
Murray Jensen