Prägnantes und portables "Join" in der Unix-Befehlszeile


77

Wie kann ich mehrere Zeilen zu einer Zeile zusammenfügen, mit einem Trennzeichen, in dem sich die Zeichen für neue Zeilen befanden, und ein nachfolgendes Trennzeichen vermeiden und optional leere Zeilen ignorieren?

Beispiel. Stellen Sie sich eine Textdatei foo.txtmit drei Zeilen vor:

foo
bar
baz

Die gewünschte Ausgabe ist:

foo,bar,baz

Der Befehl, den ich jetzt benutze:

tr '\n' ',' <foo.txt |sed 's/,$//g'

Im Idealfall wäre es ungefähr so:

cat foo.txt |join ,

Was ist:

  1. der tragbarste, prägnanteste und lesbarste Weg.
  2. Die präziseste Art, nicht standardmäßige Unix-Tools zu verwenden.

Natürlich könnte ich etwas schreiben oder einfach einen Alias ​​verwenden. Aber ich bin interessiert, die Optionen zu kennen.


Antworten:


130

Vielleicht ein wenig überraschend, pasteist ein guter Weg, dies zu tun:

paste -s -d","

Dies behandelt nicht die von Ihnen erwähnten Leerzeilen. Führen Sie dazu grepzuerst Ihren Text durch :

grep -v '^$' | paste -s -d"," -

@codaddict Ich auch nicht, aber ich muss zugeben, dass ich es überhaupt nicht intuitiv finde - ich muss immer die Manpages darauf überprüfen. Ich bin auf jeden Fall gespannt, was andere vorschlagen.
Michael J. Barber

Es gibt andere Möglichkeiten, aber keine schönere (und die lustigen sind ein bisschen schüchtern).
Sorpigal

Es scheint leere Zeilen nicht zu ignorieren, aber das ist immer noch sehr schön und funktioniert für meinen Anwendungsfall. Vielen Dank!
Hintern

