voidinit
10-07-2006, 04:55 AM
A while ago I helped evac-q8r in performing multiple divisions in bash that required floating point percision. The problems we were facing is that bash arithmatic expansion does not support floating point precision, and that the 'hacks' required to emulate it are pretty slow.
Here's that thread: http://www.justlinux.com/forum/showthread.php?t=146778
Well, I figured out an ever better, much faster way to do floating point math in bash using bc. Naturally, using command substiution for each and every bc call would be very slow, innecffiecent, and just plain unstylish. However, executing one bc process, and having it listen for requests and then serving them is much faster, more effiecient and very stylish. So if you need to do a lot of math in bash quickly, here is the best solution I can come up with.
#!/bin/bash
mypid=$$
scale=5 # The floating point precision we wish to achieve.
# A function to exit cleanly or warn in case of an error.
die(){
echo "$1" >&2
if [ ! -z $2 ] ; then
if [ -e /proc/self/fd/7 ]; then
echo "exit" >&7 #Stop bc if running.
exec 7>&- #Close file descriptor 7
exec 7<&-
fi
if [ -e /proc/self/fd/8 ] ;then
exec 8>&- #Close file descriptor 8
exec 8<&-
fi
[ -e /tmp/bc-$mypid.in ] && rm /tmp/bc-$mypid.in
[ -e /tmp/bc-$mypid.out ] && rm /tmp/bc-$mypid.out
exit $2
fi
return 0
}
# Create some fifo's that will act as our "bc services" sockets.
mkfifo /tmp/bc-$mypid.{in,out} || die "Cannot create fifo's in /tmp" 1
# Attach file descriptors to those sockets in read-write mode.
exec 7<>/tmp/bc-$mypid.in || die "Could not attach to /tmp/bc-$mypid.in" 1
exec 8<>/tmp/bc-$mypid.out || die "Could not attach to /tmp/bc-$mypid.out" 1
# Start our "bc service" in the background and
# attach it's stdout/stdin/stderr to our fifo sockets.
bc 0<&7 1>&8 2>&8 &
# Do the work.
while read num den
do
echo "scale=$scale; $num / $den " >&7 #Send our request.
read -t1 response <&8 || die "Connection to bc service timed out." # Read our response
# Evaluate the response.
if [ "${response//:/}" != "$response" ]; then #if the response contains a colon
# we have an error.
while read -t1 error
do
response="$response$error"
done <&8
die "Bc reported an error. Message: $response"
else
#everything went smoothly.
echo "$num / $den is $response"
fi
response="ERROR" # So no false answer is given if bc dies.
done < ./numbers.txt
# Shutdown our bc service
echo "exit" >&7
die "Done" 0 #exit gracefully
Contents of numbers.txt used for input
20 49
598 39
84598 583
8584 8584
9301 583
4830 485
Output of script:
20 / 49 is .40816
598 / 39 is 15.33333
84598 / 583 is 145.10806
8584 / 8584 is 1.00000
9301 / 583 is 15.95368
4830 / 485 is 9.95876
Done
I've used this same methodology to talk to different interpreters, like php, mysql client and to a lesser degree perl!
Inline php/SQL in bash? Hell yes!
Here's that thread: http://www.justlinux.com/forum/showthread.php?t=146778
Well, I figured out an ever better, much faster way to do floating point math in bash using bc. Naturally, using command substiution for each and every bc call would be very slow, innecffiecent, and just plain unstylish. However, executing one bc process, and having it listen for requests and then serving them is much faster, more effiecient and very stylish. So if you need to do a lot of math in bash quickly, here is the best solution I can come up with.
#!/bin/bash
mypid=$$
scale=5 # The floating point precision we wish to achieve.
# A function to exit cleanly or warn in case of an error.
die(){
echo "$1" >&2
if [ ! -z $2 ] ; then
if [ -e /proc/self/fd/7 ]; then
echo "exit" >&7 #Stop bc if running.
exec 7>&- #Close file descriptor 7
exec 7<&-
fi
if [ -e /proc/self/fd/8 ] ;then
exec 8>&- #Close file descriptor 8
exec 8<&-
fi
[ -e /tmp/bc-$mypid.in ] && rm /tmp/bc-$mypid.in
[ -e /tmp/bc-$mypid.out ] && rm /tmp/bc-$mypid.out
exit $2
fi
return 0
}
# Create some fifo's that will act as our "bc services" sockets.
mkfifo /tmp/bc-$mypid.{in,out} || die "Cannot create fifo's in /tmp" 1
# Attach file descriptors to those sockets in read-write mode.
exec 7<>/tmp/bc-$mypid.in || die "Could not attach to /tmp/bc-$mypid.in" 1
exec 8<>/tmp/bc-$mypid.out || die "Could not attach to /tmp/bc-$mypid.out" 1
# Start our "bc service" in the background and
# attach it's stdout/stdin/stderr to our fifo sockets.
bc 0<&7 1>&8 2>&8 &
# Do the work.
while read num den
do
echo "scale=$scale; $num / $den " >&7 #Send our request.
read -t1 response <&8 || die "Connection to bc service timed out." # Read our response
# Evaluate the response.
if [ "${response//:/}" != "$response" ]; then #if the response contains a colon
# we have an error.
while read -t1 error
do
response="$response$error"
done <&8
die "Bc reported an error. Message: $response"
else
#everything went smoothly.
echo "$num / $den is $response"
fi
response="ERROR" # So no false answer is given if bc dies.
done < ./numbers.txt
# Shutdown our bc service
echo "exit" >&7
die "Done" 0 #exit gracefully
Contents of numbers.txt used for input
20 49
598 39
84598 583
8584 8584
9301 583
4830 485
Output of script:
20 / 49 is .40816
598 / 39 is 15.33333
84598 / 583 is 145.10806
8584 / 8584 is 1.00000
9301 / 583 is 15.95368
4830 / 485 is 9.95876
Done
I've used this same methodology to talk to different interpreters, like php, mysql client and to a lesser degree perl!
Inline php/SQL in bash? Hell yes!