¿Cómo imprimir la línea más larga en un archivo?

35

Estoy buscando el método más simple para imprimir la línea más larga en un archivo. Busqué en Google y sorprendentemente no pude encontrar una respuesta. Con frecuencia imprimo la longitud de la línea más larga en un archivo, pero no sé cómo imprimir realmente la línea más larga. ¿Alguien puede proporcionar una solución para imprimir la línea más larga en un archivo? Gracias por adelantado.

Dr. Bunsen
fuente
1
¿Qué pasa cuando hay múltiples líneas "más largas"? Debido a que desea más que una longitud máxima simple, ¿desea ver todas las instancias de líneas que son iguales más largas?
Peter.O

Respuestas:

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

UPD : resumiendo todos los consejos en los comentarios

awk 'length > max_length { max_length = length; longest_line = $0 } END { print longest_line }' ./text 
ДМИТРИЙ МАЛИКОВ
fuente
3
Lo es, tanto llamar a otro comando ( cat) como usar una tubería son operaciones costosas, sin mencionar que es más eficiente para awk simplemente leer el archivo. Las implicaciones de rendimiento son definitivamente notables si esto se hace con frecuencia, y aun así, está haciendo un mal uso por completo cat.
Chris Down el
77
@laebshade Hay una razón absoluta: es para que no necesite recordar qué comandos toman nombres de archivos y cuáles no, o no le importa qué comando se ejecutará primero en la tubería. Si vas a escribir un script que se ejecuta con frecuencia, preocúpate por algo como esto. Si está escribiendo algo único para encontrar la línea más larga en un archivo, el proceso adicional y la cantidad fraccional de tiempo consumido es completamente irrelevante. Es una tontería que la gente esté tan obsesionada con eso aquí, es increíblemente menor
Michael Mrozek
44
@Keith Thompson: catno es inútil aquí. Puede ser inútil para una computadora, pero para un lector humano podría proporcionar valor. La primera variante muestra claramente la entrada. El flujo es más natural (de izquierda a derecha). En el segundo caso, no sabe cuál es la entrada a menos que desplace la ventana.
jfs
1
@JFSebastian Incluso si lo quieres a la izquierda, no lo necesitas cat. < file commandfunciona bien
Chris Down
3
@JFSebastian: El hecho de que se pueda escribir una redirección al comienzo de un comando es algo oscuro; < filename commandes equivalente a filename < commanden cada shell que he probado. Pero una vez que lo sepa, puede aprovecharlo al escribir tuberías largas que muestren claramente la dirección del flujo de datos (sin invocar un comando adicional):< input-file command1 | command2 | command3 > output-file
Keith Thompson
6
cat filename | awk '{ print length }' | sort -n | tail -1
aspinalln
fuente
+1 Hubo muchas soluciones interesantes para esto, pero esta fue la más simple. (Sería más simple sin el gato dejando que awk lea el archivo, pero ¿por qué objetar?)
usuario1683793
5
sed -rn "/.{$(<file expand -t1 |wc -L)}/{p;q}" file

Esto lee primero el archivo dentro de la sustitución del comando y genera la longitud de la línea más larga (anteriormente, expandconvierte las pestañas en espacios, para superar la semántica de wc -L- cada pestaña en la línea agregará 8 en lugar de 1 a la longitud de la línea). Esta longitud se usa en una sedexpresión que significa "encontrar una línea de este número de caracteres, imprimirla y salir". Entonces, en realidad, esto puede ser tan óptimo como la línea más larga está cerca de la parte superior del archivo, jeje (gracias por los comentarios impresionantes y constructivos).

Otro, había pensado antes que el sed (en bash):

