Bash

在沒有 bc 實用程序的情況下複製浮點運算

  • May 16, 2022

我需要在一些尚未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)

如果您想/需要純粹在 Bash(甚至 Dash 或 Busybox)中執行此操作,您可以從如何使用 bash 或其他語言/框架進行整數和浮點計算?並濫用printf應該能夠解析浮點數並將其列印出來的事實。添加後綴 likee2會將數字放大 100(只要該數字還沒有 E 後綴)。

例如,這將按a100 放大列印,或者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.

如果我理解您的程式碼正確,您希望在 的第一個值/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(錯誤,假)退出,否則以程式碼 0(OK,真)退出。

如果門檻值已經在 shell 變數中,您當然可以將其用於awk變數分配。

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

如有必要,awk還可以擴展腳本以檢查其他欄位/proc/loadavg。這將比執行多個awk命令並在 shell 腳本中組合結果更有效和更一致。

如果要區分更多情況,可以更改腳本以使用不同的退出程式碼。

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

引用自:https://unix.stackexchange.com/questions/702743