I'm needing to write a script on some embedded Linux systems that don't have bc
installed on them and won't be able to have it installed. The script I'm writing is essentially a monitoring script that takes certain corrective actions at different load values. For example, at load average 1.5, do something.
I am wondering if there's a simple way to take the load average variable and just multiply it by 100 or simply move the decimal place to the right two spaces and pad with 0s if necessary, that would then make this integer arithmetic and normal bash (()) arithmetic expansion could take over.
Right now, I'm breaking the floating point number its truncated integer and the decimal as a whole number(e.g. 1.5, LOAD1_INT=1, LOAD1_DECIMAL=50) but would like to simplify it if possible.
Current (complicated) version:
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
Is there a way to just pare down all this code by simply turning loadavg (e.g. 1.50) into an int (e.g. 150)? Again, without using the bc
utility as it't not available on these systems.
EDIT: I wound up taking the printf
command @ilkkachu suggested and modified it into a function for use in my code. I chose this route over the awk
command as there are other places in this code where calling a function to emulate floating point arithmetic simplifies code readability and re-usability. Marking his answer as solution.
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)
^(3\.[6-9]|[4-9]\.|[0-9]{2,}\.)
? Or something similar. – Kusalananda May 16 '22 at 13:33bc
installed out of the box. See alsopax
andm4
which are often omitted. – Stéphane Chazelas May 16 '22 at 13:38awk
installed :D – Romeo Ninov May 16 '22 at 13:413.5
and3.25
, you might get an unexpected result if you compare5
with25
. – Bodo May 16 '22 at 13:42That's a great catch on the decimal point only working if it has a value in the hundreths place as well, I can fix that with another conditional but this is just further evidence to me that I need to simplify this by moving the decimal point to the right two places.
– William May 16 '22 at 13:48awk
is probably your best bet but you can turn a decimal to a scaled integer withprintf "%.2f" 3.5 | tr -d .
. – doneal24 May 16 '22 at 13:49awk -v load1_int="$load1_int" -v crit_load_int="$crit_load_int" 'BEGIN{if (load1_int > crit_load_int) { ... }}'
– jesse_b May 16 '22 at 13:57dec_to_int()
, without thefunction
keyword (it's a ksh thing), and while Bash supports it too, not all shells might (e.g. Dash and Busybox don't), and there's no upside. Also, if you're on Bash, you can useprintf -v SCALED_INT "%.0f\n" "${DECIMAL}e${SCALE_FACTOR}"
instead ofSCALED_INT=$(printf ...)
, it saves spawning a copy of the shell for the command substitution. – ilkkachu May 16 '22 at 20:43