Come stampare la linea più lunga in un file?


35

Sto cercando il metodo più semplice per stampare la riga più lunga in un file. Ho fatto alcuni googling e sorprendentemente non sono riuscito a trovare una risposta. Spesso stampo la lunghezza della linea più lunga in un file, ma non so come stampare effettivamente la linea più lunga. Qualcuno può fornire una soluzione per stampare la riga più lunga in un file? Grazie in anticipo.


1
Che dire quando ci sono più linee "più lunghe"? Perché vuoi più di una semplice lunghezza massima, vuoi vedere tutte le istanze di linee che sono uguali più lunghe?
Peter

Risposte:


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

UPD : riassumendo tutti i consigli nei commenti

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

3
Sia chiamare un altro comando ( cat) sia usare una pipe sono operazioni costose, per non parlare del fatto che è più efficiente per awk leggere il file. Le implicazioni sulle prestazioni sono sicuramente evidenti se questo viene fatto frequentemente e anche in questo caso, stai completamente abusando cat.
Chris Down,

7
@laebshade C'è assolutamente una ragione - è quindi non è necessario ricordare quali comandi prendono i nomi dei file e quali no, o preoccuparsi di quale comando verrà eseguito per primo nella pipeline. Se stai per scrivere una sceneggiatura che viene eseguita frequentemente, preoccupati di qualcosa del genere. Se stai scrivendo una cosa una tantum per trovare la riga più lunga in un file, il processo extra e la quantità frazionaria di tempo consumato è completamente irrilevante. È sciocco che le persone siano così ossessionate da questo, è incredibilmente minore
Michael Mrozek

4
@Keith Thompson: catqui non è inutile. Potrebbe essere inutile per un computer, ma per un lettore umano potrebbe fornire valore. La prima variante mostra chiaramente l'input. Il flusso è più naturale (da sinistra a destra). Nel secondo caso non sai quale sia l'input se non scorri la finestra.
jfs

1
@JFSebastian Anche se lo vuoi a sinistra, non è necessario cat. < file commandfunziona benissimo.
Chris Down,

3
@JFSebastian: il fatto che un reindirizzamento possa essere scritto all'inizio di un comando è alquanto oscuro; < filename commandè equivalente a filename < commandin ogni shell che ho provato. Ma una volta che ne sei consapevole, puoi trarne vantaggio quando scrivi lunghe pipe che mostrano chiaramente la direzione del flusso di dati (senza invocare un comando aggiuntivo):< input-file command1 | command2 | command3 > output-file
Keith Thompson

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

+1 C'erano molte soluzioni interessanti a questo, ma questa era la più semplice. (Sarebbe più semplice senza il gatto lasciando awk a leggere il file ma perché cavillo?)
user1683793

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

Questo prima legge il file all'interno della sostituzione del comando e genera la lunghezza della linea più lunga, (in precedenza, expandconverte le schede in spazi, per superare la semantica di wc -L- ogni scheda nella linea aggiungerà 8 anziché 1 alla lunghezza della linea). Questa lunghezza viene quindi utilizzata in sedun'espressione che significa "trova una linea con questo numero di caratteri, stampala, quindi esci". Quindi questo in realtà può essere ottimale in quanto la linea più lunga è vicino all'inizio del file, heheh (grazie ferito per i commenti fantastici e costruttivi).

Un altro, avevo pensato prima di quello sed (in bash):

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

2
Questo metodo è molto costoso e lento.
Chris Down,

2
@ Chris Down: Oh sì, lo è. Ma la domanda riguardava il metodo più ordinato, non il più efficiente. Funziona finemente per file medio-piccoli o per attività non critiche.
ata

3
ATTENZIONE : l'opzione wc -L, --max-line-lengthstampa la lunghezza della linea più lunga, secondo la pagina man, ma se scavi più a fondo (come in quando ottieni risultati errati / imprevisti ), scopri che questa opzione aumenta la lunghezza di 8 per ogni carattere di 1 tab \x09 vedi questo Q / A Unix & Linux
Peter.O

PS. La tua risposta stamperà tutte le righe "ugualmente più lunghe", il che è probabilmente una buona cosa ... Per forzare wc a contare solo 1 carattere per scheda, questo funziona. sed -rn "/.{$(<file expand -t1 |wc -L)}/p" file
Peter

1
read lineinterpreterà caratteri backslash escape come il carattere letterale, ad esempio \Aresloves a A, che ovviamente in modo efficace riporta una più breve rispetto effettivo di byte-uso ... Per evitare questo sfuggito interpretazione, uso: read -r line. . . . Inoltre, per chiudere la versione sed + wc dopo la prima "linea più lunga", passare pa {p;q}..sed -rn "/.{$(<file expand -t1 |wc -L)}/{p;q}" file
Peter.O

4

Ecco una soluzione Perl:

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

Oppure, se si desidera stampare tutte le linee più lunghe

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

