Como encontrar uma linha com menos caracteres


22

Estou escrevendo um script de shell, usando qualquer comando geral do UNIX. Eu tenho que recuperar a linha que tem menos caracteres (espaço em branco incluído). Pode haver até 20 linhas.

Eu sei que posso usar head -$L | tail -1 | wc -mpara encontrar a contagem de caracteres da linha L. O problema é que o único método em que posso pensar, usando isso, seria escrever manualmente uma bagunça de instruções if, comparando os valores.

Dados de exemplo:

seven/7
4for
8 eight?
five!

Voltaria, 4forpois essa linha tinha menos caracteres.

No meu caso, se várias linhas tiverem o menor comprimento, uma única deverá ser retornada. Não importa qual é selecionado, desde que tenha o comprimento mínimo. Mas não vejo mal em mostrar os dois lados para outros usuários com outras situações.


5
E se houver várias linhas com comprimento de 4? Eles devem ser impressos também?
caos

No meu caso, se várias linhas tiverem o menor comprimento, uma única deverá ser retornada. Não importa qual é selecionado, desde que tenha o comprimento mínimo. Mas não vejo mal em mostrar os dois lados para outros usuários com outras situações.
Matthew D. Scholefield

Respostas:


13

Um jeito Perl. Observe que, se houver muitas linhas do mesmo comprimento mais curto, essa abordagem imprimirá apenas uma delas:

perl -lne '$m//=$_; $m=$_ if length()<length($m); END{print $m if $.}' file 

Explicação

  • perl -lne: -nsignifica "ler o arquivo de entrada linha por linha", -lfaz com que as novas linhas finais sejam removidas de cada linha de entrada e uma nova linha seja adicionada a cada printchamada; e -eé o script que será aplicado a cada linha.
  • $m//=$_: defina $ma linha atual ( $_), a menos que $mesteja definido. O //=operador está disponível desde o Perl 5.10.0.
  • $m=$_ if length()<length($m): se o comprimento do valor atual de $mfor maior que o comprimento da linha atual, salve a linha atual ( $_) como $m.
  • END{print $m if $.}: depois que todas as linhas tiverem sido processadas, imprima o valor atual da $mmenor linha. Os if $.garante que isto só acontece quando o número da linha ( $.) é definido, evitando a impressão de uma linha de vazio para a entrada em branco.

Como alternativa, como seu arquivo é pequeno o suficiente para caber na memória, você pode:

perl -e '@K=sort{length($a) <=> length($b)}<>; print "$K[0]"' file 

Explicação

  • @K=sort{length($a) <=> length($b)}<>: <>aqui está uma matriz cujos elementos são as linhas do arquivo. Eles sortos classificarão de acordo com seu comprimento e as linhas classificadas serão salvas como matriz @K.
  • print "$K[0]": imprime o primeiro elemento da matriz @K: a linha mais curta.

Se você deseja imprimir todas as linhas mais curtas, pode usar

perl -e '@K=sort{length($a) <=> length($b)}<>; 
         print grep {length($_)==length($K[0])}@K; ' file 

1
Adicione -Cpara medir o comprimento em termos de número de caracteres em vez de número de bytes. Em um código de idioma UTF-8, $$possui menos bytes que (2 x 3), mas mais caracteres (2 x 1).
Stéphane Chazelas 12/08/2015

17

Com sqlite3:

sqlite3 <<EOT
CREATE TABLE file(line);
.import "data.txt" file
SELECT line FROM file ORDER BY length(line) LIMIT 1;
EOT

Esse é o meu favorito aqui, nunca pensei em SQL ...
caos

2
Este é o estado de golfe código inteligente
shadowtalker

2
Isso lerá o arquivo inteiro na memória e / ou criará uma segunda cópia em disco? Nesse caso, é inteligente, mas ineficiente.
John Kugelman apoia Monica

1
@JohnKugelman Isso provavelmente absorverá as quatro linhas inteiras em um banco de dados apenas de memória temporária (é o que straceindica). Se você precisar trabalhar com arquivos muito grandes (e seu sistema não estiver trocando), você pode forçá-lo apenas acrescentando um nome de arquivo sqlite3 $(mktemp)e todos os dados serão gravados no disco.
precisa saber é o seguinte

