Como imprimir a linha mais longa de um arquivo?

35

Estou procurando o método mais simples para imprimir a linha mais longa em um arquivo. Eu pesquisei no Google e surpreendentemente não consegui encontrar uma resposta. Frequentemente, imprimo o comprimento da linha mais longa em um arquivo, mas não sei como realmente imprimir a linha mais longa. Alguém pode fornecer uma solução para imprimir a linha mais longa em um arquivo? Desde já, obrigado.

dr.bunsen
fonte
11
E quando existem várias linhas "mais longas"? Como você deseja mais do que um comprimento máximo simples, deseja ver todas as instâncias de linhas iguais por mais tempo?
Peter.O

Respostas:

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

UPD : resumindo todos os conselhos nos comentários

awk 'length > max_length { max_length = length; longest_line = $0 } END { print longest_line }' ./text 
ДМИТРИЙ МАЛИКОВ
fonte
3
Ou seja, chamar outro comando ( cat) e usar um canal são operações caras, sem mencionar que é mais eficiente para o awk ler apenas o arquivo. As implicações de desempenho são definitivamente perceptíveis se isso for feito com freqüência e, mesmo assim, você estiver usando mal o uso cat.
Chris Baixo
7
@laebshade Há absolutamente uma razão - é para que você não precise se lembrar de quais comandos recebem nomes de arquivos e quais não, ou se preocupam com o comando que será executado primeiro no pipeline. Se você estiver escrevendo um script que é executado com frequência, preocupe-se com algo assim. Se você está escrevendo uma coisa única para encontrar a linha mais longa em um arquivo, o processo extra e a quantidade fracionada de tempo consumida são completamente irrelevantes. É bobagem que as pessoas estão tão obcecados com ele aqui, é incrivelmente menor
Michael Mrozek
4
@ Keith Thompson: catnão é inútil aqui. Pode ser inútil para um computador, mas para um leitor humano pode fornecer valor. A primeira variante mostra claramente a entrada. O fluxo é mais natural (da esquerda para a direita). No segundo caso, você não sabe qual é a entrada, a menos que você role a janela.
JFS
11
@JFSebastian Mesmo que você queira à esquerda, não precisa cat. < file commandfunciona muito bem.
Chris Baixo
3
@JFSebastian: O fato de um redirecionamento poder ser escrito no início de um comando é um tanto obscuro; < filename commandé equivalente a filename < commandem todos os shell que eu tentei. Mas uma vez que você está ciente disso, você pode tirar vantagem disso ao escrever longos canos que mostram claramente a direção do fluxo de dados (sem invocar um comando extra):< input-file command1 | command2 | command3 > output-file
Keith Thompson
6
cat filename | awk '{ print length }' | sort -n | tail -1
aspinalln
fonte
+1 Havia muitas soluções interessantes para isso, mas essa era a mais simples. (Seria mais simples sem o gato, deixando o awk ler o arquivo, mas por que
reclamar
5
sed -rn "/.{$(<file expand -t1 |wc -L)}/{p;q}" file

Isso primeiro lê o arquivo dentro da substituição de comando e gera o comprimento da linha mais longa (anteriormente, expandconverte guias em espaços, para superar a semântica de wc -L- cada guia na linha adicionará 8 em vez de 1 ao comprimento da linha). Esse comprimento é usado em uma sedexpressão que significa "encontre uma linha com esse número de caracteres, imprima-a e saia". Então, na verdade, isso pode ser o ideal, pois a linha mais longa fica perto do topo do arquivo, heheh (obrigado pelos comentários impressionantes e construtivos).

Outro, pensei antes do sed (no bash):

#!/bin/bash
while read -r line; do
    (( ${#line} > max )) && max=${#line} && longest="$line"
done
echo "$longest"
ata
fonte
2
Este método é muito caro e lento.
Chris Baixo
2
@ Chris Down: Oh sim, é. Mas a pergunta era sobre o método mais gentil, não o mais eficiente. No entanto, funciona muito bem para arquivos pequenos a médios ou para tarefas não críticas.
ata
3
AVISO : a opção wc's -L, --max-line-lengthimprime o comprimento da linha mais longa, de acordo com a página do manual, mas se você for mais fundo (como quando obtém resultados errados / inesperados ), verá que essa opção aumenta o comprimento em 8 para cada caractere de 1 guia \x09 veja este Q / A em Unix e Linux
Peter.O
PS. Sua resposta imprimirá todas as linhas "igualmente longas", o que provavelmente é uma coisa boa ... Para forçar o wc a contar apenas 1 caracter por guia, isso funciona. sed -rn "/.{$(<file expand -t1 |wc -L)}/p" file
Peter.O
11
read lineirá interpretar caracteres escapou-barra invertida como o caractere literal, por exemplo \Aresloves para A, o que naturalmente efetivamente relata um mais curto do que real byte-uso ... Para evitar que isso escapou interpretação, use: read -r line. . . . Além disso, para tornar o + wc sed versão parar após a primeira "linha mais longa", a mudança ppara {p;q}..sed -rn "/.{$(<file expand -t1 |wc -L)}/{p;q}" file
Peter.O
4

Aqui está uma solução Perl:

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

Ou, se você quiser imprimir todas as linhas mais longas

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

Como não tinha nada melhor para fazer, executei alguns benchmarks em um arquivo de texto de 625M. Surpreendentemente, minha solução Perl foi consistentemente mais rápida que as outras. É verdade que a diferença com a awksolução aceita é pequena, mas existe. Obviamente, as soluções que imprimem várias linhas são mais lentas, por isso classifiquei por tipo, da mais rápida para a mais lenta.

Imprima apenas uma das linhas mais longas:

$ 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

Imprimir todas as linhas mais longas:

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

Grep a primeira linha mais longa

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

O comando é extraordinariamente difícil de ler sem prática, porque combina sintaxe shell e regexp.
Para explicação, usarei primeiro o pseudocódigo simplificado. As linhas que começam com ##não são executadas no shell.
Esse código simplificado usa o nome do arquivo F e deixa de fora citações e partes de regexps para facilitar a leitura.

Como funciona

O comando possui duas partes, a grep- e uma wcinvocação:

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

O wcé usado em uma expansão de processo e $( ... ), portanto, é executado antes grep. Calcula o comprimento da linha mais longa. A sintaxe de expansão do shell é misturada com a sintaxe do padrão de expressão regular de uma maneira confusa; portanto, decompomos a expansão do processo:

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

Aqui, a expansão do processo foi substituída pelo valor que retornaria, criando a greplinha de comando usada. Agora podemos ler a expressão regular com mais facilidade: ela corresponde exatamente do início ( ^) ao final ( $) da linha. A expressão entre eles corresponde a qualquer caractere, exceto nova linha, repetida por 42 vezes. Combinadas, ou seja, linhas que consistem em exatamente 42 caracteres.


Agora, voltando aos comandos reais do shell: A grepopção -E( --extended-regexp) permite não escapar da {}legibilidade. Option -m 1( --max-count=1) faz com que pare depois que a primeira linha for encontrada. O <no wccomando grava o arquivo para seu stdin, para evitar que wcimprimam o nome do arquivo junto com o comprimento.

Quais linhas mais longas?

Para tornar os exemplos mais legíveis com o nome do arquivo ocorrendo duas vezes, usarei uma variável fpara o nome do arquivo; Cada um $fno exemplo pode ser substituído pelo nome do arquivo.

f="file.txt"

Mostre a primeira linha mais longa - a primeira linha que contenha a linha mais longa:

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

Mostrar todas as linhas mais longas - todas as linhas que contenham a linha mais longa:

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

Mostrar a última linha mais longa - a última linha que é tão longa quanto a linha mais longa:

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

Mostre a linha mais longa - a linha mais longa que todas as outras linhas ou falhe:

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

(O último comando é ainda mais ineficiente que os outros, pois repete o comando grep completo. Obviamente, ele deve ser decomposto para que a saída wce as linhas escritas por grepsejam salvas nas variáveis.
Observe que todas as linhas mais longas podem na verdade ser todas as linhas Para salvar em uma variável, apenas as duas primeiras linhas precisam ser mantidas.)

Volker Siegel
fonte
Uau ótima resposta, aprendi muito com isso. graças
somethingSomething
2

O exemplo a seguir seria e deveria ter sido um comentário para a resposta de dmitry.malikov , mas por causa do Uso inútil do espaço de comentários visível lá, eu escolhi apresentá-lo aqui, onde pelo menos será visto. ..

Esta é uma variação simples do de Dmitry método awk single-pass.
Imprime todas as linhas "iguais maiores". (Nota. delete arrayÉ uma extensão gawk).

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

Na festança pura:

#!/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
fonte
Como está, o código pode retornar resultados inválidos. Configuração _max_line[0]=${_line}não remove o resto de quaisquer mais curtos "mais longas linhas" previamente acumulados ... unset _max_linevai limpar toda a matriz ...
Peter.O
@fered Obrigado por isso, foi escrito muito rapidamente. Fixo.
Chris Baixo
0

Eu desenvolvi um pequeno script de shell para isso. Ele exibe o comprimento, a linha # e a própria linha pelo comprimento que excede um tamanho específico, 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
fonte
11
Existem algumas melhorias que você pode fazer. Cite suas variáveis . Isso será interrompido em qualquer nome de arquivo que contenha espaço em branco ou outros caracteres estranhos. Usar $*raramente é uma boa ideia, você quer"$@" . O /.*/no seu awknão faz nada, pois também corresponde a linhas vazias. Você pode evitar escapar \$0se citar o 'EOF'. Por que usar um BEGIN{}bloco vazio ? Finalmente, você não precisa cat, apenasawk . . . "$file" | . . .
terdon
11
Você também pode fazer tudo diretamente no 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

Você pode usar wc:

wc -L fileName
ynot1074
fonte
3
Por favor, leia a pergunta novamente. A saída necessária é a linha mais longa em si, não o comprimento da linha mais longa. Veja também o comentário de Peter.O sobre wc -La desvantagem de.
Manatwork