Comment imprimer la plus longue ligne dans un fichier?

35

Je cherche la méthode la plus simple pour imprimer la plus longue ligne d'un fichier. J'ai fait quelques recherches sur Google et, étonnamment, je n'ai pas semblé trouver de réponse. J'imprime fréquemment la longueur de la plus longue ligne d'un fichier, mais je ne sais pas comment imprimer la plus longue ligne. Quelqu'un peut-il fournir une solution pour imprimer la plus longue ligne d'un fichier? Merci d'avance.

dr.bunsen
la source
1
Qu'en est-il quand il y a plusieurs "plus longues" lignes? Parce que vous voulez plus qu'une simple longueur maximale, voulez-vous voir toutes les occurrences de lignes égales?
Peter.O

Réponses:

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

UPD : résumant tous les conseils dans les commentaires

awk 'length > max_length { max_length = length; longest_line = $0 } END { print longest_line }' ./text 
ДМИТРИЙ МАЛИКОВ
la source
3
C’est, appeler une autre commande ( cat) et utiliser un tube sont des opérations coûteuses, sans compter qu’il est plus efficace pour awk de lire le fichier. Les conséquences sur les performances sont clairement perceptibles si cela est fait fréquemment, et même dans ce cas, vous utilisez complètement mal cat.
Chris Down
7
@laebshade Il y a absolument une raison - c'est pour que vous n'ayez pas besoin de savoir quelles commandes portent des noms de fichiers et lesquelles ne le font pas, ou ne vous souciez pas de savoir quelle commande sera exécutée en premier dans le pipeline. Si vous écrivez un script qui doit être exécuté fréquemment, ne vous inquiétez pas. Si vous écrivez un élément unique pour trouver la ligne la plus longue d'un fichier, le processus supplémentaire et la fraction de temps utilisée ne sont absolument pas pertinents. C'est ridicule que les gens soient si obsédés par ça ici, c'est incroyablement mineur
Michael Mrozek
4
@ Keith Thompson: catn'est pas inutile ici. Cela pourrait être inutile pour un ordinateur, mais pour un lecteur humain, cela pourrait être très utile. La première variante montre clairement l'entrée. Le flux est plus naturel (de gauche à droite). Dans le second cas, vous ne savez pas quelle est la saisie, sauf si vous faites défiler la fenêtre.
JFS
1
@JFSebastian Même si vous le voulez à gauche, vous n'en avez pas besoin cat. < file commandfonctionne très bien.
Chris Down
3
@JFSebastian: Le fait qu'une redirection puisse être écrite au début d'une commande est quelque peu obscur; < filename commandest équivalent à filename < commanddans chaque coquille que j'ai essayée. Mais une fois que vous en êtes conscient, vous pouvez en tirer parti lorsque vous écrivez de longs tubes qui indiquent clairement la direction du flux de données (sans invoquer de commande supplémentaire):< input-file command1 | command2 | command3 > output-file
Keith Thompson
6
cat filename | awk '{ print length }' | sort -n | tail -1
aspinalln
la source
+1 Il y avait beaucoup de solutions intéressantes à cela, mais c'était la plus simple. (Ce serait plus simple sans le chat en laissant awk lire le fichier, mais pourquoi chipoter?)
user1683793
5
sed -rn "/.{$(<file expand -t1 |wc -L)}/{p;q}" file

Ceci lit d'abord le fichier à l'intérieur de la substitution de commande et affiche la longueur de la plus longue ligne (auparavant, expandconvertit les tabulations en espaces pour surmonter la sémantique de wc -L- chaque tabulation de la ligne ajoutera 8 au lieu de 1 à la longueur de la ligne). Cette longueur est ensuite utilisée dans une sedexpression signifiant "trouver une ligne de ce nombre de caractères, l'imprimer, puis quitter". Cela peut donc être aussi optimal que la plus longue ligne se trouve en haut du fichier, heheh (merci pour les commentaires impressionnants et constructifs).

Un autre, j'avais pensé plus tôt que le sed (en bash):