Dato che non avevo niente di meglio da fare, ho eseguito alcuni benchmark su un file di testo 625M. Sorprendentemente, la mia soluzione Perl è stata costantemente più veloce delle altre. Certo, la differenza con la awksoluzione accettata è minuscola, ma è lì. Ovviamente, le soluzioni che stampano più righe sono più lente, quindi ho ordinato per tipo, dal più veloce al più lento.

Stampa solo una delle linee più lunghe:

$ 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

Stampa tutte le righe più lunghe:

$ 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 la prima linea più lunga

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

Il comando è insolitamente difficile da leggere senza pratica perché mescola la sintassi shell e regexp.
Per spiegazione, userò prima lo pseudocodice semplificato. Le linee che iniziano con ##non vengono eseguite nella shell.
Questo codice semplificato utilizza il nome del file F, e lascia fuori citazione e parti di regexps per la leggibilità.

Come funziona

Il comando ha due parti, a grep- e una wcchiamata:

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

La wcviene utilizzato in un processo di espansione, $( ... ), quindi viene eseguito prima grep. Calcola la lunghezza della linea più lunga. La sintassi di espansione della shell è mescolata con la sintassi del modello di espressione regolare in modo confuso, quindi decomporrò l'espansione del processo:

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

Qui, l'espansione del processo è stata sostituita con il valore che avrebbe restituito, creando la grepriga di comando utilizzata. Ora possiamo leggere più facilmente l'espressione regolare: corrisponde esattamente dall'inizio ( ^) alla fine ( $) della riga. L'espressione tra loro corrisponde a qualsiasi carattere tranne newline, ripetuto per 42 volte. Combinati, ovvero linee composte da esattamente 42 caratteri.


Ora, torniamo ai comandi reali della shell: l' grepopzione -E( --extended-regexp) consente di non sfuggire alla {}leggibilità. L'opzione -m 1( --max-count=1) lo fa arrestare dopo aver trovato la prima riga. Il <nel wccomando scrive il file nel suo stdin, per evitare wcdi stampare il nome del file insieme alla lunghezza.

Quali linee più lunghe?

Per rendere più leggibili gli esempi con il nome del file che si verifica due volte, userò una variabile fper il nome del file; Ciascuno $fnell'esempio potrebbe essere sostituito dal nome del file.

f="file.txt"

Mostra la prima linea più lunga - la prima linea che è lunga quanto la linea più lunga:

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

Mostra tutte le linee più lunghe - tutte le linee lunghe quanto la linea più lunga:

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

Mostra l' ultima riga più lunga - l'ultima riga che è lunga quanto la riga più lunga:

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

Mostra la linea più lunga singola - la linea più lunga più lunga di tutte le altre linee o non riesce:

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

(L'ultimo comando è ancora più inefficiente degli altri, poiché ripete il comando grep completo. Dovrebbe ovviamente essere decomposto in modo che l'output di wce le righe scritte da grepvengano salvati in variabili.
Notare che tutte le righe più lunghe possono effettivamente essere tutte righe Per salvare in una variabile, è necessario mantenere solo le prime due righe.)


Wow, ottima risposta, ho imparato molto da esso. grazie
qualcosa del

2

L'esempio seguente sarebbe stato, e avrebbe dovuto essere, un commento alla risposta di dmitry.malikov , ma a causa dell'uso inutile dello spazio dei commenti visibili lì, ho scelto di presentarlo qui, dove almeno sarà visto. ..

Questa è una semplice variante del metodo awk single-pass del dmitry .
Stampa tutte le linee "uguali più lunghe". (Nota. delete arrayÈ un'estensione gawk).

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

1

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

Così com'è, il codice può restituire risultati non validi. L'impostazione _max_line[0]=${_line}non rimuove il resto delle "linee più lunghe" più brevi accumulate in precedenza ... unset _max_linecancellerà l'intero array ...
Peter.O,

@fered Grazie per quello, è stato scritto abbastanza rapidamente. Fisso.
Chris Down,

0

Per questo ho sviluppato un piccolo script di shell. Visualizza lunghezza, linea # e linea stessa per lunghezza che supera una dimensione particolare come 80 caratteri:

#!/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
Ci sono alcuni miglioramenti che potresti apportare. Cita le tue variabili . Ciò si interromperà su tutti i nomi di file che contengono spazi bianchi o altri caratteri strani. L'uso $*è raramente una buona idea, vuoi"$@" . Il /.*/nel tuo awknon fa nulla poiché corrisponde anche a righe vuote. Potresti evitare di scappare \$0se citi una sola 'EOF'. Perché usare un BEGIN{}blocco vuoto ? Infine, non è necessario cat, soloawk . . . "$file" | . . .
terdon

1
Potresti anche fare tutto direttamente in awk direttamente:awk -vmax=15 '{len=length($0); if(len>=max){printf("%s, %d at line # %d %s\n", FILENAME, len, NR, $0);}}' file*
terdon

-3

Puoi usare wc:

wc -L fileName

3
Si prega di leggere di nuovo la domanda. L'output richiesto è la linea più lunga stessa, non la lunghezza della linea più lunga. Vedi anche il commento di Peter.O riguardo allo wc -Lsvantaggio.
arte
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.