Sortuj plik tekstowy według długości linii, w tym spacji


137

Mam plik CSV, który wygląda tak

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

Muszę to posortować według długości linii, w tym spacji. Poniższe polecenie nie zawiera spacji, czy istnieje sposób na zmodyfikowanie go, aby działał dla mnie?

cat $@ | awk '{ print length, $0 }' | sort -n | awk '{$1=""; print $0}'

21
Naprawdę chciałbym mieszkać na Binary Avenue lub Ternary Street, ci ludzie z pewnością zgodziliby się z takimi rzeczami, jak „8192 to okrągła liczba”
schnaader

Odpowiedzi:


224

Odpowiedź

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

Lub, aby wykonać oryginalne (być może niezamierzone) sortowanie dowolnych równych długości wierszy:

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

W obu przypadkach rozwiązaliśmy zgłoszony problem, odsuwając się od awk do ostatecznego cięcia.

Linie o dopasowanej długości - co zrobić w przypadku krawata:

Pytanie nie określało, czy dalsze sortowanie było potrzebne dla wierszy o pasującej długości. Założyłem, że jest to niepożądane i zasugerowałem użycie -s( --stable), aby zapobiec sortowaniu takich wierszy względem siebie i utrzymywać je we względnej kolejności, w jakiej występują na wejściu.

(Ci, którzy chcą mieć większą kontrolę nad sortowaniem tych powiązań, mogą spojrzeć na --keyopcję sortowania ).

Dlaczego próba rozwiązania tego pytania zawodzi (przebudowa wiersza awk):

Warto zauważyć różnicę między:

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

Dają odpowiednio

hello   awk   world
hello awk world