#!/bin/bash
while read -r line; do
    (( ${#line} > max )) && max=${#line} && longest="$line"
done
echo "$longest"
ata
fuente
2
Este método es muy costoso y lento.
Chris Down el
2
@ Chris Down: Oh sí, lo es. Pero la pregunta era sobre el método más ordenado, no el más eficiente. Sin embargo, funciona finamente para archivos pequeños o medianos o tareas no críticas.
ata
3
ADVERTENCIA : la opción wc -L, --max-line-lengthimprime la longitud de la línea más larga, de acuerdo con la página del manual, pero si profundiza (como cuando obtiene resultados incorrectos / inesperados ), encontrará que esta opción incrementa la longitud en 8 por cada 1 pestaña de caracteres \x09 ver este Q / A de Unix y Linux
Peter
PD. Su respuesta imprimirá todas las líneas "igualmente largas", lo que probablemente sea algo bueno ... Para forzar a wc a contar solo 1 carácter por pestaña, esto funciona. sed -rn "/.{$(<file expand -t1 |wc -L)}/p" file
Peter.O
1
read lineinterpretará caracteres escapados como el carbón literal, por ejemplo \Aresloves a A, que por supuesto efectivamente informa de un corto que real de bytes en el uso ... Para evitar esto escapado interpretación, utilice: read -r line. . . . Además, para cerrar la versión sed + wc después de la primera "línea más larga", cambie pa {p;q}...sed -rn "/.{$(<file expand -t1 |wc -L)}/{p;q}" file
Peter.O
4

Aquí hay una solución de Perl:

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

O, si desea imprimir todas las líneas más largas

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

Como no tenía nada mejor que hacer, ejecuté algunos puntos de referencia en un archivo de texto 625M. Sorprendentemente, mi solución Perl fue consistentemente más rápida que las otras. Por supuesto, la diferencia con lo aceptadoawk solución es pequeña, pero está ahí. Obviamente, las soluciones que imprimen varias líneas son más lentas, así que las he ordenado por tipo, de la más rápida a la más lenta.

Imprima solo una de las líneas más largas:

$ 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

Imprima todas las líneas más largas:

$ 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
fuente
3

Grep la primera línea más larga

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

El comando es inusualmente difícil de leer sin práctica porque mezcla la sintaxis de shell y regexp.
Para explicación, usaré pseudocódigo simplificado primero. Las líneas que comienzan con ##no se ejecutan en el shell.
Este código simplificado usa el nombre de archivo F y deja de lado las comillas y partes de expresiones regulares para facilitar la lectura.

Cómo funciona

El comando tiene dos partes, una grep- y una wcinvocación:

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

Se wcutiliza en una expansión de proceso $( ... ), por lo que se ejecuta antes grep. Calcula la longitud de la línea más larga. La sintaxis de expansión de shell se mezcla con la sintaxis del patrón de expresión regular de una manera confusa, por lo que descompondré la expansión del proceso:

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

Aquí, la expansión del proceso se reemplazó con el valor que devolvería, creando la greplínea de comando que se utiliza. Ahora podemos leer la expresión regular más fácilmente: coincide exactamente desde el inicio ( ^) hasta el final ($ ) de la línea. La expresión entre ellos coincide con cualquier carácter, excepto la nueva línea, que se repite 42 veces. Combinados, es decir, líneas que consisten en exactamente 42 caracteres.


Ahora, volviendo a los comandos de shell reales: la grepopción -E( --extended-regexp) permite no escapar de la {}legibilidad. La opción -m 1( --max-count=1) hace que se detenga después de encontrar la primera línea. El <en el wccomando escribe el archivo en su stdin, para evitar wcimprimir el nombre del archivo junto con la longitud.

¿Qué líneas más largas?

Para hacer que los ejemplos sean más legibles con el nombre de archivo que ocurre dos veces, usaré una variable fpara el nombre de archivo; Cada uno $fen el ejemplo podría ser reemplazado por el nombre del archivo.

f="file.txt"

Mostrar la primera línea más larga : la primera línea que es tan larga como la línea más larga:

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

Mostrar todas las líneas más largas : todas las líneas que son tan largas como la línea más larga:

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

Mostrar la última línea más larga : la última línea que es tan larga como la línea más larga:

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

Mostrar la línea más larga individual : la línea más larga más larga que todas las demás líneas, o falla:

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

(El último comando es aún más ineficiente que los demás, ya que repite el comando grep completo. Obviamente, debe descomponerse para que la salida wcy las líneas escritas por grepse guarden en las variables.
Tenga en cuenta que todas las líneas más largas pueden ser todas líneas Para guardar en una variable, solo se deben mantener las dos primeras líneas).

Volker Siegel
fuente
Wow gran respuesta, aprendí mucho de ella. gracias
algo Algo
2

El siguiente ejemplo iba a ser, y debería haber sido, un comentario a la respuesta de dmitry.malikov , pero debido al uso inútil del espacio visible de comentarios allí, he elegido presentarlo aquí, donde al menos se verá. ..

Esta es una variación simple del método awk de paso único de dmitry .
Imprime todas las líneas "iguales más largas". (Nota: delete arrayes una extensión gawk).

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

En puro golpe:

#!/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
fuente
Tal como está, el código puede devolver resultados no válidos. La configuración _max_line[0]=${_line}no elimina el resto de las "líneas más largas" más cortas previamente acumuladas ... unset _max_lineborrará toda la matriz ...
Peter.O
@fered Gracias por eso, fue escrito bastante rápido. Fijo.
Chris Down
0

He desarrollado un pequeño script de shell para esto. Muestra la longitud, el número de línea y la línea por longitud que excede un tamaño particular como 80 caracteres:

#!/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
fuente
1
Hay algunas mejoras que podrías hacer. Cita tus variables . Esto se romperá en cualquier nombre de archivo que contenga espacios en blanco u otros caracteres extraños. Usando $*rara vez es una buena idea, que desea"$@" . El /.*/en su awkno hace nada ya que también coincide con líneas vacías. Podrías evitar escapar de la \$0cita simple 'EOF'. ¿Por qué usar un BEGIN{}bloque vacío ? Finalmente, no necesita cat, soloawk . . . "$file" | . . .
terdon
1
También podría hacer todo en awk directamente:awk -vmax=15 '{len=length($0); if(len>=max){printf("%s, %d at line # %d %s\n", FILENAME, len, NR, $0);}}' file*
terdon
-3

Puedes usar wc:

wc -L fileName
ynot1074
fuente
3
Por favor lea la pregunta nuevamente. La salida requerida es la línea más larga en sí misma, no la longitud de la línea más larga. También vea el comentario de Peter.O sobre wc -Lel inconveniente de.
manatwork