
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 bash
script mais complexo, no qual quero incluir essa condição, com um if
ciclo, 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_initial
e t_final
são calculados separadamente, pelo próprio script.
Não consigo escrever esta condição na bash
sintaxe. 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
bash
nã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 bc
para 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 compareRanges
função leva pelo menos dois argumentos. O primeiro é seu t_initial
e 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
é begin1
e 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 while
loop, olhamos cada par por vez e comparamos com t_initial
e t_final
.
Porque bash
nã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 &&
. bc
será gerado 1
quando as comparações forem verdadeiras e 0
quando uma for falsa. Salvamos o resultado em in_range
e 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
bc
irá 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 zsh
caso de eles são flutuantes, portanto não são necessariamente precisos; bc
sempre 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, 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 ))
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 normalize
funçã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 17
→ 17000
. Se o parâmetro to normalize
for um número de ponto flutuante (ou seja, contém um ponto decimal, por exemplo, 42.5
), ainda acrescentamos o 000
e usamos sed
para remover o ponto decimal e tudo após o terceiro dígito. O sed
comando s/\(.*\)\.\(...\).*/\1\2/
usa uma string comoabcdef.gijkl
e retornaabcdefghi, então 42.5
→ 42.5000
→ 42500
(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