Jak wydrukować najdłuższą linię w pliku?

35

Szukam najprostszej metody wydrukowania najdłuższej linii w pliku. Zrobiłem trochę googlingu i, co zaskakujące, nie mogłem znaleźć odpowiedzi. Często drukuję długość najdłuższej linii w pliku, ale nie wiem jak wydrukować najdłuższą linię. Czy ktoś może dostarczyć rozwiązanie do wydrukowania najdłuższej linii w pliku? Z góry dziękuję.

dr.Bunsen
źródło
1
A co, jeśli jest wiele „najdłuższych” linii ?. Ponieważ chcesz czegoś więcej niż zwykłej maksymalnej długości, czy chcesz zobaczyć wszystkie wystąpienia linii o równej długości?
Peter.O,

Odpowiedzi:

39
cat ./text | awk ' { if ( length > x ) { x = length; y = $0 } }END{ print y }'

UPD : podsumowanie wszystkich porad w komentarzach

awk 'length > max_length { max_length = length; longest_line = $0 } END { print longest_line }' ./text 
ДМИТРИЙ МАЛИКОВ
źródło
3
Jest tak, zarówno wywołanie innej komendy ( cat), jak i użycie potoku są kosztownymi operacjami, nie wspominając już o tym, że awk może po prostu czytać plik. Wpływ na wydajność jest zdecydowanie zauważalny, jeśli odbywa się to często, a mimo to całkowicie nadużywasz cat.
Chris Down,
7
@laebshade Jest absolutnie jeden powód - dlatego nie musisz pamiętać, które polecenia przyjmują nazwy plików, a które nie, ani nie przejmuj się, które polecenie zostanie wykonane jako pierwsze w potoku. Jeśli zamierzasz pisać skrypt, który jest często uruchamiany, z pewnością martw się o coś takiego. Jeśli piszesz jednorazową rzecz, aby znaleźć najdłuższą linię w pliku, dodatkowy proces i ułamek czasu jest całkowicie nieistotny. To głupie, że ludzie mają tutaj taką obsesję, to jest niewiarygodnie niewielkie
Michael Mrozek
4
@Keith Thompson: catnie jest tu bezużyteczny. Może być bezużyteczny dla komputera, ale dla ludzkiego czytelnika może stanowić wartość. Pierwszy wariant wyraźnie pokazuje dane wejściowe. Przepływ jest bardziej naturalny (od lewej do prawej). W drugim przypadku nie wiesz, co to jest wejście, chyba że przewiniesz okno.
jfs
1
@JFSebastian Nawet jeśli chcesz to po lewej, nie potrzebujesz cat. < file commanddziała dobrze.
Chris Down,
3
@JFSebastian: Fakt, że przekierowanie można zapisać na początku polecenia, jest nieco niejasny; < filename commandjest równoważne z filename < commandkażdą próbowaną powłoką. Ale kiedy zdasz sobie z tego sprawę, możesz z niego skorzystać, pisząc długie potoki, które wyraźnie pokazują kierunek przepływu danych (bez konieczności wywoływania dodatkowego polecenia):< input-file command1 | command2 | command3 > output-file
Keith Thompson
6
cat filename | awk '{ print length }' | sort -n | tail -1
aspinalln
źródło
+1 Było wiele ciekawych rozwiązań, ale to było najprostsze. (Byłoby łatwiej bez kota, pozwalając awk odczytać plik, ale po co się
spierać
5
sed -rn "/.{$(<file expand -t1 |wc -L)}/{p;q}" file

Najpierw odczytuje plik wewnątrz podstawienia polecenia i wyświetla długość najdłuższej linii (poprzednio expandkonwertuje tabulacje na spacje, aby przezwyciężyć semantykę wc -L- każda tabulacja w linii doda 8 zamiast 1 do długości linii). Ta długość jest następnie używana w sedwyrażeniu oznaczającym „znajdź wiersz o tej liczbie znaków, wydrukuj go, a następnie zakończ”. Więc to może być tak optymalne, jak najdłuższa linia jest blisko początku pliku, heheh (dziękuję za niesamowite i konstruktywne komentarze).

Innym, pomyślałem wcześniej niż sed (w bash):

#!/bin/bash
while read -r line; do
    (( ${#line} > max )) && max=${#line} && longest="$line"
done
echo "$longest"
ata
źródło
2
Ta metoda jest bardzo droga i powolna.
Chris Down,
2
@Chris Down: O tak, jest. Pytanie dotyczyło jednak najmilszej, a nie najskuteczniejszej metody. Pracuje jednak doskonale dla małych i średnich plików lub niekrytycznych zadań.
ata
3
OSTRZEŻENIE : opcja wc -L, --max-line-lengthdrukuje długość najdłuższej linii, zgodnie ze stroną podręcznika, ale jeśli kopiesz głębiej (na przykład w przypadku błędnych / nieoczekiwanych wyników), zauważysz, że ta opcja zwiększa długość o 8 dla każdego 1 znaku tab \x09 zobacz ten Q / A dla systemów Unix i Linux
Peter.O,
PS. Twoja odpowiedź wydrukuje wszystkie „równie najdłuższe” linie, co jest prawdopodobnie dobrą rzeczą ... Aby zmusić wc do zliczenia tylko 1 znaku na kartę, to działa. sed -rn "/.{$(<file expand -t1 |wc -L)}/p" file
Peter.O,
1
read linezinterpretuje odwróconego ukośnika znaki jak dosłownym char, np \Aresloves to A, co oczywiście skutecznie zgłasza krótszy niż rzeczywisty bajt wykorzystanie ... Aby zapobiec temu uciekł interpretację, przeznaczenie: read -r line. . . . Ponadto, aby wersja sed + wc zakończyła się po pierwszej „najdłuższej linii”, zmień pna {p;q}sed -rn "/.{$(<file expand -t1 |wc -L)}/{p;q}" file
Peter.O,
4

Oto rozwiązanie Perla:

perl -e 'while(<>){
           $l=length;  
           $l>$m && do {$c=$_; $m=$l}  
         } print $c' file.txt 

Lub, jeśli chcesz wydrukować wszystkie najdłuższe linie

perl -e 'while(<>){
           $l=length;
           push @{$k{$l}},$_;
           $m=$l if $l>$m;
         } print @{$k{$m}}' file.txt 

Ponieważ nie miałem nic lepszego do roboty, przeprowadziłem testy porównawcze dla pliku tekstowego 625M. O dziwo, moje rozwiązanie Perla było konsekwentnie szybsze niż inne. To prawda, że ​​różnica w stosunku do przyjętego awkrozwiązania jest niewielka, ale istnieje. Oczywiście rozwiązania drukujące wiele linii są wolniejsze, więc posortowałem według typu, od najszybszego do najwolniejszego.

Wydrukuj tylko jedną z najdłuższych linii:

$ time perl -e 'while(<>){
           $l=length;  
           $l>$m && do {$c=$_; $m=$l}  
         } print $c' file.txt 
real    0m3.837s
user    0m3.724s
sys     0m0.096s



$ time awk 'length > max_length { max_length = length; longest_line = $0 }
 END { print longest_line }' file.txt
real    0m5.835s
user    0m5.604s
sys     0m0.204s



$ time sed -rn "/.{$(<file.txt expand -t1 |wc -L)}/{p;q}" file.txt 
real    2m37.348s
user    2m39.990s
sys     0m1.868s

Wydrukuj wszystkie najdłuższe linie:

$ time perl -e 'while(<>){
           $l=length;
           push @{$k{$l}},$_;
           $m=$l if $l>$m;
         } print @{$k{$m}}' file.txt 