Eu recebo os seguintes erros: "" "xaa: 8146: sem caractere" caractere "" "" e "" "xaa: 8825: 1 coluna esperada, mas encontrada 2 - extras ignorados" "". O arquivo consiste em documentos json 1 por cada linha .
Ahmedov

17

Aqui está uma variante de uma awksolução para imprimir a primeira linha mínima encontrada:

awk '
  NR==1 || length<len {len=length; line=$0}
  END {print line}
'

que pode ser simplesmente estendido por uma condição para imprimir todas as linhas mínimas:

awk '
  length==len {line=line ORS $0}
  NR==1 || length<len {len=length; line=$0}
  END {print line}'
'

12

O Python é bastante conciso e o código faz o que diz na lata:

python -c "import sys; print min(sys.stdin, key=len),"

A vírgula final é obscura, admito. Impede que a declaração de impressão adicione uma quebra de linha adicional. Além disso, você pode escrever isso no Python 3 suportando 0 linhas como:

python3 -c "import sys; print(min(sys.stdin, key=len, default='').strip('\n'))"


o que a lata diz?
mikeserv

@mikeserve: ele diz, "imprime o mínimo de sys.stdin, usando len como a chave" ;-)
Steve Jessop

1
ahh nada sobre tamanho binário, dependência de fluência ou tempo de execução, então?
mikeserv

2
@ mikeserv: não, as letras pequenas não estão na lata. Está em um folheto informativo em um arquivo trancado, em um porão, atrás de uma porta marcada "cuidado com o leopardo".
Steve Jessop

Peguei - assim em exibição.
mikeserv

10

Eu sempre adoro soluções com scripts de shell puro (sem exec!).

#!/bin/bash
min=
is_empty_input="yes"

while IFS= read -r a; do
    if [ -z "$min" -a "$is_empty_input" = "yes" ] || [ "${#a}" -lt "${#min}" ]; then
        min="$a"
    fi
    is_empty_input="no"
done

if [ -n "$a" ]; then
    if [ "$is_empty_input" = "yes" ]; then
        min="$a"
        is_empty_input="no"
    else
        [ "${#a}" -lt "${#min}" ] && min="$a"
    fi
fi

[ "$is_empty_input" = "no" ] && printf '%s\n' "$min"

Nota :

Há um problema com NUL bytes na entrada. Então, printf "ab\0\0\ncd\n" | bash this_scriptimprime em abvez de cd.


Este é realmente o mais puro. No entanto, a falta de jeito dos testes bashme convenceria a canalizar um resultado intermediário sort.
orion

2
Você já tentou bancar seu não executivo! solução versus outros que fazem? Aqui está uma comparação das diferenças de desempenho entre exec! e não exec! soluções para um problema semelhante. a execução de um processo separado raramente é vantajosa quando ocorre - em formas como var=$(get data)porque restringe o fluxo de dados a um único contexto - mas quando você move os dados por um pipeline - em um fluxo - cada exec aplicado geralmente é útil - porque habilita especialistas aplicação de programas modulares somente quando necessário.
mikeserv

1
@DigitalTrauma - uma sequência contínua de dígitos expandida não é mais ou menos isenta das condições que tornam a citação de shell necessária do que qualquer outra sequência expandida. $IFSnão é discriminatório por dígitos - mesmo que não exista um $IFSvalor padrão , embora muitos shells aceitem uma configuração de ambiente predefinida $IFS- e, portanto, esse não é um padrão particularmente confiável.
mikeserv


1
Obrigado a todos pelos comentários e votos positivos (alguns dos representantes devem ir ao @cuonglm para corrigir minha resposta). Geralmente, eu não recomendo que outras pessoas pratiquem diariamente scripts de shell puro, mas essa habilidade pode ser considerada muito útil em algumas condições extremas em que nada além de estático /bin/shestá disponível. Isso já aconteceu comigo várias vezes com hosts SunOS4 /usrperdidos ou .sodanificados. Agora, na era moderna do Linux, ainda ocasionalmente encontro situações semelhantes com sistemas embarcados ou initrd de sistemas com falha de inicialização. O BusyBox é uma das grandes coisas que adquirimos recentemente.
usar o seguinte comando