Odpowiedniej sekcji (gawk'S) instrukcja wspomina tylko na marginesie, że awk będzie odbudować całe $ 0 (na podstawie separatora, etc) w przypadku zmiany jednego pola. Myślę, że to nie jest szalone zachowanie. Ma to:

„Wreszcie są chwile, kiedy wygodnie jest zmusić awk do odbudowania całego rekordu przy użyciu bieżącej wartości pól i OFS. Aby to zrobić, użyj pozornie nieszkodliwego przypisania:”

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

„To zmusza awk do odbudowania rekordu”.

Wejście testowe zawierające kilka wierszy o równej długości:

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

1
heemayl, tak to jest, dzięki. W miarę możliwości starałem się dopasować kształt do proponowanego przez OP rozwiązania, aby umożliwić mu skupienie się tylko na ważnych różnicach między jego i moim.
neillb

1
Warto zaznaczyć, że też cat $@jest zepsuty. Zdecydowanie chcesz to zacytować, na przykładcat "$@"
tripleee

27

Rozwiązanie AWK od Neillb jest świetne, jeśli naprawdę chcesz go używać awki wyjaśnia, dlaczego jest to kłopotliwe, ale jeśli chcesz, aby praca została wykonana szybko i nie obchodzi Cię, w czym to robisz, jednym z rozwiązań jest użycie sort()Funkcja Perla z niestandardową procedurą Caparison do iteracji po liniach wejściowych. Oto jedna linijka:

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

Możesz umieścić to w swoim potoku gdziekolwiek tego potrzebujesz, otrzymując STDIN (od catlub przekierowanie powłoki) lub po prostu podaj nazwę pliku perlowi jako kolejny argument i pozwól mu otworzyć plik.

W moim przypadku musiałem najdłuższe linie pierwszy, więc zamieniłem się $ai $bw porównaniu.


Jest to lepsze rozwiązanie, ponieważ awk powoduje nieoczekiwane sortowanie, gdy plik wejściowy zawiera linie numeryczne i alfanumeryczne. Tutaj polecenie oneline: $ cat testfile | perl -e 'print sort {length ($ a) <=> length ($ b)} <>'
alemol

Szybki! Czy plik 465,000 linii (jedno słowo w linii) w <1 sekundę, gdy wyjście zostało przekierowane do innego pliku - tak:cat testfile.txt | perl -e 'print sort { length($a) <=> length($b) } <>' > out.txt
cssyphus

Windows z StrawberryPerl działa:type testfile.txt | perl -e "print sort { length($a) <=> length($b) } <>" > out.txt
bryc

14

Zamiast tego spróbuj tego polecenia:

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

10

Wyniki testów porównawczych

Poniżej znajdują się wyniki testu porównawczego rozwiązań z innych odpowiedzi na to pytanie.

Metoda badania

  • 10 sekwencyjnych przebiegów na szybkiej maszynie, uśrednione
  • Perl 5.24
  • awk 3.1.5 (gawk 4.1.0 razy był ~ 2% szybszy)
  • Plik wejściowy to monstrum o rozmiarze 550 MB, 6 milionów wierszy (txt British National Corpus)

Wyniki

  1. perlRozwiązanie Caleba zajęło 11,2 sekundy
  2. moje perlrozwiązanie zajęło 11,6 sekundy
  3. awkRozwiązanie nr 1 Neillba zajęło 20 sekund
  4. awkRozwiązanie nr 2 Neillba zajęło 23 sekundy
  5. awkRozwiązanie anubhavy zajęło 24 sekundy
  6. awkRozwiązanie Jonathana zajęło 25 sekund
  7. Fretz jest bashrozwiązanie trwa 400 razy dłuższy od awkrozwiązania (stosując skróconą sprawdzian 100000 linii). Działa dobrze, po prostu trwa wieczność.

Dodatkowa perlopcja

Dodałem również kolejne rozwiązanie Perla:

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

6

Czysty bas:

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

3

length()Funkcja ma zawierać spacji. Wprowadziłbym tylko drobne poprawki do twojego rurociągu (w tym unikanie UUOC ).

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

sedPolecenie bezpośrednio usuwa cyfr i okrężnicy dodane przez awkpolecenia. Możesz też nie dopuścić do formatowania awk:

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

2

Zauważyłem, że te rozwiązania nie będą działać, jeśli plik zawiera wiersze zaczynające się od liczby, ponieważ zostaną one posortowane numerycznie wraz ze wszystkimi policzonymi wierszami. Rozwiązanie to daje sortsię -g(ogólnie numeryczne-rodzaju) flagi zamiast -n(numeryczna sortowania)

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

2
Cześć, Markus. Nie uważam, że zawartość linii (numeryczna lub nie) - w przeciwieństwie do długości linii - ma jakikolwiek wpływ na sortowanie, z wyjątkiem linii o pasujących długościach. Czy to miałeś na myśli? W takich przypadkach nie znalazłem -nsugerowanej zmiany metody sortowania z na twoją, -gaby uzyskać jakąkolwiek poprawę, więc nie spodziewam się. W mojej odpowiedzi odniosłem się teraz do tego, jak zabronić sortowania podrzędnego wierszy o równej długości (przy użyciu --stable). Czy to miałeś na myśli, czy nie, dziękuję za zwrócenie mi na to uwagi! Dodałem również przemyślane wejście do przetestowania.
neillb

4
Nie, pozwól mi wyjaśnić, rozkładając to. Tylko awkczęść wygeneruje listę linii z prefiksem długości linii i spacją. Rurowanie sort -nbędzie działać zgodnie z oczekiwaniami. Ale jeśli któryś z tych wierszy ma już numer na początku, te wiersze będą rozpoczynać się od długości + spacja + liczba. sort -npomija tę przestrzeń i potraktuje ją jako jedną liczbę połączoną z długości + liczba. Użycie -gflagi spowoduje zamiast tego zatrzymanie się na pierwszym miejscu, dając poprawne sortowanie. Spróbuj sam, tworząc plik z kilkoma wierszami z prefiksami liczbowymi i uruchamiaj polecenie krok po kroku.
Markus Amalthea Magnuson

1
Zauważyłem również, że sort -npomija przestrzeń i powoduje nieprawidłowe sortowanie. sort -gwyświetla poprawną kolejność.
Robert Smith

Nie mogę odtworzyć opisanego problemu -nw formacie sort (GNU coreutils) 8.21. infoDokumentacja opisuje -gjako potencjalnie mniej wydajne i mniej precyzyjny (konwertuje numery do pływaków), więc prawdopodobnie nie używaj go, jeśli nie trzeba.
phils

Dokumentacja NB dla -n: „Sortuj numerycznie. Numer zaczyna się w każdym wierszu i składa się z opcjonalnych odstępów, opcjonalnego znaku„ - ”oraz zera lub większej liczby cyfr, ewentualnie oddzielonych separatorami tysięcy, po których opcjonalnie następuje znak przecinka dziesiętnego i zero lub więcej cyfr . Pusta liczba jest traktowana jako „0”. Ustawienia regionalne „LC_NUMERIC” określają znak przecinka dziesiętnego i separator tysięcy. Domyślnie spacja to spacja lub tabulator, ale ustawienie regionalne „LC_CTYPE” może to zmienić. "
phils


2

1) czyste rozwiązanie awk. Załóżmy, że długość linii nie może być wtedy większa niż 1024