#!/bin/bash
while read -r line; do
    (( ${#line} > max )) && max=${#line} && longest="$line"
done
echo "$longest"
à
la source
2
Cette méthode est très coûteuse et lente.
Chris Down
2
@ Chris Down: Oh oui c'est ça. Mais la question portait sur la méthode de tri, pas la plus efficace. Fonctionne très bien pour les fichiers de taille petite à moyenne ou les tâches non critiques.
Ata
3
AVERTISSEMENT : l'option de wc -L, --max-line-lengthimprime la longueur de la plus longue ligne, conformément à la page de manuel, mais si vous creusez plus profondément (comme lorsque vous obtenez des résultats erronés / inattendus ), vous constaterez que cette option incrémente la longueur de 8 pour chaque 1 caractère tab \x09 voir cette Unix & Linux Q / A
Peter.O
PS Votre réponse imprimera toutes les lignes "également les plus longues", ce qui est probablement une bonne chose ... Pour forcer wc à ne compter qu'un caractère par onglet, cela fonctionne. sed -rn "/.{$(<file expand -t1 |wc -L)}/p" file
Peter.O
1
read lineinterprétera les caractères échappés barre oblique inverse comme le charbon littéral, par exemple \Aresloves à Aqui des rapports sur les cours effectivement un plus court que octet utilisation réelle ... Pour éviter cela échappé à l' interprétation, l' utilisation: read -r line. . . . Aussi, pour que la version sed + wc s'arrête après la première "ligne la plus longue", remplacez ppar {p;q}..sed -rn "/.{$(<file expand -t1 |wc -L)}/{p;q}" file
Peter.O
4

Voici une solution Perl:

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

Ou, si vous voulez imprimer toutes les lignes les plus longues

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

Comme je n'avais rien de mieux à faire, j'ai effectué des tests sur un fichier texte 625M. Étonnamment, ma solution Perl était toujours plus rapide que les autres. Certes, la différence avec la awksolution acceptée est minime, mais elle est là. Évidemment, les solutions imprimant plusieurs lignes étant plus lentes, j'ai donc trié par type, du plus rapide au plus lent.

N'imprimez qu'une des plus longues lignes:

$ 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

Imprimer toutes les lignes les plus longues:

$ 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
la source
3

Grep la première ligne la plus longue

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

La commande est inhabituellement difficile à lire sans pratique, car elle mélange la syntaxe shell et regexp.
Pour l'explication, je vais d'abord utiliser le pseudocode simplifié. Les lignes commençant par ##ne sont pas exécutées dans le shell.
Ce code simplifié utilise le nom de fichier F et laisse de côté les citations et des parties de regexps pour des raisons de lisibilité.

Comment ça marche

La commande a deux parties, une grep- et une wcinvocation:

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

Le wcest utilisé dans une extension de processus $( ... ), il est donc exécuté avant grep. Il calcule la longueur de la plus longue ligne. La syntaxe d'expansion du shell est mélangée à la syntaxe du modèle d'expression régulière d'une manière qui prête à confusion, donc je décomposerai l'extension du processus:

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

Ici, l’extension du processus a été remplacée par la valeur qu’elle renverrait, créant la grepligne de commande utilisée. Nous pouvons maintenant lire l’expression régulière plus facilement: elle correspond exactement de start ( ^) à end ( $) de la ligne. L'expression entre eux correspond à n'importe quel caractère sauf newline, répété 42 fois. Combinées, il s’agit de lignes comportant exactement 42 caractères.


Revenons maintenant aux commandes réelles du shell: L' grepoption -E( --extended-regexp) permet de ne pas échapper à la {}lisibilité. Option -m 1( --max-count=1) le fait s'arrêter après la première ligne. Le <dans la wccommande écrit le fichier dans son stdin, pour empêcher l' wcimpression du nom du fichier avec la longueur.

Quelles lignes les plus longues?

Pour rendre les exemples plus lisibles avec le nom de fichier apparaissant deux fois, je vais utiliser une variable fpour le nom de fichier; Chacun $fdans l'exemple pourrait être remplacé par le nom du fichier.

f="file.txt"

Affiche la première ligne la plus longue - la première ligne aussi longue que la plus longue:

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

Afficher toutes les lignes les plus longues - toutes les lignes aussi longues que la ligne la plus longue:

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

Affiche la dernière ligne la plus longue - la dernière ligne aussi longue que la ligne la plus longue:

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

Afficher la ligne la plus longue unique - la ligne la plus longue plus longue que toutes les autres lignes, ou échouer:

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

(La dernière commande est encore plus inefficace que les autres, car elle répète la commande grep complète. Elle doit évidemment être décomposée de manière à ce que la sortie wcet les lignes écrites par grepsoient enregistrées dans des variables.
Notez que toutes les lignes les plus longues peuvent en réalité être toutes les lignes. Pour enregistrer dans une variable, seules les deux premières lignes doivent être conservées.)

Volker Siegel
la source
Wow super réponse, a beaucoup appris. merci
quelque chose quelque chose
2

L'exemple suivant allait être, et aurait dû être, un commentaire sur la réponse de dmitry.malikov , mais à cause de l' utilisation inutile de l'espace de commentaire visible ici, j'ai choisi de le présenter ici, où il sera au moins visible. ..

Ceci est une simple variante de la méthode awk à passe unique de dmitry .
Il imprime toutes les lignes "égales les plus longues". (Remarque. delete arrayEst une extension gawk).

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

En pure 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
la source
Tel quel, le code peut renvoyer des résultats invalides. Le réglage _max_line[0]=${_line}ne supprime pas le reste des "lignes les plus longues" précédemment accumulées unset _max_line...
effacera
@fered Merci pour cela, a été écrit assez rapidement. Fixé.
Chris Down
0

J'ai développé un petit script shell pour cela. Il affiche la longueur, le numéro de ligne et le trait lui-même par longueur dépassant une taille donnée, telle que 80 caractères:

#!/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
la source
1
Vous pouvez apporter quelques améliorations. Citez vos variables . Cela se répercutera sur tous les noms de fichiers contenant des espaces ou d'autres caractères étranges. L'utilisation $*est rarement une bonne idée, vous voulez"$@" . Le /.*/dans votre awkne fait rien car cela correspond aussi aux lignes vides. Vous pourriez éviter d’échapper à la \$0citation simple 'EOF'. Pourquoi utiliser un BEGIN{}bloc vide ? Enfin, vous n’avez pas besoin cat, simplementawk . . . "$file" | . . .
terdon
1
Vous pouvez également faire le travail directement dans 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

Vous pouvez utiliser wc:

wc -L fileName
ynot1074
la source
3
Veuillez relire la question. La sortie requise est la ligne la plus longue elle-même et non la longueur de la ligne la plus longue. Voir également le commentaire de Peter.O concernant son wc -Linconvénient.
manatwork