real    0m9.263s
user    0m8.417s
sys     0m0.760s


$ time awk 'length >x { delete y; x=length }
     length==x { y[NR]=$0 } END{ for (z in y) print y[z] }' file.txt
real    0m10.220s
user    0m9.925s
sys     0m0.252s


## This is Chris Down's bash solution
$ time ./a.sh < file.txt 
Max line length: 254
Lines matched with that length: 2
real    8m36.975s
user    8m17.495s
sys     0m17.153s
terdon
źródło
3

Grep pierwsza najdłuższa linia

grep -Em1 "^.{$(wc -L <file.txt)}\$" file.txt 

Polecenie jest niezwykle trudne do odczytania bez praktyki, ponieważ łączy w sobie składnię powłoki i wyrażenia regularnego.
Dla wyjaśnienia najpierw użyję uproszczonego pseudokodu. Linie zaczynające się od ##nie działają w powłoce.
Ten uproszczony kod używa nazwy pliku F i pomija cytowanie i fragmenty wyrażeń regularnych dla czytelności.

Jak to działa

Polecenie składa się z dwóch części, a grep- i wcwywołania:

## grep "^.{$( wc -L F )}$" F

wcStosuje się ekspansji procesu, $( ... )tak że prowadzony jest przed grep. Oblicza długość najdłuższej linii. Składnia rozszerzania powłoki jest mieszana ze składnią wzorca wyrażeń regularnych w mylący sposób, więc rozpakuję rozwinięcie procesu:

## wc -L F
42
## grep "^.{42}$" F

Tutaj rozszerzenie procesu zostało zastąpione wartością, którą zwróci, tworząc grepużywany wiersz poleceń. Możemy teraz łatwiej odczytać wyrażenie regularne: Pasuje dokładnie od początku ( ^) do końca ( $) linii. Wyrażenie między nimi pasuje do dowolnego znaku oprócz znaku nowej linii, powtarzanego 42 razy. Łącznie, czyli wiersze składające się dokładnie z 42 znaków.


Wróćmy teraz do prawdziwych poleceń powłoki: grepOpcja -E( --extended-regexp) pozwala nie uciec przed {}czytelnością. Opcja -m 1( --max-count=1) powoduje zatrzymanie po znalezieniu pierwszego wiersza. Komenda <in wczapisuje plik na standardowe wejście, aby zapobiec wcdrukowaniu nazwy pliku wraz z jego długością.

Które najdłuższe linie?

Aby przykłady były bardziej czytelne, a nazwa pliku występowała dwukrotnie, użyję zmiennej fdla nazwy pliku; Każdy $fw tym przykładzie można zastąpić nazwą pliku.

f="file.txt"

Pokaż pierwszą najdłuższą linię - pierwszą linię, która jest tak długa jak najdłuższa linia:

grep -E -m1 "^.{$(wc -L <"$f")}\$" "$f"

Pokaż wszystkie najdłuższe linie - wszystkie linie, które są tak długie jak najdłuższa linia:

grep -E "^.{$(wc -L <"$f")}\$" "$f" 

Pokaż ostatnią najdłuższą linię - ostatnia linia, która jest tak długa jak najdłuższa linia:

tac "$f" | grep -E -m1 "^.{$(wc -L <"$f")}\$"

Pokaż pojedynczą najdłuższą linię - najdłuższą linię dłuższą niż wszystkie inne linie, lub zawieść:

[ $(grep -E "^.{$(wc -L <"$f")}\$" "$f" | wc -l) = 1 ] && grep -E "^.{$(wc -L <"$f")}\$" "$f" 

(Ostatnie polecenie jest nawet bardziej nieefektywne niż inne, ponieważ powtarza kompletne polecenie grep. Oczywiście należy je rozłożyć, aby dane wyjściowe wci wiersze zapisane przez grepbyły zapisywane w zmiennych.
Zauważ, że wszystkie najdłuższe linie mogą w rzeczywistości być wszystkimi liniami Aby zapisać w zmiennej, należy zachować tylko dwa pierwsze wiersze.)

