간격 내의 변수 조건이 있는 경우 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_initialt_final는 스크립트 자체에 의해 별도로 계산됩니다.

이 조건을 구문으로 작성할 수는 없습니다 bash. 다른 해결책을 찾았지만 너무 길고 불편한 것 같아서 더 간단하고 읽기 쉬운 해결책을 묻고 싶습니다.


코드가 부동 소수점에서 제대로 작동하는 것이 중요합니다. 이에 대한 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_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

분수는 일반적인 소수 형식으로 지정할 수 있습니다.

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's'의 경우에는 부동 소수점이므로 반드시 정확하지는 않습니다. 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.


또 다른 접근법은 10의 거듭제곱을 곱하여 숫자를 정수로 정규화하는 것입니다. 이를 위해서는 최대 소수 자릿수가 필요합니다. 예를 들어, 소수점 오른쪽에 세 자리 이상의 숫자가 있는 데이터 포인트가 없으면 모든 값에 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.기클 그리고 반환abcdefghi, 그래서 42.542.500042500(즉, 42.5 × 1000).

bash를 사용하지 않고 에서 이 문자열 조작을 완전히 수행하는 것이 가능할 수도 있습니다 sed.

답변4

당신이 요구하는 "11..19"는 중괄호 확장이라고 합니다.

다음 중 하나를 사용할 수 있습니다 eval {$t_initial..$t_final}.

...또는

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

관련 정보