9

Aqui está uma zshsolução pura (ela imprime todas as linhas com o comprimento mínimo, de file):

IFS=$'\n'; print -l ${(M)$(<file):#${~${(o@)$(<file)//?/?}[1]}}

Exemplo de entrada:

seven/7
4for
8 eight?
five!
four

A saída é:

4for
four

Eu acho que precisa de uma breve explicação :-)


Primeiro, configuramos o separador de campo interno como nova linha:

IFS=$'\n';

Até aí tudo bem, agora a parte mais difícil. printusa o -lsinalizador para imprimir o resultado separado por novas linhas em vez de espaços.

Agora, começamos por dentro:

$(<file)

O arquivo é lido linha por linha e tratado como matriz. Então:

${(o@)...//?/?}

A obandeira diz que o resultado deve ser ordenado em ordem crescente, os @meios para tratar o resultado como um array também. A parte behind ( //?/?) é uma substituição que substitui todos os caracteres por a ?. Agora:

${~...[1]}

Pegamos o primeiro elemento da matriz [1], que é o mais curto, no seu caso, é agora ????.

${(M)$(<file):#...}

A correspondência é realizada em cada elemento da matriz separadamente e os elementos da matriz não correspondentes são removidos ( M). Cada elemento que corresponde ????(4 caracteres) permanece na matriz. Portanto, os elementos restantes são os que têm 4 caracteres (os mais curtos).

Editar: se você precisar de apenas uma das linhas mais curtas, esta versão modificada imprime a primeira:

IFS=$'\n'; print -l ${${(M)$(<file):#${~${(o@)$(<file)//?/?}[1]}}[1]}

8
tr -c \\n 1 <testfile |   #first transform every [^\n] char to a 1
grep -nF ''           |   #next get line numbers
paste -d: - testfile  |   #then paste it together with itself
sort  -t: -nk2,2          #then sort on second field

... e o vencedor é ... a linha 2, ao que parece.

2:1111:4for
4:11111:five!
1:1111111:seven/7
3:11111111:8 eight?

Mas o problema é que todas as linhas devem ter mais do que o dobro de comprimento para que funcionem - portanto, LINE_MAX seja efetivamente dividido pela metade. A causa é que ele está usando - o que, uma base 1? - para representar o comprimento da linha. Uma abordagem semelhante - e talvez mais organizada - pode ser compactar essas informações no fluxo. A primeira ideia nesse sentido que me ocorre é que eu deveria unexpand:

tr -c \\n \  <testfile    |   #transform all [^\n] to <space>
unexpand -t10             |   #squeeze every series of 10 to one tab
grep -nF ''               |   #and get the line numbers
sed    's/:/!d;=;:/;h;:big    #sed compares sequential lines
$P;$!N; /\(:[^ ]*\)\( *\)\n.*\1.*\2/!D     #newest line is shorter or...
        g;/:./!q;b big'   |   #not; quit input entirely for blank line
sed -f - -e q testfile        #print only first occurrence of shortest line

Isso imprime ...

2
4for

Outro, apenas sed :

sed -n '/^\n/D;s/\(.\)\(\n.*\)*/\1/g
$p;h;   s// /g;G;x;n;//!g;H;s// /g
G;      s/^\( *\)\(\n \1 *\)\{0,1\}\n//
D'      <infile >outfile

A sintaxe é compatível com os padrões - mas isso não garante que nenhum antigo sed lidem com os problemas \(reference-group\)\{counts\}corretamente - muitos não.

Basicamente, aplica o mesmo regexp à entrada repetidamente - o que pode ser muito benéfico quando é hora de compilá-las. Esse padrão é:

\(.\)\(\n.*\)*

Que corresponde a diferentes cadeias de maneiras diferentes. Por exemplo:

string1\nstring2\nstring3

... é correspondido com sin \1e ''a cadeia nula em\2 .

1\nstring2\nstring3

... é combinado com 1in \1e \nstring2\nstring3in\2

\nstring2\nstring3

... é correspondido com \nin \1e ''a cadeia nula em \2. Isso seria problemático se houvesse alguma chance de uma linha de \new ocorrer no início do espaço do padrão - mas os comandos /^\n/De //!gsão usados ​​para evitar isso. Usei, [^\n]mas outras necessidades desse pequeno script tornaram a portabilidade uma preocupação e não fiquei satisfeito com as muitas maneiras pelas quais ele é mal interpretado. Além disso, .é mais rápido.

\nstring2
string1

... correspondem \ne snovamente \1e ambos obtêm a ''cadeia nula \2. Linhas vazias não coincidem.

Quando o padrão é aplicado gglobalmente, os dois desvios - o viés padrão mais à esquerda e o lado direito menor\n ew do direito - são contrabalançados para efetuar um salto. Alguns exemplos:

s/\(.\)\(\n.*\)*/\1:\2/g
s/\(.\)\(\n.*\)*/\2\1:/g
s/\(.\)\(\n.*\)*/\1: /g
s/\(.\)\(\n.*\)*/ :\2/g

... se todos aplicados (não em sucessão) à seguinte sequência ...

string1\nstring2

... irá transformá-lo em ...

s:t:r:i:n:g:1:\nstring2
s:t:r:i:n:g:\nstring21:
s:t:r:i:n:g:1: 
 : : : : : : :\nstring2

Basicamente, eu uso o regexp para sempre manipular apenas a primeira linha em qualquer espaço de padrão ao qual eu o aplico. Isso me permite manipular duas versões diferentes de uma linha de correspondência mais curta retida até o momento e a linha mais recente sem recorrer a loops de teste - cada substituição aplicada lida com todo o espaço do padrão de uma só vez.

As versões diferentes são necessárias para comparações literais de string / string - portanto, deve haver uma versão de cada linha em que todos os caracteres sejam garantidos como iguais. Mas é claro que, se um ou outro deveria realmente ser a linha mais curta de entrada mais precoce, a linha impressa na saída provavelmente deveria ser a versão original da linha - e não a que eu higienizei / homogeneizei para fins de comparação. E então eu preciso de duas versões de cada.

É lamentável que outra necessidade seja muita troca de buffer para lidar com o mesmo - mas pelo menos nenhum buffer excede mais do que as quatro linhas necessárias para se manter atualizado - e, portanto, talvez não seja terrível.

De qualquer forma, para cada ciclo, a primeira coisa que acontece é uma transformação na linha lembrada - porque a única cópia realmente salva é o original literal - em ...

^               \nremembered line$

... e depois a nlinha de entrada ext substitui qualquer buffer antigo. Se não contiver pelo menos um único caractere, será efetivamente ignorado. Seria muito mais fácil apenasq a primeira linha em branco, mas, bem, meus dados de teste tinham muitos deles e eu queria lidar com vários parágrafos.

E, se ele contém um caractere, sua versão literal é anexada à linha lembrada e sua versão de comparação espaçada é posicionada na cabeça do espaço do padrão, assim:

^   \n               \nremembered line\nnew$

Por último, uma substituição é aplicada a esse espaço de padrão:

s/^\( *\)\(\n \1 *\)\{0,1\}\n//

Portanto, se a nova linha puder caber no espaço necessário para conter a linha lembrada com pelo menos um caractere de reposição, as duas primeiras linhas serão substituídas, senão somente a primeira.

Independentemente do resultado, a primeira linha no espaço do padrão é sempre Dexcluída no final do ciclo antes de iniciar novamente. Isso significa que, se a nova linha for mais curta que a última, a string ...

new

... é enviado de volta à primeira substituição do ciclo, que sempre tira apenas o primeiro caractere de nova linha - e, portanto, permanece inteiro. Mas se não for, então a string ...

remembered line\nnew

... começará o próximo ciclo e a primeira substituição retirará a string ...

\nnew

...toda vez.

Na última linha, a linha lembrada é impressa para padronizar e, portanto, para os dados de exemplo fornecidos, ela imprime:

4for

Mas, sério, use tr.



Você precisa inserir números de linha? Minha leitura do OP é que apenas a linha mais curta é necessária, e não necessariamente o número da linha dessa linha. Eu acho que não faz mal mostrar isso por completo.
Digital Trauma

@DigitalTrauma - nah, provavelmente não. Mas dificilmente é muito útil sem eles - e eles são tão baratos. Ao trabalhar em um fluxo, eu sempre prefiro incluir um meio de reproduzir a entrada original de forma idêntica na saída - os números de linha tornam isso possível aqui. Por exemplo, para converter os resultados da primeira volta gasoduto: REINPUT | sort -t: -nk1,1 | cut -d: -f3-. E o segundo é uma questão simples de incluir outro sed --expressionscript no final.
mikeserv

@DigitalTrauma - oh, e, no primeiro exemplo, os números de linha que afectam sorto comportamento como um laço-disjuntor quando as linhas do mesmo comprimento ocorrer na entrada - por isso a primeira linha que ocorre sempre flutua no topo, nesse caso.
mikeserv

7

Experimentar:

awk '{ print length, $0 }' testfile | sort -n | cut -d" " -f2- | head -1

A idéia é usar awkpara imprimir primeiro o comprimento de cada linha. Isso aparecerá como:

echo "This is a line of text" | awk '{print length, $0}'
22 This is a line of text

Em seguida, use a contagem de caracteres para classificar as linhas sort, cutpara se livrar da contagem e headmanter a primeira linha (aquela com menos caracteres). Obviamente, você pode usar tailpara obter a linha com mais caracteres neste caso.

(Isso foi adotado a partir desta resposta )


+1 para a lógica, mas não funcionará em todos os casos. Se as duas linhas tiverem o mesmo número de caracteres e o mínimo. Ele fornecerá apenas a primeira linha encontrada por causa de #head -1
Thushi 3/15

Para obter a linha mais longa, é um pouco mais eficiente reverter a classificação do que usar tail(como headpode sair assim que o trabalho for concluído, sem ler o restante da entrada).
perfil completo de Toby Speight

@Thushi Usando um pouco de regex, após a impressão dos números de linha, tudo, exceto as linhas com o mesmo número da linha 1, pode ser removido, produzindo assim todas as linhas mais curtas.
Matthew D. Scholefield

5

Com o POSIX awk:

awk 'FNR==1{l=$0;next};length<length(l){l=$0};END{print l}' file

Não funcionará se mais de uma linha tiver o mesmo número de caracteres e também for mínima.
Thushi

@Thushi: Ele reportará a primeira linha mínima.
precisa saber é

Sim. Mas isso não está correto, certo? Até as outras linhas têm o número mínimo de caracteres.
Thushi

1
@ Thushi: Isso não é mencionado no requisito do OP, aguardando atualização do OP.
precisa saber é

3
Eu não acho que Lfoi a melhor carta para escolheu o nome da variável: D Algo como minfaria as coisas mais claras
fedorqui

3

Tomando emprestado algumas das idéias de @ mikeserv:

< testfile sed 'h;s/./:/g;s/.*/expr length "&"/e;G;s/\n/\t/' | \
sort -n | \
sed -n '1s/^[0-9]+*\t//p'

O primeiro sed faz o seguinte:

  • h salva a linha original no buffer de espera
  • Substitua todos os caracteres da linha por : - para remover qualquer perigo de injeção de código
  • Substitua a linha inteira por expr length "whole line" - esta é uma expressão de shell que pode ser avaliada
  • O comando e paras é uma extensão GNU sed para avaliar o espaço do padrão e colocar o resultado de volta no espaço do padrão.
  • G anexa uma nova linha e o conteúdo do espaço em espera (a linha original) ao espaço do padrão
  • o final ssubstitui a nova linha por uma guia

O número de caracteres agora é um número no início de cada linha, portanto, sort -nclassifica pelo comprimento da linha.

A final sedremove todas as linhas, exceto a primeira (menor) e o comprimento da linha, e imprime o resultado.


1
@ MikeServ Sim, eu acho que expré melhor aqui. Sim, egerará uma concha para cada linha. Eu editei a expressão sed para que ela substitua cada caractere na string por um :antes da avaliação, que eu acho que deveria remover qualquer possibilidade de injeção de código.
Digital Trauma

Normalmente, eu optaria xargs exprpessoalmente - mas, além de evitar um shell intermediário, isso provavelmente é mais uma coisa estilística. Eu gosto mesmo.
mikeserv

3

Ocorreu-me que tudo é possível em uma sedexpressão. Não é bonito:

$ sed '1h;s/.*/&\n&/;G;:l;s/\n[^\n]\([^\n]*\)\n[^\n]/\n\1\n/;tl;/\n\n/{s/\n.*//;x};${x;p};d' testfile
4for
$ 

Quebrando isso:

1h            # save line 1 in the hold buffer (shortest line so far)
s/.*/&\n&/    # duplicate the line with a newline in between
G             # append newline+hold buffer to current line
:l            # loop start
s/\n[^\n]\([^\n]*\)\n[^\n]/\n\1\n/
              # attempt to remove 1 char both from current line and shortest line
tl            # jump back to l if the above substitution succeeded
/\n\n/{       # matches if current line is shorter
  s/\n.*//    # remove all but original line
  x           # save new shortest line in hold buffer
}
${            # at last line
  x           # get shortest line from hold buffer
  p           # print it
}
d             # don't print any other lines

O BSD sed no OS X é um pouco mais exigente com as novas linhas. Esta versão funciona para as versões BSD e GNU do sed:

$ sed -e '1h;G;s/\([^\n]*\)\(\n\)\(.*\)/\1\2\1\2\3/;:l' -e 's/\(\n\)[^\n]\([^\n]*\n\)[^\n]/\1\2/;tl' -e '/\n\n/{s/\n.*//;x;};${x;p;};d' testfile
4for
$

Observe que esta é mais uma resposta "porque é possível" do que uma tentativa séria de fornecer uma resposta para as melhores práticas. Eu acho que isso significa que eu tenho jogado muito code-colf


@mikeserv man sedNo OS X: "A sequência de escape \ n corresponde a um caractere de nova linha incorporado no espaço do padrão" . Então, acho que o GNU sed permite \nno regex e na substituição, enquanto o BSD apenas permite \nno regex e não na substituição.
Digital Trauma

Pedir emprestado o \nespaço do padrão é uma boa ideia e funcionaria na segunda s///expressão, mas a s/.*/&\n&/expressão está inserindo um \nno espaço do padrão onde não havia um antes. Além disso, o BSD sed parece exigir novas linhas literais após as definições e ramificações dos rótulos.
Digital Trauma

1
Essas novas linhas são delimitadores de parâmetros - você precisa deles para delimitar qualquer comando que possa aceitar um parâmetro arbitrário - pelo menos, é o que dizem as especificações. A especificação também diz que um sedscript deve ser um arquivo de texto, exceto que não precisa terminar em uma nova linha . Portanto, você também pode delimitá-los como argumentos separados - sed -e :\ label -e :\ label2e assim por diante. Como você está fazendo de 1hqualquer maneira, você pode mudar para alguma lógica baseada em x;Hpara obter sua nova linha - e você pode cortar uma nova linha principal do espaço do padrão no final do ciclo sem puxar uma nova linha com D.
mikeserv

@mikeserv Nice. Sim, inseri a nova linha de que precisava fazendo a Gprimeira e alterando a s///expressão. Dividir usando -epermite que tudo ocorra em uma (longa) linha sem novas linhas literais.
Digital Trauma

A \nfuga é especificada para sedo LHS também, e acho que é a declaração da especificação literalmente, exceto que as expressões de colchete POSIX também são especificadas de tal forma que todos os caracteres perdem seu significado especial - (incluindo explicitamente \\) - dentro de um, exceto os colchetes, o traço como um separador de intervalo e ponto, igual a, sinal de intercalação, dois pontos para agrupamento, equivalência, negação e classes.
mikeserv

2

Outra solução perl: armazene as linhas em um hash de matrizes, a chave de hash sendo o comprimento da linha. Em seguida, imprima as linhas com a tecla mínima.

perl -MList::Util=min -ne '
    push @{$lines{ length() }}, $_;
} END {
    print @{$lines{ min keys %lines }};
' sample 
4for

Você pode usar push @{$lines{+length}};e print @{$lines{+min keys %lines}};para menos digitação :)
cuonglm

Se eu fosse jogar golfe, eu não teria usado o nome da variável "linhas" ou:perl -MList::Util=min -nE'push @{$l{+length}},$_}END{say@{$l{min keys%l}}' sample
Glenn Jackman

+1 para uma versão sem golfe (que funciona!), Embora apenas para a variante imprimir todas . - perlfica um pouco complicado para aqueles de nós que não estão à altura perldo par com a natureza enigmática da. Entre. o golfed sayimprime uma linha em branco falsa no final da saída.
precisa saber é o seguinte

2

Para obter apenas a primeira linha mais curta:

f=file; sed -n "/^$(sed 's/./1/g' $f | sort -ns | sed 's/././g;q')$/{p;q}" $f

Para obter todos os fiapos mais curtos, mude {p;q}parap


Outro método (um tanto incomum) é sortfazer a classificação real por comprimento . É relativamente lento, mesmo com linhas curtas, e se torna dramaticamente mais lento à medida que o comprimento da linha aumenta.
No entanto, acho que a idéia de classificar por sobreposição de chaves . Estou publicando para o caso de outras pessoas também acharem interessante / informativo.

Como funciona:
classifique por variantes de comprimento da mesma chave - key 1que abrange toda a linha.
Cada variante de chave sucessiva incrementa o comprimento da chave em um caractere, até o comprimento da linha mais longa do arquivo (determinada por wc -L)

Para obter apenas a primeira linha mais curta (classificada):

f=file; sort -t'\0' $(seq -f "-k1.%0.0f" $(<"$f" wc -L) -1 1) "$f" | head -n1

que é o mesmo que:

f=file.in; 
l=$(<"$f" wc -L)
k=$(seq -f "-k1.%0.0f" $l -1 1) 
sort -st'\0' $k "$f" | head -n1

2

Supondo que linhas em branco não sejam consideradas a linha mais curta e que possam existir linhas em branco, o AWK puro a seguir funcionará:

awk '
    {
        len   = length;
        a[$0] = len
    }
    !len { next }
    !min { min = len }
    len < min { min = len }
    END {
        for (i in a)
            if (min == a[i])
                print i
    }
' infile.txt

2

Que tal usar classificação?

awk '{ print length($0) "\t" $0 }' input.txt | sort -n | head -n 1 | cut -f2-

1

Com o GNU awk

gawk '
    {
         a[length]=$0
    };
    END
    {
        PROCINFO["sorted_in"]="@ind_num_asc";
        for (i in a)
        {
            print a[i]; 
            exit
        }
    }
    ' file
  • Leia cada linha em uma matriz indexada pelo comprimento da linha.

  • Defina PROCINFO["sorted_in"]como @ind_num_ascpara forçar a verificação da matriz a ser ordenada pelo índice da matriz, classificada numericamente

  • A configuração da PROCINFOmaneira acima força a linha com o menor comprimento a ser capturada primeiro na travessia da matriz. Portanto, imprima o primeiro elemento da matriz e saia

Isso tem a desvantagem de demorar um nlognpouco, enquanto algumas das outras abordagens estão ndentro do prazo.


1

Método de ferramentas shell de nível médio, sem sedou awk:

f=inputfile
head -n $(xargs -d '\n' -L 1 -I % sh -c 'exec echo "%" | wc -c' < $f | 
          cat -n | sort -n -k 2 | head -1 | cut -f 1)  $f | tail -1

Seria bom não precisar de uma $fvariável; Eu tenho uma noção que pode ser possível usando de teealguma forma ...
agc 25/04
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.