間隔内の変数の条件を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関数は少なくとも 2 つの引数を取ります。1 つ目は your t_initial、2 つ目は your ですt_final。その後に、 your 、、、、の順に任意の数の引数をペアで取ることがbegin1できfin1ます。begin2fin2

最初のテスト ケースでは、質問のコメントの範囲 1 ~ 3 と 2 ~ 4 を比較します。

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

したがって、1は、は、は、 はです。t_initial3t_final2begin14fin1

複数の範囲を使用する場合は、それらをすべてペアでリストします。

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出力され、一方が偽の場合は が出力されます。結果を に保存し、両方のテストが真の場合は関数が成功します ( )。10in_rangereturn 0

小数は通常の 10 進数形式で指定できます。

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


もう 1 つの方法は、10 の累乗を掛けて数値を整数に正規化することです。この方法では、小数点以下の桁数が最大である必要があります。たとえば、小数点の右側の桁数が 3 桁を超えるデータ ポイントがない場合は、すべてに 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 を掛けることができるので00017→となります17000。 関数のパラメータnormalizeが浮動小数点数(つまり、小数点を含む数、例:42.5)の場合は、 を追加し000、 を使用してsed小数点と 3 桁目以降のすべてを削除します。sedコマンドは次s/\(.*\)\.\(...\).*/\1\2/のような文字列を受け取ります。ABCデフギクル そして返品エイビーシーディーエフギなので42.542.500042500(つまり、42.5 × 1000)となります。

bashを使用せずに、この文字列操作全体を で実行できる可能性がありますsed

答え4

あなたが尋ねている「11..19」のことは、ブレース拡張と呼ばれます。

どちらでも使えますeval {$t_initial..$t_final}

...または

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

関連情報