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

0

Eu tenho alguns dados espalhados por intervalos de tempo, e eu quero pegar alguns desses dados dentro dos intervalos de tempo. Por exemplo, tenho dados às vezes em 1..9, 11..19, etc., e quero coletar dados dentro de 1-2, 11-12, etc.

Isso fará parte de um script bash mais complexo, no qual desejo incluir essa condição, com um if cycle, para isolar os horários em que posso capturar os dados.

Eu estava pensando em algo como:

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

onde t_initial e t_final são calculados separadamente, pelo próprio script.

Não consigo escrever esta condição na sintaxe bash . Eu encontrei algumas outras soluções, mas elas parecem extremamente longas e inconvenientes, então estou aqui para pedir soluções mais simples e mais legíveis.

É importante que o código funcione corretamente com floats. Eu estou tentando consertar a solução OP para isso, mas ainda não consigo encontrar um caminho.

    
por Py-ser 27.06.2014 / 09:37

4 respostas

1

bash não suporta nativamente uma comparação de intervalo, nem números de ponto flutuante, por isso temos que fazer alguns de nós mesmos. Eu também vou definir uma função e usar bc para o cálculo de ponto flutuante. Aqui está o resultado final e a suíte 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 função compareRanges leva pelo menos dois argumentos. O primeiro é seu t_initial e o segundo é seu t_final . Pode levar arbitrariamente muitos outros argumentos em pares depois disso, que são seus begin1 , fin1 , begin2 , fin2 , em ordem.

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

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

Portanto, 1 é t_initial , 3 é t_final , 2 é begin1 e 4 é fin1 .

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

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

Aqui testamos contra 1-4, 2-6 e 3-9. No loop while , olhamos cada par e comparamos com t_initial e t_final .

Como bash não suporta números fracionários, usamos bc , uma calculadora de precisão arbitrária . Sua entrada é dada pela <<<"$t_initial >= $1" ... part: que alimenta a string na entrada padrão. $1 é o começo do intervalo que estamos vendo nesta iteração do loop, e $2 é o fim; Comparamos os limites inferior e superior ao mesmo tempo com && . bc produzirá 1 quando as comparações forem verdadeiras e 0 quando uma for falsa. Nós salvamos o resultado em in_range e a função é bem-sucedida ( return 0 ) quando os dois testes são verdadeiros.

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

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

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

No final, se nenhum dos pares limite corresponder, nós 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 todos os "OK" quando você executá-lo.

Como alternativa, outros shells (como zsh ) do suportam 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 caso de zsh eles são floats, então eles não são necessariamente precisos; bc estará sempre correto.

    
por 12.07.2014 / 05:03
3

Não tenho certeza se você gostaria dessa solução. Tem 2 características:

  • 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
    
por 27.06.2014 / 12:20
1

Aqui estão alguns desavergonhados desavergonhados da resposta do LatinSuD que lidam com ponto flutuante. Você perceberá que a resposta dele é "Nenhum programa externo é necessário". Este usa o programa da calculadora, bc , como 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 )) test e o envia para bc , e, em seguida, captura a saída de bc .

Outra abordagem é normalizar os números para números 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 do ponto 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/\(.*\)\.\(...\).*//')
        ;;
    *)
        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 para a função normalize for um inteiro simples (ou seja, um número sem ponto decimal, por exemplo, 17 ) nós podemos multiplicar por 1000 simplesmente adicionando 000 , então 1717000 . Se o parâmetro para normalize for um número de ponto flutuante (isto é, contém um ponto decimal, por exemplo, 42.5 ) nós ainda adicionamos o 000 , e, em seguida, use sed para remover o ponto decimal e tudo após o terceiro dígito. O sed command s/\(.*\)\.\(...\).*// usa uma string como abcdef . ghijkl e retorna abcdefghi , 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 .

    
por 17.07.2014 / 01:08
0

A coisa "11..19" sobre a qual você está perguntando é chamada de expansão de contraventamento.

Você pode usar eval {$t_initial..$t_final}

... ou

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

    
por 11.07.2014 / 20:59