
У меня есть некоторые данные, распределенные по временным интервалам, и я хочу взять некоторые из этих данных в пределах временных интервалов. Например, у меня есть данные в промежутке 1..9, 11..19 и т. д., и я хочу взять данные в промежутке 1-2, затем 11-12 и т. д.
Это будет частью более сложного bash
сценария, в который я хочу включить это условие с if
циклом, чтобы изолировать моменты времени, когда я могу перехватить данные.
Я думал что-то вроде:
if (( $t_initial & $t_final )) in (begin1, fin1) or in (begin2, fin2) ...
где t_initial
и t_final
рассчитываются отдельно, самим скриптом.
Я не могу записать это условие в bash
синтаксисе. Я нашел несколько других решений, но они кажутся чрезвычайно длинными и неудобными, поэтому я здесь, чтобы попросить более простые и читабельные решения.
Важно, чтобы код работал правильно с float. Я пытаюсь исправить решение OP для этого, но все еще не могу найти способ.
решение1
Не уверен, понравится ли вам это решение. У него есть 2 особенности:
- Никаких внешних программ не требуется
- Он использует функцию, так что, по крайней мере, скрывает сложность сравнений.
Это оно:
#!/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
решение2
bash
не поддерживает сравнение диапазонов и числа с плавающей точкой, поэтому нам придется сделать часть этого самим. Я также собираюсь определить функцию и использовать ее bc
для вычислений с плавающей точкой. Вот конечный результат и тестовый набор:
# 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
Функция compareRanges
принимает как минимум два аргумента. Первый — ваш t_initial
, а второй — ваш t_final
. Она может принимать произвольное количество других аргументов парами после этого, которые являются вашими begin1
, fin1
, begin2
, fin2
, по порядку.
В первом тестовом примере сравниваются диапазоны в комментариях к вопросу: 1-3 и 2-4.
compareRanges 1 3 2 4 && echo BAD || echo OK
Так 1
есть t_initial
, 3
есть t_final
, 2
есть begin1
и 4
есть fin1
.
Если вы хотите использовать несколько диапазонов, вы затем перечисляете их все парами:
compareRanges 5 7 1 4 2 6 3 9 && echo OK || echo BAD
Здесь мы проверяем по 1-4, 2-6 и 3-9. В while
цикле мы смотрим на каждую пару по очереди и сравниваем ее с t_initial
и t_final
.
Поскольку bash
не поддерживает дробные числа, мы используемbc
, калькулятор произвольной точности. Его входные данные задаются частью <<<"$t_initial >= $1" ...
: которая подает строку в стандартный ввод. $1
— начало диапазона, который мы в данный момент просматриваем в этой итерации цикла, а $2
— конец; мы сравниваем нижнюю и верхнюю границы одновременно с &&
. bc
выведет, 1
когда сравнения истинны, а 0
когда одно из них ложно. Мы сохраняем результат в in_range
, и функция завершается успешно ( return 0
), когда оба наших теста истинны.
Дробные числа можно просто указать в их обычной десятичной форме:
compareRanges 1.5 2.5 0.3 3.1 && echo OK || echo BAD
bc
будет обрабатывать числа с любым количеством дробных цифр и любой необходимой вам величиной.
В конце, если ни одна из пар границ не совпала, мы терпим неудачу ( return 1
). Вы можете использовать функцию как:
if compareRanges $t_initial $t_final 2 4 11 19
then
...
fi
При запуске тестового набора должно быть выведено сообщение «OK».
В качестве альтернативы можно использовать другие оболочки (например,zsh
)делатьподдерживают дробные значения переменных. Если бы вы могли запустить свой скрипт в одном из них, вы могли бы избежать использования bc
, хотя сравнение все равно лучше сделать в функции. По крайней мере в zsh
случае они являются числами с плавающей точкой, поэтому они не обязательно точны; bc
всегда будут правильными.
решение3
Вот пара бесстыдных копий ответа LatinSuD, которые обрабатывают числа с плавающей точкой. Вы заметите, что его ответ хвастается: «Никаких внешних программ не требуется». Этот использует программу-калькулятор, , bc
как он и предложил:
#!/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
Это просто берет if (( $t1 >= $1 && $t2 <= $2 ))
тест и отправляет его в bc
, а затем захватывает выходные данные из bc
.
Другой подход — нормализовать числа до целых чисел, умножив их на степень десяти. Для этого требуется максимальное количество десятичных цифр. Например, если ни одна точка данных не имеет более трех цифр справа от десятичной точки, мы можем умножить все на 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
Если параметр функции normalize
— простое целое число (т. е. число без десятичной точки, например, 17
), мы можем умножить на 1000, просто добавив 000
, так что 17
→ 17000
. Если параметр normalize
— число с плавающей точкой (т. е. оно содержит десятичную точку, например, 42.5
), мы все равно добавляем 000
, а затем используем sed
для удаления десятичной точки и всего, что находится после третьей цифры. sed
Команда s/\(.*\)\.\(...\).*/\1\2/
принимает строку видаabcdef.ghijkl
и возвращаетсяabcdefghi, поэтому 42.5
→ 42.5000
→ 42500
(т.е. 42,5 × 1000).
Возможно, эту манипуляцию строками можно будет выполнить полностью в bash
, без использования sed
.
решение4
«11..19», о котором вы спрашиваете, называется расширением скобок.
Вы можете использовать либо eval {$t_initial..$t_final}
,
...или
if `seq $t_initial..$t_final`==$somevalue