Sortieren Sie eine Textdatei nach Zeilenlänge einschließlich Leerzeichen

137

Ich habe eine CSV-Datei, die so aussieht

AS2345, ASDF1232, Mr. Plain Example, 110 Binary Ave., Atlantis, RI, 12345, (999) 123-5555, 1,56
AS2345, ASDF1232, Mrs. Plain Example, 1121110 Ternary st. 110 Binary Ave., Atlantis, RI, 12345, (999) 123-5555, 1,56
AS2345, ASDF1232, Mr. Plain Example, 110 Binary Ave., Liberty City, RI, 12345, (999) 123-5555, 1,56
AS2345, ASDF1232, Mr. Plain Example, 110 Ternary Ave., Some City, RI, 12345, (999) 123-5555, 1,56

Ich muss es nach Zeilenlänge einschließlich Leerzeichen sortieren. Der folgende Befehl enthält keine Leerzeichen. Gibt es eine Möglichkeit, ihn so zu ändern, dass er für mich funktioniert?

cat $@ | awk '{ print length, $0 }' | sort -n | awk '{$1=""; print $0}'
Gnarbarianer
quelle
21
Ich würde wirklich gerne in der Binary Avenue oder der Ternary Street leben, diese Leute würden sicherlich Dingen wie "8192 ist eine runde Zahl"
zustimmen

Antworten:

224

Antworten

cat testfile | awk '{ print length, $0 }' | sort -n -s | cut -d" " -f2-

Oder um Ihre ursprüngliche (möglicherweise unbeabsichtigte) Untersortierung von Zeilen gleicher Länge durchzuführen:

cat testfile | awk '{ print length, $0 }' | sort -n | cut -d" " -f2-

In beiden Fällen haben wir Ihr angegebenes Problem gelöst, indem wir uns für Ihren endgültigen Schnitt von awk entfernt haben.

Linien gleicher Länge - was bei Krawatte zu tun ist:

In der Frage wurde nicht angegeben, ob eine weitere Sortierung für Zeilen mit übereinstimmender Länge gewünscht wurde oder nicht. Ich habe angenommen, dass dies unerwünscht ist, und die Verwendung von -s( --stable) vorgeschlagen, um zu verhindern, dass solche Zeilen gegeneinander sortiert werden, und sie in der relativen Reihenfolge zu halten, in der sie in der Eingabe vorkommen.

(Diejenigen, die mehr Kontrolle über das Sortieren dieser Bindungen wünschen, könnten sich die --keyOption der Sortierung ansehen .)

Warum der Lösungsversuch der Frage fehlschlägt (Aufbau einer awk-Leitung):

Es ist interessant, den Unterschied zwischen:

echo "hello   awk   world" | awk '{print}'
echo "hello   awk   world" | awk '{$1="hello"; print}'

Sie ergeben jeweils

hello   awk   world
hello awk world

In dem entsprechenden Abschnitt des Handbuchs von (gawk) wird nur erwähnt, dass awk die gesamten $ 0 (basierend auf dem Trennzeichen usw.) neu erstellt, wenn Sie ein Feld ändern. Ich denke, es ist kein verrücktes Verhalten. Es hat dies:

"Schließlich gibt es Zeiten, in denen es zweckmäßig ist, awk zu zwingen, den gesamten Datensatz unter Verwendung des aktuellen Werts der Felder und des OFS neu zu erstellen. Verwenden Sie dazu die scheinbar harmlose Zuweisung:"

 $1 = $1   # force record to be reconstituted
 print $0  # or whatever else with $0

"Dies zwingt awk, den Datensatz neu zu erstellen."

Testeingabe einschließlich einiger Zeilen gleicher Länge:

aa A line   with     MORE    spaces
bb The very longest line in the file
ccb
9   dd equal len.  Orig pos = 1
500 dd equal len.  Orig pos = 2
ccz
cca
ee A line with  some       spaces
1   dd equal len.  Orig pos = 3
ff
5   dd equal len.  Orig pos = 4
g
neillb
quelle
1
heemayl, ja, danke. Ich habe versucht, die Form der versuchten Lösung von OP nach Möglichkeit anzupassen, damit er sich nur auf wichtige Unterschiede zwischen seiner und meiner konzentrieren kann.
Neillb
1
Es ist erwähnenswert, dass auch cat $@das kaputt ist. Sie wollen es auf jeden Fall zitieren, wiecat "$@"
Tripleee
27