Volker Siegel
źródło
Wow, świetna odpowiedź, wiele się nauczyłem. dzięki
coś coś
2

Poniższy przykład miał być i powinien być komentarzem do odpowiedzi dmitry.malikov , ale z powodu bezużytecznego wykorzystania widocznego miejsca na komentarze postanowiłem przedstawić go tutaj, gdzie przynajmniej będzie widoczny. ..

Jest to prosta odmiana metody awk dla pojedynczego przejścia dmitry'ego.
Drukuje wszystkie „równe najdłuższe” linie. (Uwaga. delete arrayTo rozszerzenie gawk).

awk 'length >x { delete y; x=length }
     length==x { y[NR]=$0 } END{ for (z in y) print y[z] }' file
Peter.O
źródło
1

W czystej bash:

#!/bin/bash

_max_length=0
while IFS= read -r _line; do
    _length="${#_line}"
    if (( _length > _max_length )); then
        _max_length=${_length}
        _max_line=( "${_line}" )
    elif (( _length == _max_length )); then
        _max_line+=( "${_line}" )
    fi
done

printf 'Max line length: %d\n' "${_max_length}"
printf 'Lines matched with that length: %d\n' "${#_max_line[@]}"
(( ${#_max_line[@]} )) && printf '%s\n' '----------------' "${_max_line[@]}"
Chris Down
źródło
W obecnej postaci kod może zwracać nieprawidłowe wyniki. Ustawienie _max_line[0]=${_line}nie usuwa pozostałych wcześniejszych krótszych „najdłuższych linii” ... unset _max_linewyczyści całą tablicę ...
Peter.O,
@ fered Dzięki za to, został napisany dość szybko. Naprawiony.
Chris Down
0

Opracowałem do tego mały skrypt powłoki. Wyświetla długość, numer wiersza i samą linię według długości przekraczającej określony rozmiar, np. 80 znaków:

#!/bin/sh

# Author: Surinder

if test $# -lt 2
then
   echo "usage: $0 length file1 file2 ..."
   echo "usage: $0 80 hello.c"
   exit 1
fi

length=$1

shift

LONGLINE=/tmp/longest-line-$$.awk

cat << EOF > $LONGLINE
  BEGIN {
  }

  /.*/ {
    current_length=length(\$0);
    if (current_length >= expected_length) {
       printf("%d at line # %d %s\n", current_length, NR, \$0);
    }
  }

  END {
  }
EOF

for file in $*
do
  echo "$file"
  cat $file | awk -v expected_length=$length -f $LONGLINE |sort -nr
done

rm $LONGLINE

https://github.com/lordofrain/tools/blob/master/longest-line/longest-line.sh

Surinder432
źródło
1
Istnieje kilka ulepszeń, które możesz wprowadzić. Podaj swoje zmienne . Spowoduje to uszkodzenie wszystkich nazw plików zawierających białe znaki lub inne dziwne znaki. Korzystanie $*rzadko jest to dobry pomysł, chcesz"$@" . W /.*/twoim awknic nie robi, ponieważ pasuje to również do pustych linii. Możesz uniknąć ucieczki, \$0jeśli pojedynczo zacytujesz 'EOF'. Po co używać pustego BEGIN{}bloku? Wreszcie, nie potrzebujesz cat, po prostuawk . . . "$file" | . . .
terdon
1
Możesz również zrobić całą rzecz bezpośrednio w awk:awk -vmax=15 '{len=length($0); if(len>=max){printf("%s, %d at line # %d %s\n", FILENAME, len, NR, $0);}}' file*
terdon
-3

Możesz użyć wc:

wc -L fileName
ynot1074
źródło
3
Przeczytaj ponownie pytanie. Wymaganym wyjściem jest sama najdłuższa linia, a nie długość najdłuższej linii. Zobacz także komentarz Peter.O dotyczący wc -Lwady.
manatwork