13
Um die Portabilität zu verbessern, sollten Sie -am Ende des pasteBefehls hinzufügen, wann immer er gelesen werden soll stdin. (Einige Versionen von pastesolchen stdin-
BSDs

2
Danke für den Hinweis über paste! Mir ist aufgefallen, dass nur Einzelzeichen-Trennzeichen zulässig sind, und dies ist \tstandardmäßig der Fall. Um längere Begrenzer zu erreichen (z. B. , ):cat foo.txt | paste -s | sed 's/\t/, /g'
Arild

12

Diese sedeinzeilige sollte funktionieren -

sed -e :a -e 'N;s/\n/,/;ba' file

Prüfung:

[jaypal:~/Temp] cat file
foo
bar
baz

[jaypal:~/Temp] sed -e :a -e 'N;s/\n/,/;ba' file
foo,bar,baz

Um leere Zeilen zu verarbeiten, können Sie die leeren Zeilen entfernen und an den obigen Einzeiler weiterleiten.

sed -e '/^$/d' file | sed -e :a -e 'N;s/\n/,/;ba'

Eine Erklärung wäre schön!
Tejas Kale

1
Es ist klarer, zwei -e-Ausdrücke zu einem zu kombinieren sed -e ':a; N; s/\n/,/; ba'. Dies ist jedoch immer noch eine O (n²) -Methode, da sed jedes Mal eine Substitution durchführt, wenn eine neue Zeile hinzugefügt wird. sed -e ':a; N; $!ba; s/\n/,/g'ist linear und wird nur einmal ersetzt, nachdem alle Zeilen an den Musterraum von sed angehängt wurden. $!babedeutet "wenn es die letzte Zeile ist ($), springe nicht (!) zu (b) Label: a (a), breche die Schleife"
zhazha

8

Wie wäre es mit xargs?

für Ihren Fall

$ cat foo.txt | sed 's/$/, /' | xargs

Achten Sie auf die maximale Länge der Eingabe des Befehls xargs. (Dies bedeutet, dass sehr lange Eingabedateien nicht verarbeitet werden können.)


Ich fand die -L Flagge auf xargs hilfreich -L 50für 50 Artikel pro Zeile.
jmunsch

6

Perl:

cat data.txt | perl -pe 'if(!eof){chomp;$_.=","}'

oder doch überraschenderweise kürzer und schneller:

cat data.txt | perl -pe 'if(!eof){s/\n/,/}'

oder, wenn Sie möchten:

cat data.txt | perl -pe 's/\n/,/ unless eof'

2
Das Schöne daran ist, dass Sie anstelle eines einfachen Kommas eine beliebige Zeichenfolge verwenden können. Die akzeptierte Antwort ist weniger vielseitig. Ich mag besonders die letzte Iteration, obwohl ich sie so geschrieben hätte: perl -pe 's/\n/,/ unless eof' data.txt (keine Notwendigkeit für die falsche Katze).
Mike S

4

Nur zum Spaß, hier ist eine integrierte Lösung

IFS=$'\n' read -r -d '' -a data < foo.txt ; ( IFS=, ; echo "${data[*]}" ; )

Sie können printfanstelle von verwenden, echowenn der nachfolgende Zeilenumbruch ein Problem darstellt.

Dies funktioniert, indem IFSdie Trennzeichen, readdie aufgeteilt werden, nur auf Zeilenumbruch und nicht auf andere Leerzeichen gesetzt werden. Anschließend wird angegeben, dass readder Lesevorgang nicht beendet werden soll, bis ein nulWert erreicht ist , anstatt der normalerweise verwendeten Zeilenumbruchlinie, und jedes gelesene Element in das Array ( -a) eingefügt wird. Daten. Dann wird in einem Subshell , um nicht die clobber IFSdes interaktiv Shell, setzten wir IFSauf ,und erweitern das Array mit *, die jedes Element in dem Array mit dem ersten Zeichen in abgrenztIFS


1
Interessant, jedoch ist die Portabilität nicht ausgezeichnet, da der -dreine shShell- readBefehl keine Option enthält .
Mykhal

@mykhal: Stimmt. Allerdings bashkann auf vielen Systemen gefunden werden, so hat es einen gewissen Nutzen. Wenn Sie möchten, dass Portabilitäts-Arrays wahrscheinlich auch nicht verfügbar sind, können Sie einfach eine whileSchleife verwenden, um das Fehlen von zu umgehen -d. Für eine einwandfreie , tragbare All-builtins Version würde wollen Sie so etwas wie , c= ; while IFS= read -r d ; do if ! [ -z "$d" ] ; then printf "$c$d" ; fi c=, ; done < foo.txtaber es immer noch nicht für readdas weiß -r, aber das könnte weggelassen werden, und nimmt einen eingebauten printf, so echowahrscheinlich besser ist es , wenn die Effizienz wichtig ist. Trotzdem ist die akzeptierte Antwort viel besser!
Sorpigal

0

Ich musste etwas Ähnliches erreichen, indem ich eine durch Kommas getrennte Liste von Feldern aus einer Datei druckte, und war zufrieden damit, STDOUT an xargsund rubywie folgt weiterzuleiten :

cat data.txt | cut -f 16 -d ' ' | grep -o "\d\+" | xargs ruby -e "puts ARGV.join(', ')"

0

Ich hatte eine Protokolldatei, in der einige Daten in mehrere Zeilen aufgeteilt waren. In diesem Fall war das letzte Zeichen der ersten Zeile das Semikolon (;). Ich habe diese Zeilen mit den folgenden Befehlen verbunden:

for LINE in 'cat $FILE | tr -s " " "|"'
do
    if [ $(echo $LINE | egrep ";$") ]
    then
        echo "$LINE\c" | tr -s "|" " " >> $MYFILE
    else
        echo "$LINE" | tr -s "|" " " >> $MYFILE
    fi
done

Das Ergebnis ist eine Datei, in der Zeilen, die in der Protokolldatei geteilt wurden, eine Zeile in meiner neuen Datei waren.


0

Verwenden Sie Folgendes, um die Zeilen mit vorhandenem Leerzeichen zu verbinden ex(wobei auch Leerzeilen ignoriert werden):

ex +%j -cwq foo.txt

Wenn Sie die Ergebnisse in der Standardausgabe drucken möchten, versuchen Sie Folgendes:

ex +%j +%p -scq! foo.txt

Verwenden Sie +%j!anstelle von Zeilen ohne Leerzeichen +%j.

Um ein anderes Trennzeichen zu verwenden, ist es etwas schwieriger:

ex +"g/^$/d" +"%s/\n/_/e" +%p -scq! foo.txt

Dabei g/^$/d(oder v/\S/d) werden Leerzeilen entfernt und es s/\n/_/handelt sich um eine Ersetzung, die im Wesentlichen genauso funktioniert wie die Verwendung sed, jedoch für alle Zeilen ( %). Wenn das Parsen abgeschlossen ist, drucken Sie den Puffer ( %p). Und schließlich wird der Befehl -cq!vi q!ausgeführt, der im Grunde genommen ohne Speichern beendet wird ( -sum die Ausgabe stumm zu schalten ).

Bitte beachten Sie, dass dies exgleichbedeutend ist mit vi -e.

Diese Methode ist ziemlich portabel, da die meisten Linux / Unix-Geräte standardmäßig mit ex/ ausgeliefert viwerden. Und es ist kompatibler als die Verwendung, sedwenn in-place parameter ( -i) keine Standarderweiterung ist und das Dienstprogramm selbst stärker auf Streams ausgerichtet ist, daher ist es nicht so portabel.


-1

Meine Antwort lautet:

awk '{printf "%s", ","$0}' foo.txt

printfreicht. Wir müssen das -F"\n"Feldtrennzeichen nicht ändern.


1
Dies fügt am Anfang der Ausgabe ein falsches Komma hinzu. -1 für nicht testen.
Mike S
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.