Die AWK-Lösung von neillb ist großartig, wenn Sie sie wirklich verwenden möchten, awkund sie erklärt, warum es dort problematisch ist. Wenn Sie jedoch möchten, dass die Arbeit schnell erledigt wird und es Ihnen egal ist, in was Sie sie ausführen, ist eine Lösung die Verwendung Perls sort()Funktion mit einer benutzerdefinierten Caparison-Routine zum Durchlaufen der Eingabezeilen. Hier ist ein Einzeiler:

perl -e 'print sort { length($a) <=> length($b) } <>'

Sie können dies in Ihre Pipeline einfügen, wo immer Sie es benötigen, indem Sie entweder STDIN (von catoder eine Shell-Umleitung) empfangen oder einfach den Dateinamen perl als weiteres Argument angeben und die Datei öffnen lassen.

In meinem Fall brauchte ich zuerst die längsten Zeilen, also habe ich sie ausgetauscht $aund $bverglichen.

Caleb
quelle
Dies ist eine bessere Lösung, da awk eine unerwartete Sortierung verursacht, wenn die Eingabedatei numerische und alfanumerische Zeilen enthält. Hier der Online-Befehl: $ cat testfile | perl -e 'Drucksortierung {Länge ($ a) <=> Länge ($ b)} <>'
Alemol
Schnell! Hat 465.000 Zeilendateien (ein Wort pro Zeile) in <1 Sekunde ausgeführt, als die Ausgabe in eine andere Datei umgeleitet wurde - also:cat testfile.txt | perl -e 'print sort { length($a) <=> length($b) } <>' > out.txt
cssyphus
Windows mit StrawberryPerl funktioniert:type testfile.txt | perl -e "print sort { length($a) <=> length($b) } <>" > out.txt
Bryc
14

Versuchen Sie stattdessen diesen Befehl:

awk '{print length, $0}' your-file | sort -n | cut -d " " -f2-
Anubhava
quelle
10

Benchmark-Ergebnisse

Nachfolgend finden Sie die Ergebnisse eines Benchmarks für Lösungen aus anderen Antworten auf diese Frage.

Testmethode

  • 10 aufeinanderfolgende Läufe auf einer schnellen Maschine, gemittelt
  • Perl 5.24
  • awk 3.1.5 (gawk 4.1.0 waren ~ 2% schneller)
  • Die Eingabedatei ist eine Monstrosität mit 550 MB und 6 Millionen Zeilen (British National Corpus txt).

Ergebnisse

  1. Calebs perlLösung dauerte 11,2 Sekunden
  2. Meine perlLösung dauerte 11,6 Sekunden
  3. neillbs awklösung Nr. 1 dauerte 20 sekunden
  4. neillbs awklösung Nr. 2 dauerte 23 sekunden
  5. Die awkLösung von Anubhava dauerte 24 Sekunden
  6. Jonathans awkLösung dauerte 25 Sekunden
  7. Fretz der bashLösung nimmt 400x länger als die awkLösungen (mit Hilfe eines abgestumpften Testfall von 100000 Linien). Es funktioniert gut, dauert nur ewig.

Zusätzliche perlOption

Außerdem habe ich eine weitere Perl-Lösung hinzugefügt:

perl -ne 'push @a, $_; END{ print sort { length $a <=> length $b } @a }' file
Chris Koknat
quelle
6

Pure Bash:

declare -a sorted

