Estou tentando encontrar a maneira mais eficiente de iterar certos valores que são um número consistente de valores distantes um do outro em uma lista de palavras separadas por espaço (não quero usar uma matriz). Por exemplo,
list="1 ant bat 5 cat dingo 6 emu fish 9 gecko hare 15 i j"
Então, eu quero poder apenas iterar na lista e acessar apenas 1,5,6,9 e 15.
EDIT: Eu deveria ter deixado claro que os valores que estou tentando obter da lista não precisam ser diferentes em formato do resto da lista. O que os torna especiais é apenas sua posição na lista (neste caso, posição 1,4,7...). Então a lista poderia ser1 2 3 5 9 8 6 90 84 9 3 2 15 75 55
, mas eu ainda gostaria dos mesmos números. E também, quero poder fazer isso assumindo que não sei o tamanho da lista.
Os métodos que eu pensei até agora são:
Método 1
set $list
found=false
find=9
count=1
while [ $count -lt $# ]; do
if [ "${@:count:1}" -eq $find ]; then
found=true
break
fi
count=`expr $count + 3`
done
Método 2
set list
found=false
find=9
while [ $# ne 0 ]; do
if [ $1 -eq $find ]; then
found=true
break
fi
shift 3
done
Método 3 Tenho certeza de que a tubulação torna essa a pior opção, mas eu estava tentando encontrar um método que não usasse set, por curiosidade.
found=false
find=9
count=1
num=`echo $list | cut -d ' ' -f$count`
while [ -n "$num" ]; do
if [ $num -eq $find ]; then
found=true
break
fi
count=`expr $count + 3`
num=`echo $list | cut -d ' ' -f$count`
done
Então, o que seria mais eficiente, ou estou perdendo um método mais simples?
Primeira regra de otimização de software: não .
Até que você saiba que a velocidade do programa é um problema, não há necessidade de pensar em quão rápido ele é. Se sua lista tiver esse tamanho ou apenas cerca de 100 a 1.000 itens, você provavelmente nem perceberá quanto tempo leva. Há uma chance de você estar gastando mais tempo pensando na otimização do que na diferença.
Segunda regra: Medir .
Essa é a maneira certa de descobrir, e aquela que dá respostas para o seu sistema. Especialmente com conchas, existem muitas e nem todas são idênticas. Uma resposta para um shell pode não se aplicar ao seu.
Em programas maiores, a criação de perfil também ocorre aqui. A parte mais lenta pode não ser a que você pensa que é.
Terceiro, a primeira regra de otimização de script de shell: não use o shell .
Sim, realmente. Muitos shells não são feitos para serem rápidos (já que o lançamento de programas externos não precisa ser), e eles podem até analisar as linhas do código-fonte novamente a cada vez.
Use algo como awk ou Perl. Em um micro-benchmark trivial que fiz,
awk
foi dezenas de vezes mais rápido do que qualquer shell comum na execução de um loop simples (sem E/S).No entanto, se você usar o shell, use as funções internas do shell em vez de comandos externos. Aqui, você está usando o
expr
que não está embutido em nenhum shell que encontrei no meu sistema, mas que pode ser substituído pela expansão aritmética padrão. Por exemploi=$((i+1))
, em vez dei=$(expr $i + 1)
incrementari
. Seu uso decut
no último exemplo também pode ser substituído por expansões de parâmetros padrão.Veja também: Por que usar um loop de shell para processar texto é considerado uma prática ruim?
Os passos 1 e 2 devem se aplicar à sua pergunta.
Bem simples com
awk
. Isso fornecerá o valor de cada quarto campo para entrada de qualquer comprimento:Isso funciona aproveitando
awk
variáveis internas comoNF
(o número de campos no registro) e fazendo alguns loops simplesfor
para iterar ao longo dos campos para fornecer os que você deseja sem precisar saber com antecedência quantos serão.Ou, se você realmente deseja apenas esses campos específicos, conforme especificado em seu exemplo:
Quanto à questão sobre eficiência, o caminho mais simples seria testar este ou cada um de seus outros métodos e usar
time
para mostrar quanto tempo leva; você também pode usar ferramentas comostrace
ver como as chamadas do sistema fluem. Uso detime
se parece com:Você pode comparar essa saída entre vários métodos para ver qual é o mais eficiente em termos de tempo; outras ferramentas podem ser usadas para outras métricas de eficiência.
Vou apenas dar alguns conselhos gerais nesta resposta, e não referências. Os benchmarks são a única maneira de responder de forma confiável a perguntas sobre desempenho. Mas como você não diz quantos dados você está manipulando e com que frequência você executa essa operação, não há como fazer um benchmark útil. O que é mais eficiente para 10 itens e o que é mais eficiente para 1000.000 itens geralmente não é o mesmo.
Como regra geral, invocar comandos externos é mais caro do que fazer algo com construções de shell puro, desde que o código de shell puro não envolva um loop. Por outro lado, um loop de shell que itera em uma string grande ou em uma grande quantidade de string provavelmente será mais lento do que uma invocação de uma ferramenta de propósito especial. Por exemplo, sua invocação de loop
cut
pode ser notavelmente lenta na prática, mas se você encontrar uma maneira de fazer tudo com uma únicacut
invocação, provavelmente será mais rápido do que fazer a mesma coisa com a manipulação de strings no shell.Observe que o ponto de corte pode variar muito entre os sistemas. Pode depender do kernel, de como o escalonador do kernel está configurado, do sistema de arquivos que contém os executáveis externos, de quanta pressão de CPU vs memória existe no momento e muitos outros fatores.
Não ligue
expr
para fazer aritmética se estiver preocupado com o desempenho. Na verdade, não ligueexpr
para fazer cálculos aritméticos. Os shells têm aritmética integrada, que é mais clara e rápida do que invocarexpr
.Você parece estar usando bash, já que está usando construções bash que não existem em sh. Então, por que diabos você não usaria uma matriz? Uma matriz é a solução mais natural e provavelmente a mais rápida também. Observe que os índices de matriz começam em 0.
Seu script pode ser mais rápido se você usar sh, se seu sistema tiver dash ou ksh as em
sh
vez de bash. Se você usar sh, não obterá arrays nomeados, mas ainda obterá um array de parâmetros posicionais, que pode ser definido comset
. Para acessar um elemento em uma posição que não é conhecida até o tempo de execução, você precisa usareval
(cuide de citar as coisas corretamente!).Se você quiser acessar o array apenas uma vez e estiver indo da esquerda para a direita (pulando alguns valores), você pode usar
shift
em vez de índices de variáveis.Qual abordagem é mais rápida depende do shell e do número de elementos.
Outra possibilidade é usar o processamento de strings. Tem a vantagem de não usar os parâmetros posicionais, então você pode usá-los para outra coisa. Será mais lento para grandes quantidades de dados, mas é improvável que faça uma diferença notável para pequenas quantidades de dados.
awk
é uma ótima escolha, se você puder fazer todo o seu processamento dentro do script Awk. Caso contrário, você acaba canalizando a saída do Awk para outros utilitários, destruindo o ganho de desempenho doawk
.bash
a iteração sobre uma matriz também é ótima, se você puder encaixar sua lista inteira dentro da matriz (o que para shells modernos provavelmente é uma garantia) e não se importar com a ginástica de sintaxe de matriz.No entanto, uma abordagem de pipeline:
Onde:
xargs
agrupa a lista separada por espaços em branco em lotes de três, cada nova linha separadawhile read
consome essa lista e gera a primeira coluna de cada grupogrep
filtra a primeira coluna (correspondente a cada terceira posição na lista original)Melhora a compreensão, na minha opinião. As pessoas já sabem o que essas ferramentas fazem, então é fácil ler da esquerda para a direita e raciocinar sobre o que vai acontecer. Essa abordagem também documenta claramente o comprimento da passada (
-n3
) e o padrão de filtro (9
), por isso é fácil variar:Quando fazemos perguntas de "eficiência", não se esqueça de pensar em "eficiência total ao longo da vida". Esse cálculo inclui o esforço dos mantenedores para manter o código funcionando, e nós, sacos de carne, somos as máquinas menos eficientes em toda a operação.
Talvez isso?
Não use comandos shell se quiser ser eficiente. Limite-se a pipes, redirecionamentos, substituições, etc., e programas. É por isso que
xargs
existemparallel
utilitários - porque bash while loops são ineficientes e muito lentos. Use loops bash apenas como a última resolução.Mas você provavelmente deve ficar um pouco mais rápido com bom
awk
.Na minha opinião, a solução mais clara (e provavelmente a de melhor desempenho também) é usar as variáveis RS e ORS awk:
Usando o shell script GNU
sed
e POSIX :Ou com
bash
a substituição de parâmetro de :Não - GNU ( ou seja , POSIX )
sed
, ebash
:Ou mais portátil, usando ambos POSIX
sed
e script de shell:Saída de qualquer um destes: