Bash se condições de variáveis ​​dentro de intervalos

Bash se condições de variáveis ​​dentro de intervalos

Tenho alguns dados espalhados por intervalos de tempo e quero pegar alguns desses dados dentro de intervalos de tempo. Por exemplo, às vezes tenho dados entre 1..9, 11..19, etc., e quero obter dados entre 1-2, depois 11-12, etc.

Isso fará parte de um bashscript mais complexo, no qual quero incluir essa condição, com um ifciclo, para isolar os horários onde posso capturar os dados.

Eu estava pensando algo como:

if (( $t_initial & $t_final )) in (begin1, fin1) or in (begin2, fin2) ...

onde t_initiale t_finalsão calculados separadamente, pelo próprio script.

Não consigo escrever esta condição na bashsintaxe. Encontrei algumas outras soluções, mas parecem extremamente longas e inconvenientes, por isso estou aqui para pedir soluções mais simples e legíveis.


É importante que o código funcione corretamente com carros alegóricos. Estou tentando consertar a solução OP para isso, mas ainda não consigo encontrar uma maneira.

Responder1

Não tenho certeza se você gostaria desta solução. Possui 2 recursos:

  • Nenhum programa externo é necessário
  • Ele usa uma função, então pelo menos esconde a complexidade das comparações

É isso:

#!/bin/bash

# The comparing function
function compareInterval {
 t1=$1
 t2=$2

 shift 2

 while (( "$2" )); do
   if ((  $t1 >= $1  &&  $t2 <= $2 )); then
     # got match
     return 0
   fi
   shift 2
 done

 return 1
}

# sample values
t_initial=2
t_final=4

# Invocation. Compares against 1-3, 3-5, 2-5
if compareInterval  $t_initial $t_final  1 3  3 5  2 5; then
 echo Got match
fi

Responder2

bashnão suporta nativamente uma comparação de intervalo, nem números de ponto flutuante, então temos que fazer isso nós mesmos. Também vou definir uma função e usá-la bcpara cálculo de ponto flutuante. Aqui está o resultado final e o conjunto de testes:

# Call as `compareRanges start end b1 f1 b2 f2 b3 f3...`
compareRanges() {
    local t_initial=$1
    local t_final=$2
    shift 2
    while [ ${#@} -gt 1 ]
    do
        local in_range=$(bc <<<"$t_initial >= $1 && $t_final <= $2")
        if [ $in_range = 1 ]
        then
            # Debugging output to stderr - can be removed:
            echo "[$t_initial,$t_final] is within [$1,$2]" >&2
            return 0
        fi
        shift 2
    done
    # Debugging output to stderr - can be removed:
    echo "[$t_initial,$t_final] is not within any ranges." >&2
    return 1
}
# Basic integers from the example
compareRanges 1 3 2 4 && echo BAD || echo OK
compareRanges 1 3 1 3 && echo OK || echo BAD
compareRanges 1 3 0 4 && echo OK || echo BAD
# Fractional numbers
compareRanges 1.5 2.5 1.1 2.2 && echo BAD || echo OK
compareRanges 1.5 2.5 0.3 3.1 && echo OK || echo BAD
# Multiple ranges
compareRanges 5 7 1 4 2 6 3 9 && echo OK || echo BAD
compareRanges 5 7 1 2 3 4 5 6 7 8 && echo BAD || echo OK

A compareRangesfunção leva pelo menos dois argumentos. O primeiro é seu t_initiale o segundo é seu t_final. Depois disso, podem ser necessários arbitrariamente muitos outros argumentos em pares, que são seus begin1, fin1, begin2, fin2, em ordem.

O primeiro caso de teste compara os intervalos nos comentários da questão: 1-3 e 2-4.

compareRanges 1 3 2 4 && echo BAD || echo OK

Então 1é t_initial, 3é t_final, 2é begin1e 4é fin1.

Quando você quiser usar vários intervalos, liste-os em pares posteriormente:

compareRanges 5 7 1 4 2 6 3 9 && echo OK || echo BAD

Aqui testamos 1-4, 2-6 e 3-9. No whileloop, olhamos cada par por vez e comparamos com t_initiale t_final.

Porque bashnão suporta números fracionários que usamosbc, uma calculadora de precisão arbitrária. Sua entrada é dada pela <<<"$t_initial >= $1" ...parte: que alimenta a string na entrada padrão. $1é o início do intervalo que estamos vendo nesta iteração do loop e $2é o fim; comparamos os limites inferior e superior ao mesmo tempo com &&. bcserá gerado 1quando as comparações forem verdadeiras e 0quando uma for falsa. Salvamos o resultado em in_rangee a função é bem-sucedida ( return 0) quando ambos os nossos testes são verdadeiros.

Os números fracionários podem ser especificados apenas com sua forma decimal ordinária:

compareRanges 1.5 2.5 0.3 3.1 && echo OK || echo BAD

bcirá lidar com números com quantos dígitos fracionários você quiser e com qualquer magnitude que você precisar.

No final, se nenhum dos pares de limites corresponder, falhamos ( return 1). Você pode usar a função como:

if compareRanges $t_initial $t_final 2 4 11 19
then
    ...
fi

O conjunto de testes deve imprimir tudo "OK" quando você executá-lo.


Alternativamente, outros shells (comozsh)fazersuporta valores de variáveis ​​​​fracionárias. Se você pudesse executar seu script em um desses, você poderia evitar o uso de bc, embora a comparação ainda seja melhor em uma função. Pelo menos no zshcaso de eles são flutuantes, portanto não são necessariamente precisos; bcsempre estará correto.

Responder3

Aqui estão algumas cópias descaradas da resposta do LatinSuD que lidam com ponto flutuante. Você notará que a resposta dele afirma “Nenhum programa externo é necessário”. Este usa o programa calculadora, bccomo ele sugeriu:

#!/bin/bash

# The comparing function
function compareInterval {
 t1=$1
 t2=$2

 shift 2

 while (( "$2" ))
 do
   # if ((  $t1 >= $1  &&  $t2 <= $2 ))
   bc_result=$(echo "print $t1 >= $1  &&  $t2 <= $2" | bc)
   if [  "$bc_result" = 1 ]
   then
     # got match
     return 0
   fi
   shift 2
 done

 return 1
}

# sample values
t_initial=2.3
t_final=4.2

# Invocation. Compares against 1-3, 3-5, 2-5
if compareInterval  $t_initial $t_final  1 3  3 5  2 5
then
 echo Got match
fi

Isso simplesmente pega o if (( $t1 >= $1 && $t2 <= $2 ))teste e o envia para bc, e então captura a saída de bc.


Outra abordagem é normalizar os números para inteiros multiplicando por uma potência de dez. Isso requer que você tenha um número máximo de dígitos decimais. Por exemplo, se nenhum ponto de dados tiver mais de três dígitos à direita da vírgula decimal, podemos multiplicar tudo por 1000.

#!/bin/bash

# Normalize function: it multiplies a floating point number by 1000
# without using floating point arithmetic.
normalize()
{
  case "$1" in
    *.*)
        result=$(echo "$1"000 | sed 's/\(.*\)\.\(...\).*/\1\2/')
        ;;
    *)
        result="$1"000
  esac
  echo "$result"
}

# The comparing function
function compareInterval {
 t1=$(normalize $1)
 t2=$(normalize $2)

 shift 2

 while (( "$2" ))
 do
   a1=$(normalize $1)
   a2=$(normalize $2)
   if ((  $t1 >= $a1  &&  $t2 <= $a2 ))
   then
     # got match
     return 0
   fi
   shift 2
 done

 return 1
}

# sample values
t_initial=2.3
t_final=4.2

# Invocation. Compares against 1-3, 3-5, 2-5
if compareInterval  $t_initial $t_final  1 3  3 5  2 5
then
 echo Got match
fi

Se o parâmetro da normalizefunção for um número inteiro simples (ou seja, um número sem ponto decimal, por exemplo, 17), podemos multiplicar por 1000 simplesmente acrescentando 000, então 1717000. Se o parâmetro to normalizefor um número de ponto flutuante (ou seja, contém um ponto decimal, por exemplo, 42.5), ainda acrescentamos o 000e usamos sedpara remover o ponto decimal e tudo após o terceiro dígito. O sedcomando s/\(.*\)\.\(...\).*/\1\2/usa uma string comoabcdef.gijkl e retornaabcdefghi, então 42.542.500042500(ou seja, 42,5 × 1000).

Pode ser possível fazer essa manipulação de string inteiramente em bash, sem usar sed.

Responder4

A coisa "11..19" que você está perguntando é chamada de expansão de chaves.

Você pode usar qualquer um dos dois eval {$t_initial..$t_final},

...ou

if `seq $t_initial..$t_final`==$somevalue

informação relacionada