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 -m
para 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, 4for
pois 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.
shell
text-processing
wc
Matthew D. Scholefield
fonte
fonte
Respostas:
Um jeito Perl. Observe que, se houver muitas linhas do mesmo comprimento mais curto, essa abordagem imprimirá apenas uma delas:
Explicação
perl -lne
:-n
significa "ler o arquivo de entrada linha por linha",-l
faz com que as novas linhas finais sejam removidas de cada linha de entrada e uma nova linha seja adicionada a cadaprint
chamada; e-e
é o script que será aplicado a cada linha.$m//=$_
: defina$m
a linha atual ($_
), a menos que$m
esteja definido. O//=
operador está disponível desde o Perl 5.10.0.$m=$_ if length()<length($m)
: se o comprimento do valor atual de$m
for 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$m
menor linha. Osif $.
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:
Explicação
@K=sort{length($a) <=> length($b)}<>
:<>
aqui está uma matriz cujos elementos são as linhas do arquivo. Elessort
os 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
fonte
-C
para 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).Com
sqlite3
:fonte
strace
indica). Se você precisar trabalhar com arquivos muito grandes (e seu sistema não estiver trocando), você pode forçá-lo apenas acrescentando um nome de arquivosqlite3 $(mktemp)
e todos os dados serão gravados no disco.Aqui está uma variante de uma
awk
solução para imprimir a primeira linha mínima encontrada:que pode ser simplesmente estendido por uma condição para imprimir todas as linhas mínimas:
fonte
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'))"
fonte
Eu sempre adoro soluções com scripts de shell puro (sem exec!).
Nota :
Há um problema com NUL bytes na entrada. Então,
printf "ab\0\0\ncd\n" | bash this_script
imprime emab
vez decd
.fonte
bash
me convenceria a canalizar um resultado intermediáriosort
.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.$IFS
não é discriminatório por dígitos - mesmo que não exista um$IFS
valor padrão , embora muitos shells aceitem uma configuração de ambiente predefinida$IFS
- e, portanto, esse não é um padrão particularmente confiável./bin/sh
está disponível. Isso já aconteceu comigo várias vezes com hosts SunOS4/usr
perdidos ou.so
danificados. 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.Aqui está uma
zsh
solução pura (ela imprime todas as linhas com o comprimento mínimo, defile
):Exemplo de entrada:
A saída é:
Eu acho que precisa de uma breve explicação :-)
Primeiro, configuramos o separador de campo interno como nova linha:
Até aí tudo bem, agora a parte mais difícil.
print
usa o-l
sinalizador para imprimir o resultado separado por novas linhas em vez de espaços.Agora, começamos por dentro:
O arquivo é lido linha por linha e tratado como matriz. Então:
A
o
bandeira 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:Pegamos o primeiro elemento da matriz
[1]
, que é o mais curto, no seu caso, é agora????
.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:
fonte
... e o vencedor é ... a linha 2, ao que parece.
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
:Isso imprime ...
Outro, apenas
sed
: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 é:
Que corresponde a diferentes cadeias de maneiras diferentes. Por exemplo:
... é correspondido com
s
in\1
e''
a cadeia nula em\2
.... é combinado com
1
in\1
e\nstring2\nstring3
in\2
... é correspondido com
\n
in\1
e''
a cadeia nula em\2
. Isso seria problemático se houvesse alguma chance de uma linha de\n
ew ocorrer no início do espaço do padrão - mas os comandos/^\n/D
e//!g
sã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.... correspondem
\n
es
novamente\1
e ambos obtêm a''
cadeia nula\2
. Linhas vazias não coincidem.Quando o padrão é aplicado
g
globalmente, 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:... se todos aplicados (não em sucessão) à seguinte sequência ...
... irá transformá-lo em ...
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 ...
... e depois a
n
linha 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:
Por último, uma substituição é aplicada a esse espaço de padrão:
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
D
excluída no final do ciclo antes de iniciar novamente. Isso significa que, se a nova linha for mais curta que a última, a string ...... é 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 ...
... começará o próximo ciclo e a primeira substituição retirará a string ...
...toda vez.
Na última linha, a linha lembrada é impressa para padronizar e, portanto, para os dados de exemplo fornecidos, ela imprime:
Mas, sério, use
tr
.fonte
REINPUT | sort -t: -nk1,1 | cut -d: -f3-
. E o segundo é uma questão simples de incluir outrosed
--expression
script no final.sort
o 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.Experimentar:
A idéia é usar
awk
para imprimir primeiro o comprimento de cada linha. Isso aparecerá como:Em seguida, use a contagem de caracteres para classificar as linhas
sort
,cut
para se livrar da contagem ehead
manter a primeira linha (aquela com menos caracteres). Obviamente, você pode usartail
para obter a linha com mais caracteres neste caso.(Isso foi adotado a partir desta resposta )
fonte
head -1
tail
(comohead
pode sair assim que o trabalho for concluído, sem ler o restante da entrada).Com o POSIX awk:
fonte
L
foi a melhor carta para escolheu o nome da variável: D Algo comomin
faria as coisas mais clarasTomando emprestado algumas das idéias de @ mikeserv:
O primeiro
sed
faz o seguinte:h
salva a linha original no buffer de espera:
- para remover qualquer perigo de injeção de códigoexpr length "whole line"
- esta é uma expressão de shell que pode ser avaliadas
é 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ãos
substitui a nova linha por uma guiaO número de caracteres agora é um número no início de cada linha, portanto,
sort -n
classifica pelo comprimento da linha.A final
sed
remove todas as linhas, exceto a primeira (menor) e o comprimento da linha, e imprime o resultado.fonte
expr
é melhor aqui. Sim,e
gerará 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.xargs expr
pessoalmente - mas, além de evitar um shell intermediário, isso provavelmente é mais uma coisa estilística. Eu gosto mesmo.Ocorreu-me que tudo é possível em uma
sed
expressão. Não é bonito:Quebrando isso:
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:
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
fonte
man sed
No 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\n
no regex e na substituição, enquanto o BSD apenas permite\n
no regex e não na substituição.\n
espaço do padrão é uma boa ideia e funcionaria na segundas///
expressão, mas as/.*/&\n&/
expressão está inserindo um\n
no 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.sed
script 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 :\ label2
e assim por diante. Como você está fazendo de1h
qualquer maneira, você pode mudar para alguma lógica baseada emx;H
para 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 comD
.G
primeira e alterando as///
expressão. Dividir usando-e
permite que tudo ocorra em uma (longa) linha sem novas linhas literais.\n
fuga é especificada parased
o 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.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.
fonte
push @{$lines{+length}};
eprint @{$lines{+min keys %lines}};
para menos digitação :)perl -MList::Util=min -nE'push @{$l{+length}},$_}END{say@{$l{min keys%l}}' sample
perl
fica um pouco complicado para aqueles de nós que não estão à alturaperl
do par com a natureza enigmática da. Entre. o golfedsay
imprime uma linha em branco falsa no final da saída.Para obter apenas a primeira linha mais curta:
Para obter todos os fiapos mais curtos, mude
{p;q}
parap
Outro método (um tanto incomum) é
sort
fazer 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 1
que 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):
que é o mesmo que:
fonte
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á:
fonte
Que tal usar classificação?
fonte
Com o GNU awk
Leia cada linha em uma matriz indexada pelo comprimento da linha.
Defina
PROCINFO["sorted_in"]
como@ind_num_asc
para forçar a verificação da matriz a ser ordenada pelo índice da matriz, classificada numericamenteA configuração da
PROCINFO
maneira 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 saiaIsso tem a desvantagem de demorar um
nlogn
pouco, enquanto algumas das outras abordagens estãon
dentro do prazo.fonte
Método de ferramentas shell de nível médio, sem
sed
ouawk
:fonte
$f
variável; Eu tenho uma noção que pode ser possível usando detee
alguma forma ...