nazwa pliku kota | awk 'BEGIN {min = 1024; s = "";} {l = length ($ 0); if (l <min) {min = l; s = 0 $;}} END {print s} '

2) jedno rozwiązanie liniowe bash zakładające, że wszystkie linie mają tylko 1 słowo, ale można je przerobić dla każdego przypadku, w którym wszystkie linie mają taką samą liczbę słów:

LINES = $ (nazwa pliku cat); dla k w $ LINES; do printf "$ k"; echo $ k | wc -L; gotowe | sort -k2 | głowa -n 1 | wytnij -d "" -f1


1

Oto wielobajtowa metoda sortowania wierszy według długości. To wymaga:

  1. wc -m jest dostępny dla Ciebie (ma go macOS).
  2. Twoje obecne ustawienia regionalne obsługują znaki wielobajtowe, np. Przez ustawienie LC_ALL=UTF-8. Możesz to ustawić albo w swoim .bash_profile, albo po prostu dodając go przed następującym poleceniem.
  3. testfile ma kodowanie znaków zgodne z Twoim ustawieniem regionalnym (np. UTF-8).

Oto pełne polecenie:

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-

Wyjaśniając część po części:

  • l=$0; gsub(/\047/, "\047\"\047\"\047", l);← tworzy kopię każdej linii w zmiennej awk li zawiera podwójne znaki specjalne, 'tak aby można ją było bezpiecznie powtórzyć jako polecenie powłoki ( \047jest to pojedynczy cudzysłów w notacji ósemkowej).
  • cmd=sprintf("echo \047%s\047 | wc -m", l);← to jest polecenie, które wykonamy, które jest echem linii uciekającej wc -m.
  • cmd | getline c;← wykonuje polecenie i kopiuje liczbę znaków, która jest zwracana do zmiennej awk c.
  • close(cmd); ← zamknij potok do polecenia powłoki, aby uniknąć przekroczenia przez system ograniczenia liczby otwartych plików w jednym procesie.
  • sub(/ */, "", c);← przycina białe znaki z wartości liczby znaków zwróconej przez wc.
  • { print c, $0 } ← wyświetla liczbę znaków w linii, spację i oryginalny wiersz.
  • | sort -ns← sortuje wiersze (według liczby poprzedzonych znaków) numerycznie ( -n), zachowując stabilną kolejność sortowania ( -s).
  • | cut -d" " -f2- ← usuwa wartości liczby znaków dołączonych na początku.

Jest wolny (tylko 160 linii na sekundę na szybkim Macbooku Pro), ponieważ musi wykonać polecenie podrzędne dla każdej linii.

Alternatywnie, zrób to tylko z gawk(od wersji 3.1.5, gawk obsługuje wiele bajtów), co byłoby znacznie szybsze. Wykonywanie wszystkich znaków ucieczki i podwójnych cudzysłowów w celu bezpiecznego przepuszczenia wierszy przez polecenie powłoki z awk jest bardzo kłopotliwe, ale jest to jedyna metoda, jaką udało mi się znaleźć, która nie wymaga instalowania dodatkowego oprogramowania (gawk nie jest domyślnie dostępny w System operacyjny Mac).

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.