bc 유틸리티 없이 부동 소수점 연산 복제

bc 유틸리티 없이 부동 소수점 연산 복제

bc아직 설치되지 않아 설치할 수 없는 일부 임베디드 Linux 시스템에 스크립트를 작성 해야 합니다 . 제가 작성 중인 스크립트는 본질적으로 다양한 로드 값에서 특정 수정 조치를 취하는 모니터링 스크립트입니다. 예를 들어 부하 평균이 1.5일 때 작업을 수행합니다.

부하 평균 변수를 가져와서 100을 곱하는 쉬운 방법이 있는지 궁금합니다.또는소수점 자리를 오른쪽으로 두 칸 이동하고 필요한 경우 0으로 채워 정수 연산 및 일반 bash(()) 연산 확장이 대신할 수 있도록 합니다.

지금은 부동 소수점 숫자와 잘린 정수 및 소수를 정수(예: 1.5, LOAD1_INT = 1, LOAD1_DECIMAL = 50)로 사용하고 있지만 최대한 단순화하고 싶습니다.

현재(복잡한) 버전:

CRIT_LOAD=3.5
if [[ $CRIT_LOAD =~ ^[0-9]{1,2}\.[0-9]{1,2}$ ]]; then
    # Since bash can't handle floating point arithmetic, break $CRIT_LOAD float into 2 separate integers
    CRIT_LOAD_INT=$(echo $CRIT_LOAD | cut -d'.' -f1)
    CRIT_LOAD_DECIMAL=$(echo $CRIT_LOAD | cut -d'.' -f2)
elif [[ $CRIT_LOAD =~ ^[0-9]{1,2}$ ]]; then
    # If $CRIT_LOAD is already an int, update variables so Monitor code works unchanged
    CRIT_LOAD_INT=$CRIT_LOAD
    CRIT_LOAD_DECIMAL=0
else
    # Set a default value of 1.0 if we can't parse CRIT_LOAD value
    CRIT_LOAD_INT=1
    CRIT_LOAD_DECIMAL=0
fi
LOAD1=$(cat /proc/loadavg | cut -d' ' -f1)
LOAD1_INT=$(echo $LOAD1 | cut -d'.' -f1)
LOAD1_DECIMAL=$(echo $LOAD1 | cut -d'.' -f2)

# Current load int is already higher than critical threshold int
if (( LOAD1_INT > CRIT_LOAD_INT )); then
    log "CRITICAL: Load values have exceeded threshold."
elif (( LOAD1_INT == CRIT_LOAD_INT )); then
    # If current load int is same as crit threshold int, compare decimals
    if (( LOAD1_DECIMAL > CRIT_LOAD_DECIMAL )); then
        log "CRITICAL: Load values have exceeded threshold."
    fi
fi

단순히 loadavg(예: 1.50)를 int(예: 150)로 변환하여 이 모든 코드를 줄일 수 있는 방법이 있습니까? 다시,아니요bc이 시스템에서는 이 유틸리티를 사용할 수 없으므로 이 유틸리티를 사용하십시오 .

편집하다:결국 printf@ilkkachu가 제안한 명령을 가져와 내 코드에서 사용할 함수로 수정했습니다. awk이 코드에는 부동 소수점 연산을 시뮬레이션하기 위해 함수가 호출되어 코드의 가독성과 재사용성을 단순화하는 다른 위치가 있기 때문에 명령 대신 이 경로를 선택했습니다 . 그의 대답을 해결책으로 표시하십시오.

function dec_to_int() {
    DECIMAL=$1
    SCALE_FACTOR=$2
    # printf removes decimal and allows $SCALE_FACTOR additional spaces to be included, 0-pads numbers that would be too small otherwise
    # NOTE: printf will round number if the values it keeps are greater than the scale factor
    # e.g. SCALE_FACTOR=2, 1.759 -> 176
    SCALED_INT=$(printf "%.0f\n" "${DECIMAL}e${SCALE_FACTOR}")
    echo $SCALED_INT
}

LOAD1=$(cat /proc/loadavg | cut -d' ' -f1)
LOAD1=$(dec_to_int $LOAD1 2)

답변1

귀하의 코드를 올바르게 이해했다면 /proc/loadavg첫 번째 값이 CRIT_LOAD. /proc/loadavg이 검사(분석 포함)는 단일 명령을 사용하여 수행할 수 있습니다 awk.

if ! awk -v crit_load=3.5 '$1>crit_load { exit 1 }' /proc/loadavg
then
    log "CRITICAL: Load values have exceeded threshold."
fi

스크립트는 awk(모든 행)을 읽고 /proc/loadavg첫 번째 필드가 더 크면 crit_load코드 1(Error, False)로 종료하고 , 그렇지 않으면 코드 0(OK, True)으로 종료합니다.

임계값이 이미 쉘 변수에 있는 경우 변수 awk할당에 이를 사용할 수 있습니다.

