Como iterar em um intervalo de números no Bash quando o intervalo é fornecido por uma variável?
Eu sei que posso fazer isso (chamado "expressão de sequência" na documentação do Bash ):
for i in {1..5}; do echo $i; done
Que dá:
1
2
3
4
5
No entanto, como posso substituir qualquer um dos pontos de extremidade do intervalo por uma variável? Isso não funciona:
END=5
for i in {1..$END}; do echo $i; done
Que imprime:
{1..5}
for i in {01..10}; do echo $i; done
daria números como01, 02, 03, ..., 10
.myarray=('a' 'b' 'c'); for i in ${!myarray[@]}; do echo $i; done
(observe o ponto de exclamação). É mais específico que a pergunta original, mas pode ajudar. Veja expansões de parâmetros do bash{jpg,png,gif}
que não são abordadas diretamente aqui, embora a resposta seja idêntica. Veja expansão Brace com variável? [duplicado], que está marcado como duplicado deste.Respostas:
edit: eu prefiro
seq
os outros métodos, porque na verdade consigo me lembrar;)fonte
seq $END
seria suficiente, pois o padrão é iniciar a partir de 1. Deman seq
: "Se PRIMEIRO ou INCREMENTO for omitido, o padrão será 1".O
seq
método é o mais simples, mas o Bash possui avaliação aritmética integrada.A
for ((expr1;expr2;expr3));
construção funciona comofor (expr1;expr2;expr3)
em C e linguagens semelhantes e, como em outros((expr))
casos, Bash os trata como aritméticos.fonte
seq
. Use-o!#!/bin/bash
a primeira linha do seu script. wiki.ubuntu.com/...discussão
Usar
seq
é bom, como Jiaaro sugeriu. Pax Diablo sugeriu um loop Bash para evitar chamar um subprocesso, com a vantagem adicional de ser mais amigável à memória se $ END for muito grande. Zathrus detectou um erro típico na implementação do loop e também sugeriu que, comoi
é uma variável de texto, as conversões contínuas para números são executadas com uma desaceleração associada.aritmético inteiro
Esta é uma versão aprimorada do loop Bash:
Se a única coisa que queremos é a
echo
, então poderíamos escreverecho $((i++))
.ephemient me ensinou algo: Bash permite
for ((expr;expr;expr))
construções. Como nunca li a página de manual inteira do Bash (como fiz com aksh
página de manual do Korn shell ( ), e isso foi há muito tempo), senti falta disso.Assim,
parece ser a maneira mais eficiente em termos de memória (não será necessário alocar memória para consumir
seq
a saída, o que pode ser um problema se END for muito grande), embora provavelmente não seja o "mais rápido".a pergunta inicial
eschercycle observou que a notação { a .. b } Bash funciona apenas com literais; true, de acordo com o manual do Bash. Pode-se superar esse obstáculo com um único (interno)
fork()
sem umexec()
(como é o caso da chamadaseq
, que sendo outra imagem requer um fork + exec):Ambos são
eval
eecho
Bash builtins, mas umfork()
é necessário para a substituição de comando (a$(…)
construção).fonte
for ((i=$1;i<=$2;++i)); do echo $i; done
em um script funciona bem para mim no bash v.4.1.9, então não vejo problema nos argumentos da linha de comando. Você quer dizer outra coisa?time for i in $(seq 100000); do :; done
é muito mais rápido!Aqui está o porquê da expressão original não funcionar.
De man bash :
Portanto, a expansão entre chaves é algo feito cedo como uma operação macro puramente textual, antes da expansão dos parâmetros.
Os shells são híbridos altamente otimizados entre processadores macro e linguagens de programação mais formais. Para otimizar os casos de uso típicos, a linguagem é bastante mais complexa e algumas limitações são aceitas.
Eu sugeriria manter os recursos do Posix 1 . Isso significa usar
for i in <list>; do
, se a lista já for conhecida, caso contrário, usewhile
ouseq
, como em:1. O Bash é um ótimo shell e eu o uso interativamente, mas não coloco bash-isms nos meus scripts. Os scripts podem precisar de um shell mais rápido, mais seguro, mais incorporado. Eles podem precisar executar o que estiver instalado como / bin / sh e, em seguida, existem todos os argumentos pró-padrões usuais. Lembre-se do shellshock, também conhecido como bashdoor?
fonte
seq
grandes faixas. Por exemplo,echo {1..1000000} | wc
revela que o eco produz 1 linha, um milhão de palavras e 6.888.896 bytes. A tentativaseq 1 1000000 | wc
produz um milhão de linhas, um milhão de palavras e 6.888.896 bytes e também é mais de sete vezes mais rápido, conforme medido pelotime
comando.while
método anteriormente na minha resposta: stackoverflow.com/a/31365662/895245 Mas contente você concorda :-)A maneira POSIX
Se você se importa com portabilidade, use o exemplo do padrão POSIX :
Resultado:
Coisas que não são POSIX:
(( ))
sem dólar, embora seja uma extensão comum, como mencionado pelo próprio POSIX .[[
.[
é suficiente aqui. Veja também: Qual é a diferença entre colchetes simples e duplos no Bash?for ((;;))
seq
(GNU Coreutils){start..end}
, e isso não pode funcionar com variáveis, conforme mencionado no manual do Bash .let i=i+1
: POSIX 7 2. A linguagem de comandos do shell não contém a palavralet
e falha nobash --posix
4.3.42i=$i+1
pode ser necessário o dólar em , mas não tenho certeza. O POSIX 7 2.6.4 Expansão aritmética diz:mas lê-lo literalmente, não implica que se
$((x+1))
expanda, poisx+1
não é uma variável.fonte
x
, não a expressão inteira.$((x + 1))
está bem.seq
(o BSDseq
permite que você defina uma sequência de terminação com-t
), o FreeBSD e o NetBSD também têmseq
desde 9.0 e 3.0, respectivamente.$((x+1))
e$((x + 1))
parse exatamente o mesmo, como quando o analisador tokenizesx+1
será dividido em 3 fichas:x
,+
, e1
.x
não é um token numérico válido, mas é um token de nome de variável válido, aindax+
não é, portanto, a divisão.+
é um token de operador aritmético válido, ainda+1
não é, portanto o token é novamente dividido lá. E assim por diante.Outra camada de indireção:
fonte
Você pode usar
fonte
Se você precisar dele como prefixo, poderá gostar deste
isso renderá
fonte
printf "%02d\n" $i
seria mais fácil do queprintf "%2.0d\n" $i |sed "s/ /0/"
?Se você estiver no BSD / OS X, poderá usar jot em vez de seq:
fonte
Isso funciona bem em
bash
:fonte
echo $((i++))
funciona e combina em uma linha.bash
, podemos simplesmente fazerwhile [[ i++ -le "$END" ]]; do
o (pós) incremento no teste #Combinei algumas das idéias aqui e medi o desempenho.
TL; DR Takeaways:
seq
e{..}
são muito rápidosfor
ewhile
loops são lentos$( )
é lentofor (( ; ; ))
loops são mais lentos$(( ))
é ainda mais lentoEstas não são conclusões . Você teria que olhar o código C por trás de cada um deles para tirar conclusões. Isso é mais sobre como tendemos a usar cada um desses mecanismos para fazer loop sobre o código. A maioria das operações individuais está perto o suficiente para ter a mesma velocidade que não importa na maioria dos casos. Mas um mecanismo como esse
for (( i=1; i<=1000000; i++ ))
é muitas operações, como você pode ver visualmente. Também há muito mais operações por loop do que você obtémfor i in $(seq 1 1000000)
. E isso pode não ser óbvio para você, e é por isso que fazer testes como esse é valioso.Demonstrações
fonte
$(seq)
é sobre a mesma velocidade que{a..b}
. Além disso, cada operação leva aproximadamente o mesmo tempo, portanto adiciona cerca de 4μs a cada iteração do loop para mim. Aqui, uma operação é um eco no corpo, uma comparação aritmética, um incremento etc. Isso é surpreendente? Quem se importa com quanto tempo a parafernália do loop leva para fazer seu trabalho - é provável que o tempo de execução seja dominado pelo conteúdo do loop.Eu sei que essa pergunta é sobre
bash
, mas - apenas para constar -ksh93
é mais inteligente e a implementa conforme o esperado:fonte
Esta é outra maneira:
fonte
Se você deseja permanecer o mais próximo possível da sintaxe da expressão entre chaves, tente a
range
função em bash-tricks 'range.bash
.Por exemplo, tudo o que se segue fará exatamente a mesma coisa que
echo {1..10}
:Ele tenta suportar a sintaxe nativa do bash com o mínimo possível de "truques": não apenas as variáveis são suportadas, mas o comportamento muitas vezes indesejável de intervalos inválidos sendo fornecidos como strings (por exemplo,
for i in {1..a}; do echo $i; done
).As outras respostas funcionarão na maioria dos casos, mas todas elas têm pelo menos uma das seguintes desvantagens:
seq
é um binário que deve ser instalado para ser usado, deve ser carregado pelo bash e deve conter o programa que você espera, para que ele funcione nesse caso. Onipresente ou não, é muito mais para confiar do que apenas a própria linguagem Bash.{a..z}
; cinta expansão vontade. A pergunta era sobre faixas de números , no entanto, então isso é uma queixa.{1..10}
sintaxe da faixa expandida, então os programas que usam os dois podem ser um pouco mais difíceis de ler.$END
variável não for um "bookend" de intervalo válido para o outro lado do intervalo. SeEND=a
, por exemplo, um erro não ocorrer e o valor literal{1..a}
for repetido. Esse também é o comportamento padrão do Bash - geralmente é inesperado.Isenção de responsabilidade: eu sou o autor do código vinculado.
fonte
Substitua
{}
por(( ))
:Rendimentos:
fonte
Tudo isso é bom, mas seq é supostamente obsoleto e a maioria funciona apenas com intervalos numéricos.
Se você colocar o loop for entre aspas duplas, as variáveis de início e fim serão desreferenciadas quando você repetir a sequência e poderá enviá-la de volta ao BASH para execução.
$i
precisa ser escapado com \ 's, para NÃO ser avaliado antes de ser enviado ao subshell.Esta saída também pode ser atribuída a uma variável:
A única "sobrecarga" que isso deve gerar deve ser a segunda instância do bash, portanto deve ser adequada para operações intensivas.
fonte
Se você está executando comandos de shell e você (como eu) tem um fetiche por pipelining, este é bom:
seq 1 $END | xargs -I {} echo {}
fonte
Existem muitas maneiras de fazer isso, mas as que eu prefiro são apresentadas abaixo
Usando
seq
Comando completo
seq first incr last
Exemplo:
Somente com o primeiro e o último:
Somente com o último:
Usando
{first..last..incr}
Aqui, o primeiro e o último são obrigatórios e o incr é opcional
Usando apenas o primeiro e o último
Usando incr
Você pode usar isso mesmo para caracteres como abaixo
fonte
se você não quiser usar o formato de expansão '
seq
' ou 'eval
'jot
ou aritmético, por exemplo.for ((i=1;i<=END;i++))
, ou outros loops, por exemplo.while
, e você não quer 'printf
' e gosta de 'echo
' apenas, essa solução alternativa simples pode caber no seu orçamento:a=1; b=5; d='for i in {'$a'..'$b'}; do echo -n "$i"; done;' echo "$d" | bash
PS: Meu bash não tem
seq
comando ' ' de qualquer maneira.Testado no Mac OSX 10.6.8, Bash 3.2.48
fonte
Isso funciona em Bash e Korn, também pode ir de números maiores para menores. Provavelmente não é o mais rápido ou o mais bonito, mas funciona bem o suficiente. Lida com negativos também.
fonte