it-swarm.com.de

Sortieren Sie eine Textdatei nach Zeilenlänge und Leerzeichen

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 [email protected] | awk '{ print length, $0 }' | sort -n | awk '{$1=""; print $0}'
121
gnarbarian

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.

Leinen mit passender Länge - was tun bei einem Unentschieden?

In der Frage wurde nicht angegeben, ob für Zeilen mit übereinstimmender Länge eine weitere Sortierung gewünscht ist oder nicht. Ich bin davon ausgegangen, dass dies unerwünscht ist, und habe die Verwendung von -s (--stable) Vorgeschlagen, um zu verhindern, dass solche Zeilen gegeneinander sortiert werden Eingang.

(Diejenigen, die mehr Kontrolle über das Sortieren dieser Krawatten wünschen, könnten die Option --key Von sort in Betracht ziehen.)

Warum schlägt der Lösungsversuch der Frage fehl (awk line-rebuilding):

Es ist interessant, den Unterschied zwischen:

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

Sie geben jeweils nach

hello   awk   world
hello awk world

Der relevante Abschnitt des Handbuchs von (gawk) erwähnt nur, dass awk den gesamten Wert von $ 0 (basierend auf dem Trennzeichen usw.) neu aufbaut, wenn Sie ein Feld ändern. Ich denke, es ist kein verrücktes Verhalten. Es hat folgendes:

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 von 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 Rekord neu zu erstellen."

Testeingabe mit einigen 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
192
neillb

Die AWK-Lösung von neillb ist großartig, wenn Sie wirklich awk verwenden möchten, und sie erklärt, warum es dort ein Ärger ist, aber wenn Sie den Job schnell erledigen und nicht möchten. ' Es ist egal, was Sie tun, eine Lösung besteht darin, die Funktion sort() von Perl mit einer benutzerdefinierten Caparison-Routine zu verwenden, um über die Eingabezeilen zu iterieren. 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. Entweder erhalten Sie STDIN (von cat oder eine Shell-Umleitung), oder Sie geben Perl den Dateinamen als weiteres Argument und lassen die Datei öffnen.

In meinem Fall brauchte ich zuerst die längsten Zeilen, also habe ich $a Und $b Im Vergleich vertauscht.

20
Caleb

Versuchen Sie stattdessen diesen Befehl:

awk '{print length, $0}' your-file | sort -n | cut -d " " -f2-
14
anubhava

Benchmark-Ergebnisse

Nachfolgend sind die Ergebnisse eines Benchmarks aufgeführt, der Lösungen aus anderen Antworten auf diese Frage umfasst.

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 550 MB große 6-Millionen-Zeilen-Monstrosität (British National Corpus txt).

Ergebnisse

  1. Calebs Perl Lösung dauerte 11,2 Sekunden
  2. meine Perl Lösung dauerte 11,6 Sekunden
  3. neillbs awk Lösung # 1 dauerte 20 Sekunden
  4. neillbs awk Lösung # 2 dauerte 23 Sekunden
  5. anubhavas awk Lösung dauerte 24 Sekunden
  6. Jonathans awk Lösung dauerte 25 Sekunden
  7. Fretz 'bash Lösung dauert 400x länger als die awk Lösungen (unter Verwendung eines abgeschnittenen Testfalls von 100000 Zeilen). Es funktioniert gut, dauert nur für immer.

Zusätzliche Option Perl

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

Perl -ne 'Push @a, $_; END{ print sort { length $a <=> length $b } @a }' file
7
Chris Koknat

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
5
Fritz G. Mehner

Die Funktion length() 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;}' "[email protected]" | sort -n | sed 's/^[0-9]*://'

Mit dem Befehl sed werden die durch den Befehl awk hinzugefügten Ziffern und Doppelpunkte direkt entfernt. Alternativ können Sie die Formatierung von awk beibehalten:

awk '{ print length($0), $0;}' "[email protected]" | sort -n | sed 's/^[0-9]* //'
3

Mit POSIX Awk:

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

Beispiel

2
Steven Penny

1) reine awk-lösung. Nehmen wir an, dass die Zeilenlänge dann nicht größer als 1024 sein kann

cat dateiname | 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, bei dem alle Zeilen die gleiche Anzahl von Wörtern haben:

LINES = $ (Katzendateiname); für k in $ LINES; do printf "$ k"; echo $ k | wc-L; fertig | sort -k2 | head -n 1 | schneiden Sie -d "" -f1 aus

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 besteht darin, sort das Flag -g (Allgemeine numerische Sortierung) anstelle von -n (Numerische Sortierung) zu geben:

awk '{ print length, $0 }' lines.txt | sort -g | cut -d" " -f2-

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. B. durch Festlegen von LC_ALL=UTF-8. Sie können dies entweder in Ihrem .bash_profile festlegen oder indem Sie es einfach vor den folgenden Befehl stellen.
  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-

Teilweise erklären:

  • l=$0; gsub(/\047/, "\047\"\047\"\047", l); ← erstellt eine Kopie jeder Zeile in der awk-Variablen l und führt bei jedem ' ein Doppel-Escape durch, damit die Zeile sicher als wiedergegeben werden kann Ein Shell-Befehl (\047 ist ein einfaches Anführungszeichen in oktaler Notation).
  • cmd=sprintf("echo \047%s\047 | wc -m", l); ← Dies ist der Befehl, den wir ausführen, der die maskierte Zeile in wc -m zurückmeldet.
  • cmd | getline c; ← führt den Befehl aus und kopiert den zurückgegebenen Zeichenzählwert in die awk-Variable 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 Systemlimit begrenzt wird.
  • sub(/ */, "", c); ← schneidet Leerzeichen von dem von wc zurückgegebenen Zeichenanzahlwert ab.
  • { print c, $0 } ← gibt den Zählwert der Zeile, ein Leerzeichen und die ursprüngliche Zeile aus.
  • | sort -ns ← sortiert die Zeilen (durch vorangestellte Zeichenanzahl) numerisch (-n) Und behält dabei 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), weil es für jede Zeile einen Unterbefehl ausführen muss.

Alternativ können Sie dies auch nur mit gawk tun (ab Version 3.1.5 ist gawk Multibyte-fähig), was erheblich schneller wäre. Es ist eine Menge Mühe, alle Escapezeichen und doppelten Anführungszeichen zu setzen, um die Zeilen sicher über einen Shell-Befehl von awk zu übergeben, aber dies ist die einzige Methode, die ich finden konnte und die keine Installation zusätzlicher Software erfordert (gawk ist standardmäßig auf nicht verfügbar) Mac OS).

1
Quinn Comendant