loop bash com incremento de 0,02

11

Eu quero fazer um loop for no bash com 0,02 como incrementos Eu tentei

for ((i=4.00;i<5.42;i+=0.02))
do
commands
done

mas não funcionou.

    
por Mehrshad 01.09.2015 / 16:36

4 respostas

18

A leitura da página de manual bash fornece as seguintes informações:

for (( expr1 ; expr2 ; expr3 )) ; do list ; done

First, the arithmetic expression expr1 is evaluated according to the rules described below under ARITHMETIC EVALUATION. [...]

e então nós começamos esta seção

ARITHMETIC EVALUATION

The shell allows arithmetic expressions to be evaluated, under certain circumstances (see the let and declare builtin commands and Arithmetic Expansion). Evaluation is done in fixed-width integers with no check for overflow [...]

Portanto, pode-se ver claramente que você não pode usar um loop for com valores não inteiros.

Uma solução pode ser simplesmente multiplicar todos os seus componentes de loop por 100, permitindo que, posteriormente, você os use, assim:

for ((k=400;k<542;k+=2))
do
    i=$(bc <<<"scale=2; $k / 100" )    # when k=402 you get i=4.02, etc.
    ...
done
    
por 01.09.2015 / 16:42
17

Evite loops em shells.

Se você quiser fazer aritmética, use awk ou bc :

awk '
  BEGIN{
    for (i = 4.00; i < 5.42; i+ = 0.02)
      print i
  }'

Ou

bc << EOF
for (i = 4.00; i < 5.42; i += 0.02)  i
EOF

Observe que awk (ao contrário de bc ) funciona com seus processadores double representação do número de ponto flutuante (provável IEEE 754 tipo). Como resultado, como esses números são aproximações binárias desses números decimais, você pode ter algumas surpresas:

$ gawk 'BEGIN{for (i=0; i<=0.3; i+=0.1) print i}'
0
0.1
0.2

Se você adicionar um OFMT="%.17g" , poderá ver o motivo da falta de 0.3 :

$ gawk 'BEGIN{OFMT="%.17g"; for (i=0; i<=0.5; i+=0.1) print i}'
0
0.10000000000000001
0.20000000000000001
0.30000000000000004
0.40000000000000002
0.5

bc faz precisão arbitrária, então não tem esse tipo de problema.

Observe que, por padrão (a menos que você modifique o formato de saída com OFMT ou use printf com especificações de formato explícitas), awk usa %.6g para exibir números de ponto flutuante, então alterne para 1e6 e superior para números de ponto flutuante acima de 1.000.000 e truncar a parte fracionária para números altos (100.000.02 seria exibido como 100.000).

Se você realmente precisa usar um loop de shell, porque, por exemplo, você quer executar comandos específicos para cada iteração desse loop, ou usar um shell com suporte aritmético de ponto flutuante como zsh , yash ou ksh93 ou gere a lista de valores com um comando como acima (ou seq se disponível) e faça um loop sobre sua saída.

Como:

unset -v IFS # configure split+glob for default word splitting
for i in $(seq 4 0.02 5.42); do
  something with "$i"
done

Ou:

seq 4 0.02 5.42 | while IFS= read i; do
  something with "$i"
done

a menos que você exceda os limites dos números de ponto flutuante do seu processador, seq manipula os erros incorridos pelas aproximações de ponto flutuante com mais graça do que a awk versão acima faria.

Se você não tem seq (um comando GNU), você pode fazer um mais confiável como uma função como:

seq() { # args: first increment last
  bc << EOF
    for (i = $1; i <= $3; i += $2) i
EOF
}

Isso funcionaria melhor para coisas como seq 100000000001 0.000000001 100000000001.000000005 . Note, entretanto, que ter números com precisão arbitrariamente alta não ajudará muito se formos passá-los para comandos que não os suportam.

    
por 01.09.2015 / 16:52
2

Use "seq" - imprima uma sequência de números

seq PRIMEIRO INCREMENTO DEPOIS

for i in $(seq 4.00 0.02 5.42)
do 
  echo $i 
done
    
por 02.09.2015 / 09:47
0

Como outros sugeriram, você pode usar bc:

i="4.00"

while [[ "$(bc <<< "$i < 5.42")" == "1" ]]; do
    # do something with i
    i="$(bc <<< "$i + 0.02")"
done
    
por 01.09.2015 / 16:55