Bash, если условия переменных внутри интервалов

Bash, если условия переменных внутри интервалов

У меня есть некоторые данные, распределенные по временным интервалам, и я хочу взять некоторые из этих данных в пределах временных интервалов. Например, у меня есть данные в промежутке 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, так что 1717000. Если параметр normalize— число с плавающей точкой (т. е. оно содержит десятичную точку, например, 42.5), мы все равно добавляем 000, а затем используем sedдля удаления десятичной точки и всего, что находится после третьей цифры. sedКоманда s/\(.*\)\.\(...\).*/\1\2/принимает строку видаabcdef.ghijkl и возвращаетсяabcdefghi, поэтому 42.542.500042500(т.е. 42,5 × 1000).

Возможно, эту манипуляцию строками можно будет выполнить полностью в bash, без использования sed.

решение4

«11..19», о котором вы спрашиваете, называется расширением скобок.

Вы можете использовать либо eval {$t_initial..$t_final},

...или

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

Связанный контент