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.

Hintern
quelle
Mögliches Duplikat des Verbindens mehrerer Zeilen zu einer mit Bash
Ciro Santilli 法轮功 冠状 冠状 六四 六四 事件 1.

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"," -
Michael J. Barber
quelle
@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'
jaypal singh
quelle
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.)

plhn
quelle
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'
Mykhal
quelle
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

sorpigal
quelle
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(', ')"
mchail
quelle
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.

Mark Dyer
quelle
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.

Kenorb
quelle
-1

Meine Antwort lautet:

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

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

Duc Chi
quelle
1
Dies fügt am Anfang der Ausgabe ein falsches Komma hinzu. -1 für nicht testen.
Mike S