while read line; do
  if [ -z "${sorted[${#line}]}" ] ; then          # does line length already exist?
    sorted[${#line}]="$line"                      # element for new length
  else
    sorted[${#line}]="${sorted[${#line}]}\n$line" # append to lines with equal length
  fi
done < data.csv

for key in ${!sorted[*]}; do                      # iterate over existing indices
  echo -e "${sorted[$key]}"                       # echo lines with equal length
done
Fritz G. Mehner
quelle
3

Die length()Funktion enthält Leerzeichen. Ich würde nur geringfügige Anpassungen an Ihrer Pipeline vornehmen (einschließlich der Vermeidung von UUOC ).

awk '{ printf "%d:%s\n", length($0), $0;}' "$@" | sort -n | sed 's/^[0-9]*://'

Der sedBefehl entfernt direkt die vom awkBefehl hinzugefügten Ziffern und Doppelpunkte . Alternativ können Sie Ihre Formatierung beibehalten von awk:

awk '{ print length($0), $0;}' "$@" | sort -n | sed 's/^[0-9]* //'
Jonathan Leffler
quelle
2

Ich habe festgestellt, dass diese Lösungen nicht funktionieren, wenn Ihre Datei Zeilen enthält, die mit einer Zahl beginnen, da sie zusammen mit allen gezählten Zeilen numerisch sortiert werden. Die Lösung ist , zu geben , sortdie -g(allgemein numerische-sort) flag anstelle von -n(numeric-sort):

awk '{ print length, $0 }' lines.txt | sort -g | cut -d" " -f2-
Markus Amalthea Magnuson
quelle
2
Hallo Markus. Ich beobachte nicht, dass der Zeileninhalt (numerisch oder nicht) - im Gegensatz zur Zeilenlänge - einen Einfluss auf die Sortierung hat, außer bei Zeilen mit übereinstimmenden Längen. Hast du das gemeint? In solchen Fällen habe ich keine Umstellung der Sortiermethoden -nauf Ihren Vorschlag gefunden -g, um eine Verbesserung zu erzielen, daher erwarte ich dies nicht. In meiner Antwort habe ich mich nun damit befasst, wie das Untersortieren von Zeilen gleicher Länge (mit --stable) verboten werden kann . Ob Sie das gemeint haben oder nicht, danke, dass Sie mich darauf aufmerksam gemacht haben! Ich habe auch eine überlegte Eingabe zum Testen hinzugefügt.
Neillb
4
Nein, lassen Sie mich das erklären, indem Sie es aufschlüsseln. Nur das awkTeil generiert eine Liste von Zeilen, denen Zeilenlänge und ein Leerzeichen vorangestellt sind. Das Weiterleiten sort -nfunktioniert wie erwartet. Wenn jedoch eine dieser Zeilen am Anfang bereits eine Nummer hat, beginnen diese Zeilen mit Länge + Leerzeichen + Nummer. sort -nignoriert diesen Raum und behandelt ihn als eine aus Länge + Zahl verkettete Zahl. Die Verwendung des -gFlags stoppt stattdessen beim ersten Leerzeichen und ergibt eine korrekte Sortierung. Probieren Sie es selbst aus, indem Sie eine Datei mit Zeilen mit Präfix erstellen und den Befehl Schritt für Schritt ausführen.
Markus Amalthea Magnuson
1
Ich fand auch, dass sort -nder Raum ignoriert und eine falsche Sortierung erzeugt. sort -ggibt die richtige Reihenfolge aus.
Robert Smith
Ich kann das beschriebene Problem mit -nin nicht reproduzieren sort (GNU coreutils) 8.21. In der infoDokumentation wird beschrieben -g, dass diese weniger effizient und möglicherweise weniger präzise ist (sie konvertiert Zahlen in Floats). Verwenden Sie sie daher wahrscheinlich nicht, wenn Sie dies nicht benötigen.
Phils
nb Dokumentation für -n: "Numerisch sortieren. Die Zahl beginnt in jeder Zeile und besteht aus optionalen Leerzeichen, einem optionalen '-' Zeichen und null oder mehr Ziffern, die möglicherweise durch Tausende Trennzeichen getrennt sind, optional gefolgt von einem Dezimalzeichen und null oder mehr Ziffern Eine leere Zahl wird als '0' behandelt. Das Gebietsschema 'LC_NUMERIC' gibt das Dezimalzeichen und das Tausendertrennzeichen an. Standardmäßig ist ein Leerzeichen ein Leerzeichen oder eine Registerkarte, das Gebietsschema 'LC_CTYPE' kann dies jedoch ändern. "
Phils
2

Mit POSIX Awk:

{
  c = length
  m[c] = m[c] ? m[c] RS $0 : $0
} END {
  for (c in m) print m[c]
}

Beispiel

Steven Penny
quelle
2

1) reine awk-Lösung. Nehmen wir an, dass die Zeilenlänge dann nicht mehr als 1024 sein kann

Katzendateiname | awk 'BEGIN {min = 1024; s = "";} {l = Länge ($ 0); wenn (l <min) {min = l; s = $ 0;}} END {print s} '

2) Eine Liner-Bash-Lösung unter der Annahme, dass alle Zeilen nur 1 Wort haben, kann jedoch für jeden Fall überarbeitet werden, in dem alle Zeilen die gleiche Anzahl von Wörtern haben:

LINES = $ (Dateiname der Katze); für k in $ LINES; printf "$ k"; echo $ k | wc -L; erledigt | sort -k2 | Kopf -n 1 | schneide -d "" -f1

Michael Yuniverg
quelle
1

Hier ist eine Multibyte-kompatible Methode zum Sortieren von Zeilen nach Länge. Es benötigt:

  1. wc -m steht Ihnen zur Verfügung (macOS hat es).
  2. Ihr aktuelles Gebietsschema unterstützt Multi-Byte-Zeichen, z LC_ALL=UTF-8. B. durch Festlegen . Sie können dies entweder in Ihrem .bash_profile oder einfach durch Voranstellen vor dem folgenden Befehl festlegen.
  3. testfile hat eine Zeichencodierung, die Ihrem Gebietsschema entspricht (z. B. UTF-8).

Hier ist der vollständige Befehl:

cat testfile | awk '{l=$0; gsub(/\047/, "\047\"\047\"\047", l); cmd=sprintf("echo \047%s\047 | wc -m", l); cmd | getline c; close(cmd); sub(/ */, "", c); { print c, $0 }}' | sort -ns | cut -d" " -f2-

Teil für Teil erklären:

  • l=$0; gsub(/\047/, "\047\"\047\"\047", l);← erstellt eine Kopie jeder Zeile in einer awk-Variablen lund maskiert jede Zeile doppelt, 'damit die Zeile sicher als Shell-Befehl wiedergegeben werden kann ( \047ist ein einfaches Anführungszeichen in Oktalschreibweise).
  • cmd=sprintf("echo \047%s\047 | wc -m", l);← Dies ist der Befehl, den wir ausführen, der die maskierte Zeile wiedergibt wc -m.
  • cmd | getline c;← führt den Befehl aus und kopiert den Zeichenanzahlwert, der in die Variable awk zurückgegeben wird c.
  • close(cmd); ← Schließen Sie die Pipe zum Shell-Befehl, um zu vermeiden, dass die Anzahl der geöffneten Dateien in einem Prozess auf ein System begrenzt wird.
  • sub(/ */, "", c);← schneidet Leerzeichen vom Zeichenanzahlwert ab, der von zurückgegeben wird wc.
  • { print c, $0 } ← druckt den Zeichenanzahlwert der Zeile, ein Leerzeichen und die ursprüngliche Zeile.
  • | sort -ns← sortiert die Zeilen (nach vorangestellten Zeichenanzahlwerten) numerisch ( -n) und behält eine stabile Sortierreihenfolge bei ( -s).
  • | cut -d" " -f2- ← entfernt die vorangestellten Zeichenanzahlwerte.

Es ist langsam (nur 160 Zeilen pro Sekunde auf einem schnellen Macbook Pro), da für jede Zeile ein Unterbefehl ausgeführt werden muss.

Alternativ können Sie dies nur mit gawk(ab Version 3.1.5 ist gawk Multibyte-fähig) ausführen, was erheblich schneller wäre. Es ist sehr mühsam, alle Zeilenumbrüche und doppelten Anführungszeichen auszuführen, um die Zeilen sicher durch einen Shell-Befehl von awk zu leiten. Dies ist jedoch die einzige Methode, bei der keine zusätzliche Software installiert werden muss (gawk ist standardmäßig nicht verfügbar) Mac OS).

Quinn Comendant
quelle