Wie drucke ich die längste Zeile einer Datei?


35

Ich suche nach der einfachsten Methode, um die längste Zeile in einer Datei zu drucken. Ich googelte und schien überraschenderweise keine Antwort zu finden. Ich drucke häufig die Länge der längsten Zeile in einer Datei, aber ich weiß nicht, wie ich die längste Zeile drucken soll. Kann jemand eine Lösung zum Drucken der längsten Zeile in einer Datei bereitstellen? Danke im Voraus.


1
Was ist, wenn es mehrere "längste" Leitungen gibt? Möchten Sie alle Instanzen von Zeilen anzeigen, die gleich lang sind, da Sie mehr als eine einfache maximale Länge möchten?
Peter.O

Antworten:


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

UPD : Zusammenfassung aller Hinweise in den Kommentaren

awk 'length > max_length { max_length = length; longest_line = $0 } END { print longest_line }' ./text 

3
Das Aufrufen eines anderen Befehls ( cat) und das Verwenden einer Pipe sind kostspielige Vorgänge, ganz zu schweigen davon, dass awk die Datei effizienter nur lesen kann. Die Auswirkungen auf die Leistung sind definitiv spürbar, wenn dies häufig gemacht wird, und trotzdem sind Sie völlig missbräuchlich cat.
Chris Down

7
@laebshade Es gibt absolut einen Grund - es ist so, dass Sie sich nicht merken müssen, welche Befehle Dateinamen annehmen und welche nicht, oder sich darum kümmern, welcher Befehl zuerst in der Pipeline ausgeführt wird. Wenn Sie ein Skript schreiben, das häufig ausgeführt wird, sorgen Sie sich auf jeden Fall um so etwas. Wenn Sie eine einmalige Aufgabe schreiben, um die längste Zeile in einer Datei zu finden, ist der zusätzliche Prozess und der zeitliche Aufwand völlig irrelevant. Es ist albern, dass die Leute hier so besessen davon sind, es ist unglaublich gering
Michael Mrozek

4
@Keith Thompson: catist hier nicht nutzlos. Es mag für einen Computer unbrauchbar sein, aber für einen menschlichen Leser könnte es einen Wert liefern. Die erste Variante zeigt deutlich die Eingabe. Der Fluss ist natürlicher (von links nach rechts). Im zweiten Fall wissen Sie nicht, was die Eingabe ist, es sei denn, Sie scrollen durch das Fenster.
jfs

1
@JFSebastian Auch wenn Sie es auf der linken Seite wollen, brauchen Sie nicht cat. < file commandfunktioniert gut
Chris Down

3
@JFSebastian: Die Tatsache, dass eine Umleitung zu Beginn eines Befehls geschrieben werden kann, ist etwas unklar. < filename commandist gleichbedeutend mit filename < commandin jeder Shell die ich ausprobiert habe. Aber sobald Sie sich dessen bewusst sind, können Sie es nutzen, wenn Sie lange Pipes schreiben, die klar die Richtung des Datenflusses anzeigen (ohne einen zusätzlichen Befehl aufzurufen):< input-file command1 | command2 | command3 > output-file
Keith Thompson

6
cat filename | awk '{ print length }' | sort -n | tail -1

+1 Es gab viele interessante Lösungen, aber dies war die einfachste. (Ohne die Katze wäre es einfacher, wenn awk die Datei lesen würde, aber warum sollte man streiten?)
user1683793

5
sed -rn "/.{$(<file expand -t1 |wc -L)}/{p;q}" file

Dies liest zuerst die Datei innerhalb der Befehlsersetzung und gibt die Länge der längsten Zeile aus ( expandkonvertiert zuvor Tabulatoren in Leerzeichen, um die Semantik von zu überwinden wc -L- jede Registerkarte in der Zeile addiert 8 anstelle von 1 zur Zeilenlänge). Diese Länge wird dann in einem sedAusdruck verwendet, der "Finde eine Zeile mit dieser Anzahl von Zeichen, drucke sie aus und beende sie dann" bedeutet. Das kann also tatsächlich so optimal sein, wie die längste Zeile sich in der Nähe des oberen Endes der Datei befindet, heheh (danke für die tollen und konstruktiven Kommentare).