CRIT_LOAD=3.5
if ! awk -v crit_load="$CRIT_LOAD" '$1>crit_load { exit 1 }' /proc/loadavg
then # ...

awk필요한 경우 이 스크립트를 확장하여 다른 필드를 확인할 수도 있습니다 /proc/loadavg. 이는 여러 awk명령을 실행하고 그 결과를 쉘 스크립트에 결합하는 것보다 더 효율적이고 일관성이 있습니다.

더 많은 사례를 차별화하려면 다른 종료 코드를 사용하도록 스크립트를 변경할 수 있습니다.

awk -v crit_load=3.5 -v warn_load=1.5 '$1>crit_load { exit 2 } $1>warn_load { exit 1 }' /proc/loadavg
case "$?" in
    2) do_something ;;
    1) do_other ;;
    0) do_nothing ;;
    *) handle_unknown_value ;;
esac

답변2

Bash(또는 Dash나 Busybox)에서만 이 작업을 수행하고 싶거나 필요하다면 다음에서 수행할 수 있습니다.Bash 또는 다른 언어/프레임워크에서 정수 및 부동 소수점 계산을 수행하는 방법은 무엇입니까?printf그리고 부동 소수점 숫자를 구문 분석하고 인쇄할 수 있어야 한다는 사실을 남용하세요. 예를 들어 접미사를 추가하면 e2숫자가 100만큼 확대됩니다(숫자에 아직 E 접미사가 없는 경우).

예를 들어, a비례적으로 100을 인쇄 합니다 123.

a=1.23
printf "%.0f\n" "${a}e2"

또는 다음 내용을 읽어보세요 /proc/loadavg.

read avg1 avg5 avg15 rest < /proc/loadavg
scaled_load=$(printf "%.0f\n" "${avg1}e2")

(또는 printf -v scaled_load "%.0f\n" "${avg1}e2"Bash에서 명령 대체 대신)

그러면 또 loadavg소수점 이하 두자리만 있으면 중간에 있는 소수점을 없애고 1.23바로 가 됩니다 123.

링크된 게시물에는 부동 소수점 연산과 함께 작동하는 다른 프로그램도 포함되어 있습니다. 설치가 가 아니더라도 bc예를 들어 가 있을 수 있습니다 awk.

답변3

DC가 있나요?

아니면 그럴 수도 있습니다. 점 뒤의 소수점 처음 두 자리를 사용합니다. 그런 다음 정수에 100을 곱하고 두 개의 소수(이제는 더 이상 소수가 아님)를 더하여 해당 숫자를 얻습니다.

loadavg="1.50"
mult100="100"
DECIMALS=$( echo "${loadavg#*.}" | cut -c 1-2 )
INT=$( echo $loadavg | cut -f 1 -d . )
NEW_INT=$(( $INT * $mult100 ))
NEW_LOADAVG=$(( $NEW_INT + $DECIMALS ))
echo "$NEW_LOADAVG"

printf가 필요하지 않습니다. 더 큰 숫자에도 작동합니다.

loadavg="147372.52772830"
mult100="100"
DECIMALS=$( echo "${loadavg#*.}" | cut -c 1-2 )
INT=$( echo $loadavg | cut -f 1 -d . )
NEW_INT=$(( $INT * $mult100 ))
NEW_LOADAVG=$(( $NEW_INT + $DECIMALS ))
echo "$NEW_LOADAVG"

항상 내림됩니다. 숫자에 100을 곱하고 나머지 소수점을 제거하면 됩니다.

가장 가까운 정수로 반올림해야 하는 경우 다음을 사용하세요.

loadavg="147372.52772830"
mult100="100"
DECIMALS=$( echo "${loadavg#*.}" | cut -c 1-2 )
INT=$( echo $loadavg | cut -f 1 -d . )
NEW_INT=$(( $INT * $mult100 ))
ROUNDING=$( echo "${loadavg#*.}" | cut -c 3 )
if [[ $ROUNDING -ge 5 ]]; then NEW_LOADAVG=$(( $NEW_INT + $DECIMALS + 1 )); else NEW_LOADAVG=$(( $NEW_INT + $DECIMALS )); fi
echo "$NEW_LOADAVG"

반올림이 필요한 경우 다음을 사용하십시오.

loadavg="147372.52772830"
mult100="100"
DECIMALS=$( echo "${loadavg#*.}" | cut -c 1-2 )
INT=$( echo $loadavg | cut -f 1 -d . )
NEW_INT=$(( $INT * $mult100 ))
ROUNDING=$( echo "${loadavg#*.}" | cut -c 3 )
if [[ $ROUNDING -gt 0 ]]; then NEW_LOADAVG=$(( $NEW_INT + $DECIMALS + 1 )); else NEW_LOADAVG=$(( $NEW_INT + $DECIMALS )); fi
echo "$NEW_LOADAVG"

수정: 아 해결됐네요 -_-'

관련 정보