bash - variaveis - shell script variavel recebe comando




Como faço para iterar em um intervalo de números definidos por variáveis no Bash? (12)

Como faço para iterar em um intervalo de números no Bash quando o intervalo é dado por uma variável?

Eu sei que posso fazer isso (chamado de "expressão de seqüência" na documentation do Bash):

 for i in {1..5}; do echo $i; done

Que dá:

1
2
3
4
5

Ainda assim, como posso substituir um dos endpoints 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}


discussão

Usar o 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, como i é uma variável de texto, conversões contínuas para números de e para números são executadas com um slow-down associado.

aritmética inteira

Esta é uma versão melhorada do loop de Bash:

typeset -i i END
let END=5 i=1
while ((i<=END)); do
    echo $i
    
    let i++
done

Se a única coisa que queremos é o echo , então podemos escrever echo $((i++)) .

ephemient me ensinou algo: Bash permite construir for ((expr;expr;expr)) . Desde que eu nunca li a man page inteira para o Bash (como eu fiz com a página de manual do Korn shell ( ksh ), e isso foi há muito tempo), eu senti falta disso.

Assim,

typeset -i i END # Let's be explicit
for ((i=1;i<=END;++i)); do echo $i; done

parece ser a maneira mais eficiente de memória (não será necessário alocar memória para consumir a saída do seq , o que poderia ser um problema se END for muito grande), embora provavelmente não seja o “mais rápido”.

a pergunta inicial

eschercycle notou que a notação de {a..b} Bash funciona apenas com literais; verdade, de acordo com o manual do Bash. Pode-se superar este obstáculo com um único fork() interno fork() sem um exec() (como é o caso da chamada seq , que sendo outra imagem requer um fork + exec):

for i in $(eval echo "{1..$END}"); do

Ambos eval e echo são Bash builtins, mas um fork() é necessário para a substituição do comando (a construção $(…) ).


Eis por que a expressão original não funcionou.

Do homem bash :

A expansão de chaves é executada antes de qualquer outra expansão, e quaisquer caracteres especiais para outras expansões são preservados no resultado. É estritamente textual. Bash não aplica qualquer interpretação sintática ao contexto da expansão ou ao texto entre as chaves.

Então, a expansão de brace é algo feito cedo como uma operação de macro puramente textual, antes da expansão do parâmetro.

Shells são híbridos altamente otimizados entre macros processadores 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.

Recomendação

Eu sugeriria ficar com os recursos do Posix 1 . Isso significa usar for i in <list>; do se a lista já é conhecida, caso contrário, use while ou seq , como em:

#!/bin/sh

limit=4

i=1; while [ $i -le $limit ]; do
  echo $i
  i=$(($i + 1))
done
# Or -----------------------
for i in $(seq 1 $limit); do
  echo $i
done

1. Bash é uma ótima shell e eu uso interativamente, mas eu não coloco bash-isms em meus scripts. Os scripts podem precisar de um shell mais rápido, mais seguro e mais incorporado. Eles podem precisar ser executados no que estiver instalado como / bin / sh e, em seguida, há todos os argumentos usuais de pro-padrões. Lembre-se de shellshock, também conhecido como bashdoor?


Eu sei que esta questão é sobre bash , mas - apenas para o registro - ksh93 é mais inteligente e implementa como esperado:

$ ksh -c 'i=5; for x in {1..$i}; do echo "$x"; done'
1
2
3
4
5
$ ksh -c 'echo $KSH_VERSION'
Version JM 93u+ 2012-02-29

$ bash -c 'i=5; for x in {1..$i}; do echo "$x"; done'
{1..5}

Isso funciona bem no bash :

END=5
i=1 ; while [[ $i -le $END ]] ; do
    echo $i
    ((i = i + 1))
done

O método seq é o mais simples, mas Bash possui avaliação aritmética embutida.

END=5
for ((i=1;i<=END;i++)); do
    echo $i
done
# ==> outputs 1 2 3 4 5 on separate lines

O for ((expr1;expr2;expr3)); Constructo funciona como for (expr1;expr2;expr3) em C e linguagens semelhantes, e como outros ((expr)) casos, Bash os trata como aritmética.


Outra camada de indirecção:

for i in $(eval echo {1..$END}); do
    

Se você está no BSD / OS X, você pode usar jot em vez de seq:

for i in $(jot $END); do echo $i; done

Se você precisar de prefixo do que você pode gostar disso

 for ((i=7;i<=12;i++)); do echo `printf "%2.0d\n" $i |sed "s/ /0/"`;done

que renderá

07
08
09
10
11
12

Substitua {} por (( )) :

tmpstart=0;
tmpend=4;

for (( i=$tmpstart; i<=$tmpend; i++ )) ; do 
echo $i ;
done

Rendimentos:

0
1
2
3
4

Tudo isso é bom, mas o seq é supostamente obsoleto e a maioria só funciona com intervalos numéricos.

Se você colocar o loop for entre aspas duplas, as variáveis ​​inicial e final serão desreferenciadas quando você fizer o eco da string, e você poderá enviar a string de volta para BASH para execução. $i precisa ser escapado com \ 's, por isso NÃO é avaliado antes de ser enviado para a sub-rede.

RANGE_START=a
RANGE_END=z
echo -e "for i in {$RANGE_START..$RANGE_END}; do echo \\${i}; done" | bash

Esta saída também pode ser atribuída a uma variável:

VAR=`echo -e "for i in {$RANGE_START..$RANGE_END}; do echo \\${i}; done" | bash`

A única "sobrecarga" que isso deve gerar deve ser a segunda instância do bash, portanto, deve ser adequada para operações intensivas.


O caminho POSIX

Se você se preocupa com a portabilidade, use o exemplo do padrão POSIX :

i=2
end=5
while [ $i -le $end ]; do
    echo $i
    i=$(($i+1))
done

Saída:

2
3
4
5

Coisas que não são POSIX:

  • (( )) sem dólar, embora seja uma extensão comum como mencionado pelo próprio POSIX .
  • [[ . [ é o 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. Linguagem de comando do shell não contém a palavra let e falha no bash --posix 4.3.42
  • o dólar em i=$i+1 pode ser necessário, mas não tenho certeza. POSIX 7 2.6.4 Expansão Aritmética diz:

    Se a variável de shell x contiver um valor que forma uma constante inteira válida, opcionalmente incluindo um sinal de mais ou menos, então as expansões aritméticas "$ ((x))" e "$ (($ x))" devem retornar o mesmo valor.

    mas ler literalmente isso não significa que $((x+1)) expande, pois x+1 não é uma variável.


for i in $(seq 1 $END); do echo $i; done

edit: Eu prefiro seq sobre os outros métodos, porque eu posso realmente lembrar dele;)





syntax