Bash Scripting and bc - bash

I'm trying to write a bash script and I needed to do some floating point math. Basically I want to do something like this:
NUM=$(echo "scale=25;$1/10" | bc)
if [ $? -ne 0 ]
then
echo bad
fi
The problem I'm running into is $? tends to hold the output from the echo program and not the bc call. Is there a way I save the output from the bc program into a variable?
EDIT:
Thanks for the quick replies. Here's another way of looking at the problem. Say I modified the script a little bit so it looks like this:
#!/bin/bash
NUM=$(echo "scale=25;$1/10" | bc)
if [ $? -ne 0 ]
then
echo bad
exit
fi
echo "$NUM"
When the user inputs a normal floating point value, it works fine:
bash script.sh 1.0
output:
.1000000000000000000000000
However, when the user enters an incorrect value, the script can't recover:
bash script.sh 1.0a
output:
(standard_in) 1: parse error
What I'm trying to do is get it to exit gracefully.

I don't see anything wrong. $NUM is supposed to hold your bc command results
see:
NUM=$(echo "scale=25;$1/10" | bc)
echo "\$? is $?"
echo "NUM is $NUM"
output
$ ./shell.sh 10
$? is 0
NUM is 1.0000000000000000000000000
another way is to use awk
NUM=$(awk -vinput="$1" 'BEGIN{printf "%.25f", input/10 }')
echo "\$? is $?"
echo "NUM is $NUM"
The other way, is to do the check of "$1" before you pass to bc. eg
shopt -s extglob
input="$1"
case "$input" in
+([0-9.]))
IFS="."; set -- $input
if [ $# -ne 2 ];then
echo "bad decimal"
else
NUM=$(echo "scale=25;$1/10" | bc )
echo "$NUM"
fi
esac
you don't have to check for $? from bc anymore

For GNU bc, an error similar to "(standard_in) 1: syntax error" will be output on stderr. You can capture this in your variable and check for it.
#!/bin/bash
NUM=$(echo "scale=25;$1/10" | bc 2>&1)
if [[ $NUM =~ error || $? -ne 0 ]]
then
echo bad
exit
fi
echo "$NUM"

Are you after the result of calculation from bc (which you store in NUM) or the status return from the system call?
As I said you have the result of calculation in $NUM:
#bctest.sh
NUM=$(echo "scale=25;$1/10" | bc)
if [ $? -ne 0 ]
then
echo bad
fi
echo "result: ", $NUM
Test:
bash ./bctest.sh 15
result: , 1.5000000000000000000000000

Related

Use of ` <` in bash script gives (standard_in) 1: syntax error

I'm getting this error as output
(standard_in) 1: syntax error
something something
something something
when i want is
something something
something something
Below is the bash script. After much trial and error checks i found the error is because of this line
done < /home/afsara/Desktop/ns2_offline/ns_code/wired.out;
What am I possibly doing wrong here?
#!/bin/bash
cd /
cd /home/afsara/Desktop/ns2_offline/ns_code/
#INPUT: output file AND number of iterations
output_file_format="tcp";
iteration_float=2.0;
end=5
iteration=$(printf %.0f $iteration_float);
r=5
while [ $r -le $end ]
do
###############################START A ROUND
l=1;thr=0.0;val=0.0
i=0
while [ $i -lt $iteration ]
do
while read val
do
dir="/home/afsara/Desktop/ns2_offline/ns_code/"
#dir=""
under="_"
all="all"
output_file="$dir$output_file_format$under$r$under$r$under$all.out"
echo -ne "Throughput: $thr " > $output_file
if [ $l == '1' ]; then
thr=$(echo "scale=5; $thr+$val/$iteration_float" | bc )
echo -ne "throughput: $val " >> $output_file
fi
echo "$val"
done < /home/afsara/Desktop/ns2_offline/ns_code/wired.out; #problem because of this
i=$(($i+1))
l=0
#################END AN ITERATION
done
r=$(($r+1))
#######################################END A ROUND
done
That's a bc error message, not a bash error message. It has nothing whatsoever to do with the done < /home/afsara/Desktop/ns2_offline/ns_code/wired.out, except perhaps that if you remove the redirection the loop has no input so its contents don't run at all.

Getting command line argument that stores in a variable

I am writing a bash script to finger the first three line of user's info.
ex:
$ ./c.sh bob unknown
Login: bob Name: Bob
Directory: /u1/h7/bob Shell: /bin/tcsh
Office: AA 044, x8361 Home Phone: 000-000-0000
unknown: no such user.
Here is my code so far
#!/bin/bash
if [ $# == 0 ]; then
echo "Usage: ./c.sh Login/Username"
exit
else
i=$#
j=1
while [ "$j" -le "$i" ]; do
finger ${$j} | head -n+3
echo
j=$(($j+1))
done
fi
instead of giving what user types for the command line arguments, ${$j} is giving me the the value of $j, any suggestion and help for how to get the login/username? I've tried $($j), $((j)), ${$j}....
The easy answer: stop using unnecessary indirection:
#!/bin/bash
if (( $# == 0 )); then
echo "Usage: ./c.sh Login/Username"
exit
else
while [[ $1 ]]; do
finger "$1" | head -n+3
echo
shift
done
fi
or…
…
for user; do # equivalent to `for user in "$#"; do`
finger "$user" | head -n+3
…
done
You could write it this way:
i=$#
j=1
while [ $j -le $i ]; do
finger "${#:j++:1}" | head -n+3
echo
done
…but you don't need to work that hard.
#!/bin/bash
if [[ $# -eq 0 ]]; then
echo "Usage: $0 Login/Username"
exit
else
for ARG in "$#"; do
finger "$ARG" | head -n 3
echo # If you want a newline
done
fi
As simple as it can be.

From bash to ksh - script throws errors but still works

I have created a simple BASH script that checks every hour for the presence of a file on a remote server. It worked error-free until I was asked to move it to a server that runs KSH.
The portion of code that errors-out is this one:
connect_string=$UID#$SERVER:$srcdir/$EVENTFILE
result=`sftp -b "$connect_string" 2>&1`
if [ echo "$result" | grep "not found" ]; then
echo "not found"
else
echo "found"
fi
These are the errors it throws:
-ksh: .[51]: [: ']' missing
grep: ]: No such file or directory
found
It still runs though and confirms that the file I am polling for is there but I need to fix this. I changed the if statement like so
if [[ echo "$result" | grep "not found" ]]; then
but it fails right away with this error
-ksh: .: syntax error: `"$result"' unexpected
What am I missing?
Your basic syntax assumptions for if are incorrect. The old [...] syntax, calls the test builtin, and [[...]] is for textual pattern matching.
As #shelter's comment, the correct syntax is:
connect_string="$UID#$SERVER:$srcdir/$EVENTFILE"
result=`sftp -b "$connect_string" 2>&1`
if echo "$result" | grep "not found" ; then
echo "not found"
else
echo "found"
fi
But this is an unnecessary use of the external grep program, you can use shell text comparison:
if [[ $result == *not\ found* ]] ; then
echo "not found"
else
echo "found"
fi
(tested with bash and ksh)
Your solution:
EXIT=`echo $?`
if [ $EXIT != 0 ]
then
...
fi
Can be improved. First, if you are going to do an arithmetic comparison, then use ((...)), not test, and I can't figure out why you have the EXIT variable:
if (( $? != 0 ))
then
...
fi
But to go full circle, you actually only need:
if sftp -b "$connect_string" 2>&1
then
...
fi
echo "$result" | grep "not found"
#capture exit status code from previous command ie grep.
if [[ $? == 0 ]]
than
echo "not found"
else
echo "found"
fi
It appears you're struggling with a basic tenet of bash/ksh control structures.
Between the if and the then keywords, the shell expects one or more commands, with
the last command in the series deciding how the if statement is processed.
The square brackets are only needed if you actually need to perform a comparison. Internally they are equivalent to the test command - if the comparison succeeds, it
results in an exit status of 0.
Example:
$ [ a == a ]
$ echo $?
0
$ [ a == b ]
$ echo $?
1
Which is equivalent to:
$ test a == a
$ echo $?
0
$ test a == b
$ echo $?
1
I changed my approach to this.
connect_string=$UID#$SERVER:$srcdir/$EVENTFILE
result=`sftp "$connect_string" 2>&1`
EXIT=`echo $?`
if [ $EXIT != 0 ]
then
echo "file not found"
exit 1
else
echo "file found"
exit 0
fi
It takes care of my problem. Thanks to all.

Find substring in shell script variable

I have a string
$VAR="I-UAT";
in my shell script code. I need a conditional statement to check if "UAT" is present in that string.
What command should I use to get either true or false boolean as output?
Or is there any other way of checking it?
What shell? Using bash:
if [[ "$VAR" =~ "UAT" ]]; then
echo "matched"
else
echo "didn't match"
fi
You can do it this way:
case "$VAR" in
*UAT*)
# code when var has UAT
;;
esac
The classic way, if you know ahead of time what string you're looking for, is a case statement:
case "$VAR" in
*UAT*) : OK;;
*) : Oops;;
esac
You can use an appropriate command in place of the : command. This will work with Bourne and Korn shells too, not just with Bash.
found=`echo $VAR | grep -c UAT`
Then test for $found non-zero.
In bash script you could use
if [ "$VAR" != "${VAR/UAT/}" ]; then
# UAT present in $VAR
fi
try with grep:
$ echo I\-UAT | grep UAT
$ echo $?
0
$ echo I\-UAT | grep UAX
$ echo $?
1
so testing
if [ $? -ne 0 ]; then
# not found
else
# found
fi
I like this a little better than using case/esac (and it'll work with non-bash shells):
#!/bin/sh
full_string="I-UAT"
substring="UAT"
if [ -z "${full_string##*$substring*}" ]; then
echo "Found substring!"
else
echo "Substring is MIA!"
fi
If the string returned is zero-length (-z), then the substring was found.

Float conditional in bash

in bash I need to compare two float numbers, one which I define in the script and the other read as paramter, for that I do:
if [[ $aff -gt 0 ]]
then
a=b
echo "xxx "$aff
#echo $CX $CY $CZ $aff
fi
but I get the error:
[[: -309.585300: syntax error: invalid arithmetic operator (error token is ".585300")
What is wrong?
Thanks
Using bc instead of awk:
float1='0.43255'
float2='0.801222'
if [[ $(echo "if (${float1} > ${float2}) 1 else 0" | bc) -eq 1 ]]; then
echo "${float1} > ${float2}"
else
echo "${float1} <= ${float2}"
fi
use awk
#!/bin/bash
num1=0.3
num2=0.2
if [ -n "$num1" -a -n "$num2" ];then
result=$(awk -vn1="$num1" -vn2="$num2" 'BEGIN{print (n1>n2)?1:0 }')
echo $result
if [ "$result" -eq 1 ];then
echo "$num1 greater than $num2"
fi
fi
Both test (which is usually linked to as [)and the bash-builtin equivalent only support integer numbers.
Use bc to check the math
a="1.21231"
b="2.22454"
c=$(echo "$a < $b" | bc)
if [ $c = '1' ]; then
echo 'a is smaller than b'
else
echo 'a is larger than b'
fi
I would use awk for that:
e=2.718281828459045
pi=3.141592653589793
if [ "yes" = "$(echo | awk "($e <= $pi) { print \"yes\"; }")" ]; then
echo "lessthanorequal"
else
echo "larger"
fi
The simplest solution is this:
f1=0.45
f2=0.33
if [[ $f1 > $f2 ]] ; then echo "f1 is greater then f2"; fi
which (on OSX) outputs:
f1 is greater then f2
Here's another example combining floating point and integer arithmetic (you need the great little perl script calc.pl that you can download from here):
dateDiff=1.9864
nObs=3
i=1
while [[ $dateDiff > 0 ]] && [ $i -le $nObs ]
do
echo "$dateDiff > 0"
dateDiff=`calc.pl $dateDiff-0.224`
i=$((i+1))
done
Which outputs
1.9864 > 0
1.7624 > 0
1.5384 > 0

Resources