Hi mocore
... but also ( as i have read a few scripts u posted hear )
make me wander if you have some policy/reason to avoid or just aversion to functions
I do not avoid or have an aversion to functions. I do try to be aware
of the impact they may have.
1. Functions are good. They provide consistency and can make code easier to follow.
2. Functions are bad. Making functions out of every little bit of code repetition can
leave you lost trying to follow a maze of functions.
3. Functions hurt speed. If your function returns a value to your variable, the impact
can become significant if it looks like this: Var=$(MyFunction).
I'd like to address item 3.
#!/bin/sh
#: Calculate elapsed time.
#: Millisecond resolution If GNU date is present, otherwise seconds only.
GetET1(){ ET1=$(date +%s.%3N); ET1=${ET1%.%3N}; }
GetET2(){ ET2=$(date +%s.%3N); ET2=${ET2%.%3N}; }
CalcET(){ calc $ET2-$ET1; }
# --------------------------------------------------------------- #
# Checks that all chars are digits, and that digits are present.
IsInteger()
{
# Empty string is not an integer.
# If we delete all of the digits, that should leave an empty string.
[ -z $1 ] || [ -n "${1//[0-9]/}" ] && echo "" && return
# Valid integer.
echo "$1"
return
}
# --------------------------------------------------------------- #
# --------------------------------------------------------------- #
# Checks that all chars are digits, and that digits are present.
IsIntegerByRef()
{
# $1=Name of variable to place the result in.
# $2=Number to validate.
local __IntVar=$1
local __Int2Test=$2
# Empty string is not an integer.
# If we delete all of the digits, that should leave an empty string.
[ -z $2 ] || [ -n "${2//[0-9]/}" ] && __Int2Test=""
eval $__IntVar="'$__Int2Test'"
}
# --------------------------------------------------------------- #
#: --------------------------------------------------------------- #
#: Fixed point division. Input positive integers only, no zeros allowed.
#: Usage: Divide $Dividend $Divisor
Divide()
{
# Vars will either be an integer or empty from IsInteger.
local Dividend=$(IsInteger $1)
local Divisor=$(IsInteger $2)
# local Dividend=$1
# local Divisor=$2
# Number of decimal places desired.
# We increases decimal places by 1 to allow rounding.
local DPcount=$(($DPdefault + 1))
local Quotient
# Leading zero causes Decimal to be treated as octal. We deal
# with this by padding with a leadin one. Doubles as carry
# flag when rounding by incrementing to two.
local Decimal=1
local Zeros=".00000000"
# 2 integers are required.
[ -z $Dividend ] && echo "" && return
[ -z $Divisor ] && echo "" && return
# Dividing by zero not allowed.
[ $Divisor -eq 0 ] && echo "" && return
# Dividing into zero equals zero plus default decimal places..
if [ $Dividend -eq 0 ]
then
[ $DPcount -gt 0 ] && Decimal="${Zeros:0:$DPcount}"
printf "0%s\n" $Decimal
return
fi
# First calculate the integer portion.
Quotient=$(($Dividend / $Divisor))
# Calculate the remainder to the requested number of decimal places.
while [ $DPcount -gt 0 ]
do
Dividend=$((($Dividend % $Divisor) * 10))
Decimal="$Decimal$(($Dividend / $Divisor))"
DPcount=$(($DPcount - 1))
done
# Perform rounding.
Decimal=$(($Decimal + 5))
# Remove the extra decimal place we added by removing the last digit.
Decimal=${Decimal%[0-9]}
# If the leading digit equals 2, rounding caused a carry.
[ ${Decimal:0:1} -eq 2 ] && Quotient=$(($Quotient + 1))
# Remove the carry digit (MSD) from the decimal portion.
Decimal=${Decimal:1}
printf "%s.%s\n" $Quotient $Decimal
}
# --------------------------------------------------------------- #
#: --------------------------------------------------------------- #
#: Fixed point division. Input positive integers only, no zeros allowed.
#: Usage: Divide $Dividend $Divisor
DivideNoFunc()
{
# Vars will either be an integer or empty from IsInteger.
# local Dividend=$(IsInteger $1)
# local Divisor=$(IsInteger $2)
local Dividend
local Divisor
[ -z $1 ] || [ -n "${1//[0-9]/}" ] && Dividend="" || Dividend="$1"
[ -z $2 ] || [ -n "${2//[0-9]/}" ] && Divisor="" || Divisor="$2"
# Number of decimal places desired.
# We increases decimal places by 1 to allow rounding.
local DPcount=$(($DPdefault + 1))
local Quotient
# Leading zero causes Decimal to be treated as octal. We deal
# with this by padding with a leadin one. Doubles as carry
# flag when rounding by incrementing to two.
local Decimal=1
local Zeros=".00000000"
# 2 integers are required.
[ -z $Dividend ] && echo "" && return
[ -z $Divisor ] && echo "" && return
# Dividing by zero not allowed.
[ $Divisor -eq 0 ] && echo "" && return
# Dividing into zero equals zero plus default decimal places..
if [ $Dividend -eq 0 ]
then
[ $DPcount -gt 0 ] && Decimal="${Zeros:0:$DPcount}"
printf "0%s\n" $Decimal
return
fi
# First calculate the integer portion.
Quotient=$(($Dividend / $Divisor))
# Calculate the remainder to the requested number of decimal places.
while [ $DPcount -gt 0 ]
do
Dividend=$((($Dividend % $Divisor) * 10))
Decimal="$Decimal$(($Dividend / $Divisor))"
DPcount=$(($DPcount - 1))
done
# Perform rounding.
Decimal=$(($Decimal + 5))
# Remove the extra decimal place we added by removing the last digit.
Decimal=${Decimal%[0-9]}
# If the leading digit equals 2, rounding caused a carry.
[ ${Decimal:0:1} -eq 2 ] && Quotient=$(($Quotient + 1))
# Remove the carry digit (MSD) from the decimal portion.
Decimal=${Decimal:1}
printf "%s.%s\n" $Quotient $Decimal
}
# --------------------------------------------------------------- #
#: --------------------------------------------------------------- #
#: Fixed point division. Input positive integers only, no zeros allowed.
#: Usage: Divide $Dividend $Divisor
DivideByRef()
{
# Vars will either be an integer or empty from IsInteger.
# local Dividend=$(IsInteger $1)
# local Divisor=$(IsInteger $2)
local Dividend
local Divisor
IsIntegerByRef "Dividend" "$1"
IsIntegerByRef "Divisor" "$2"
# Number of decimal places desired.
# We increases decimal places by 1 to allow rounding.
local DPcount=$(($DPdefault + 1))
local Quotient
# Leading zero causes Decimal to be treated as octal. We deal
# with this by padding with a leadin one. Doubles as carry
# flag when rounding by incrementing to two.
local Decimal=1
local Zeros=".00000000"
# 2 integers are required.
[ -z $Dividend ] && echo "" && return
[ -z $Divisor ] && echo "" && return
# Dividing by zero not allowed.
[ $Divisor -eq 0 ] && echo "" && return
# Dividing into zero equals zero plus default decimal places..
if [ $Dividend -eq 0 ]
then
[ $DPcount -gt 0 ] && Decimal="${Zeros:0:$DPcount}"
printf "0%s\n" $Decimal
return
fi
# First calculate the integer portion.
Quotient=$(($Dividend / $Divisor))
# Calculate the remainder to the requested number of decimal places.
while [ $DPcount -gt 0 ]
do
Dividend=$((($Dividend % $Divisor) * 10))
Decimal="$Decimal$(($Dividend / $Divisor))"
DPcount=$(($DPcount - 1))
done
# Perform rounding.
Decimal=$(($Decimal + 5))
# Remove the extra decimal place we added by removing the last digit.
Decimal=${Decimal%[0-9]}
# If the leading digit equals 2, rounding caused a carry.
[ ${Decimal:0:1} -eq 2 ] && Quotient=$(($Quotient + 1))
# Remove the carry digit (MSD) from the decimal portion.
Decimal=${Decimal:1}
printf "%s.%s\n" $Quotient $Decimal
}
# --------------------------------------------------------------- #
# Calculations are performed to DPdefault + 1 decimal places, and
# rounded to DPdefault decimal places.
DPdefault=8
echo
echo "Function (requires subshell) Divide 21053358 6701492 equals $(Divide 21053358 6701492)"
GetET1; for C in `seq 1 10000`; do Divide 21053358 6701492 > /dev/null; done; GetET2; echo "Elapsed time for 10,000 runs=$(CalcET) Secs."
echo
echo "No function (inline code) DivideNoFunc 21053358 6701492 equals $(DivideNoFunc 21053358 6701492)"
GetET1; for C in `seq 1 10000`; do DivideNoFunc 21053358 6701492 > /dev/null; done; GetET2; echo "Elapsed time for 10,000 runs=$(CalcET) Secs."
echo
echo "Function (indirect variable reference) DivideByRef 21053358 6701492 equals $(DivideByRef 21053358 6701492)"
GetET1; for C in `seq 1 10000`; do DivideByRef 21053358 6701492 > /dev/null; done; GetET2; echo "Elapsed time for 10,000 runs=$(CalcET) Secs."
echo
The above contains 3 fixed point division functions.
They all use the same code to confirm they received valid integers.
The only difference is how data is exchanged with the validation code.
Divide() calls a function like this to update the variable:
Dividend=$(IsInteger $1)
DivideNoFunc() uses inline code like this to update the variable:
[ -z $1 ] || [ -n "${1//[0-9]/}" ] && Dividend="" || Dividend="$1"
DivideByRef() passes the name of the variable and the value to validate to a function:
IsIntegerByRef "Dividend" "$1"
The timing is for each version calculating pi to 9 decimal places and
rounding to 8 decimal places 10,000 times:
tc@E310:~/Scripting/AddressParser$ ./Functions
Function (requires sub-shell) Divide 21053358 6701492 equals 3.14159265
Elapsed time for 10,000 runs=17.052 Secs.
No function (inline code) DivideNoFunc 21053358 6701492 equals 3.14159265
Elapsed time for 10,000 runs=4.866 Secs.
Function (indirect variable reference) DivideByRef 21053358 6701492 equals 3.14159265
Elapsed time for 10,000 runs=5.424 Secs.
I suspect the function call in the first example starts a sub-shell which results
in a big timing hit.
The second version with inline code is fastest as expected.
The third version only has a 12% timing penalty.
A copy of the script is also attached.