Ein anderer, ich hatte früher gedacht als der sed (in bash):

#!/bin/bash
while read -r line; do
    (( ${#line} > max )) && max=${#line} && longest="$line"
done
echo "$longest"

2
Diese Methode ist sehr teuer und langsam.
Chris Down

2
@ Chris Down: Oh ja, das ist es. Aber die Frage war nach der sortesten Methode, nicht nach der effizientesten. Arbeitet jedoch sehr gut für kleine bis mittlere Dateien oder nicht kritische Aufgaben.
ata

3
WARNUNG : Die Option wc gibt -L, --max-line-lengthdie Länge der längsten Zeile gemäß der Manpage aus. Wenn Sie jedoch tiefer graben (z. B. wenn Sie falsche / unerwartete Ergebnisse erhalten), erhöht diese Option die Länge für jedes Zeichen mit 1 Tab um 8 finden Sie in diesem Unix & Linux Q / A\x09
Peter.O

PS. Ihre Antwort gibt alle "gleich langen" Zeilen aus, was wahrscheinlich eine gute Sache ist ... Um wc zu zwingen , nur 1 Zeichen pro Tab zu zählen, funktioniert dies. sed -rn "/.{$(<file expand -t1 |wc -L)}/p" file
Peter.O

1
read linewird Backslash-escaped Zeichen als wörtliche Zeichen, zB interpretieren \Aresloves zu A, was natürlich effektiv einen kürzeren als die tatsächlichen Byte-Nutzungsberichte ... dies zu verhindern entkommen Interpretation, zu verwenden: read -r line. . . . Um die sed + wc- Version nach der ersten "längsten Zeile" zu beenden, wechseln Sie pzu {p;q}..sed -rn "/.{$(<file expand -t1 |wc -L)}/{p;q}" file
Peter.O

4

Hier ist eine Perl-Lösung:

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

Oder möchten , wenn Sie drucken alle die längste Zeile

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

Da ich nichts Besseres zu tun hatte, führte ich einige Benchmarks für eine 625M-Textdatei durch. Überraschenderweise war meine Perl-Lösung durchweg schneller als die anderen. Zugegeben, der Unterschied zur akzeptierten awkLösung ist winzig, aber es ist da. Offensichtlich sind Lösungen, die mehrere Zeilen drucken, langsamer, sodass ich nach Typ sortiert habe, am schnellsten bis am langsamsten.

Nur eine der längsten Zeilen drucken:

$ 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

Alle längsten Zeilen drucken:

$ 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

3

Grep die erste längste Zeile

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

Der Befehl ist ohne Übung ungewöhnlich schwer zu lesen, da er Shell- und Regexp-Syntax mischt.
Zur Erklärung verwende ich zunächst den vereinfachten Pseudocode. Die mit beginnenden Zeilen ##verlaufen nicht in der Shell.
Dieser vereinfachte Code verwendet den Dateinamen F und lässt aus Gründen der Lesbarkeit Anführungszeichen und Teile von regulären Ausdrücken weg.

Wie es funktioniert

Der Befehl besteht aus zwei Teilen, einem grep- und einem wcAufruf:

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

Das wcwird in einer Prozesserweiterung verwendet $( ... ), es wird also vorher ausgeführt grep. Es berechnet die Länge der längsten Linie. Die Shell-Erweiterungssyntax wird auf verwirrende Weise mit der Mustersyntax für reguläre Ausdrücke gemischt, daher werde ich die Prozesserweiterung auflösen:

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

Hier wurde die Prozesserweiterung durch den Wert ersetzt, den sie zurückgeben würde, wodurch die verwendete grepBefehlszeile erstellt wurde. Wir können den regulären Ausdruck jetzt einfacher lesen: Er stimmt genau vom Anfang ( ^) bis zum Ende ( $) der Zeile überein . Der Ausdruck zwischen ihnen stimmt mit jedem Zeichen außer Newline überein und wird 42 Mal wiederholt. Kombiniert sind das Zeilen, die aus genau 42 Zeichen bestehen.


Nun zurück zu echten Shell-Befehlen: Die grepOption -E( --extended-regexp) erlaubt es, der {}Lesbarkeit nicht zu entgehen . Option -m 1( --max-count=1) stoppt, nachdem die erste Zeile gefunden wurde. Der Befehl <im wcBefehl schreibt die Datei in das Standardverzeichnis, um zu verhindern, dass wcder Dateiname zusammen mit der Länge gedruckt wird.

Welche längsten Schlangen?

Um die Beispiele bei zweimaligem Auftreten des Dateinamens lesbarer zu machen, verwende ich eine Variable ffür den Dateinamen. Jedes $fim Beispiel könnte durch den Dateinamen ersetzt werden.

f="file.txt"

Zeige die erste längste Zeile - die erste Zeile, die so lang ist wie die längste Zeile:

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

Zeige alle längsten Linien - alle Linien , die so lang wie die längste Linie sind:

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

Zeige die letzte längste Zeile - die letzte Zeile, die so lang ist wie die längste Zeile:

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

Zeigt die längste einzelne Zeile an - die längste Zeile ist länger als alle anderen Zeilen, oder schlägt fehl:

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

(Der letzte Befehl ist noch ineffizienter als die anderen, da er den vollständigen grep-Befehl wiederholt. Er sollte offensichtlich zerlegt werden, damit die Ausgabe von wcund die von geschriebenen Zeilen grepin Variablen gespeichert werden.
Beachten Sie, dass alle längsten Zeilen tatsächlich alle Zeilen sein können Zum Speichern in einer Variablen müssen nur die ersten beiden Zeilen beibehalten werden.)


Wow tolle Antwort, viel daraus gelernt. Dank
somethingSomething

2

Das folgende Beispiel sollte und sollte ein Kommentar zu dmitry.malikovs Antwort sein, aber wegen der unbrauchbaren Verwendung des sichtbaren Kommentarraums habe ich mich dazu entschieden, ihn hier zu präsentieren, wo er zumindest zu sehen sein wird. ..

Dies ist eine einfache Variation des von dmitry Single-Pass - Verfahren awk.
Es werden alle "gleich langen" Zeilen gedruckt. (Hinweis: delete arrayist eine Gawk-Erweiterung).

awk 'length >x { delete y; x=length }
     length==x { y[NR]=$0 } END{ for (z in y) print y[z] }' file

1

In reiner 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[@]}"

Wie es ist, kann der Code ungültige Ergebnisse zurückgeben. Die Einstellung entfernt _max_line[0]=${_line}nicht den Rest aller zuvor angesammelten kürzeren "längsten Zeilen" ... unset _max_line
löscht

@fered Danke dafür, wurde ziemlich schnell geschrieben. Fest.
Chris Down

0

Ich habe dafür ein kleines Shell-Skript entwickelt. Es zeigt die Länge, die Zeilennummer und die Zeile selbst nach Länge an, die eine bestimmte Größe von 80 Zeichen überschreitet:

#!/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


1
Es gibt einige Verbesserungen, die Sie vornehmen könnten. Zitieren Sie Ihre Variablen . Dies führt zu Fehlern bei Dateinamen, die Leerzeichen oder andere seltsame Zeichen enthalten. Verwenden $*ist selten eine gute Idee, die Sie wollen"$@" . Die /.*/in Ihrem awktut nichts, da dies auch mit Leerzeilen übereinstimmt. Sie könnten vermeiden, dem zu entkommen, \$0wenn Sie das einfache Anführungszeichen verwenden 'EOF'. Warum einen leeren BEGIN{}Block verwenden? Schließlich brauchen Sie nicht cat, nurawk . . . "$file" | . . .
terdon

1
Sie können das Ganze auch direkt in awk awk -vmax=15 '{len=length($0); if(len>=max){printf("%s, %d at line # %d %s\n", FILENAME, len, NR, $0);}}' file*
erledigen

-3

Sie können verwenden wc:

wc -L fileName

3
Bitte lesen Sie die Frage noch einmal. Die erforderliche Ausgabe ist die längste Zeile selbst, nicht die Länge der längsten Zeile. Siehe auch Peter.Os Kommentar zum wc -LNachteil.
manatwork
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.