How can I do division with variables in a Linux shell? - linux

When I run commands in my shell as below, it returns an expr: non-integer argument error. Can someone please explain this to me?
$ x=20
$ y=5
$ expr x / y
expr: non-integer argument

Those variables are shell variables. To expand them as parameters to another program (ie expr), you need to use the $ prefix:
expr $x / $y
The reason it complained is because it thought you were trying to operate on alphabetic characters (ie non-integer)
If you are using the Bash shell, you can achieve the same result using expression syntax:
echo $((x / y))
Or:
z=$((x / y))
echo $z

I believe it was already mentioned in other threads:
calc(){ awk "BEGIN { print "$*" }"; }
then you can simply type :
calc 7.5/3.2
2.34375
In your case it will be:
x=20; y=3;
calc $x/$y
or if you prefer, add this as a separate script and make it available in $PATH so you will always have it in your local shell:
#!/bin/bash
calc(){ awk "BEGIN { print $* }"; }

Why not use let; I find it much easier.
Here's an example you may find useful:
start=`date +%s`
# ... do something that takes a while ...
sleep 71
end=`date +%s`
let deltatime=end-start
let hours=deltatime/3600
let minutes=(deltatime/60)%60
let seconds=deltatime%60
printf "Time spent: %d:%02d:%02d\n" $hours $minutes $seconds
Another simple example - calculate number of days since 1970:
let days=$(date +%s)/86400

Referencing Bash Variables Requires Parameter Expansion
The default shell on most Linux distributions is Bash. In Bash, variables must use a dollar sign prefix for parameter expansion. For example:
x=20
y=5
expr $x / $y
Of course, Bash also has arithmetic operators and a special arithmetic expansion syntax, so there's no need to invoke the expr binary as a separate process. You can let the shell do all the work like this:
x=20; y=5
echo $((x / y))

To get the numbers after decimal point, you can do this:-
read num1 num2
div=`echo $num1 / $num2 | bc -l`
echo $div

let's suppose
x=50
y=5
then
z=$((x/y))
this will work properly .
But if you want to use / operator in case statements than it can't resolve it.
In that case use simple strings like div or devide or something else.
See the code

Related

Understand the use of braces and parenthesis in if

In some of the code I was going through I found that if was using braces {} for someplace and parenthesis (()) for some other. Can someone tell me the exact meaning and where to use which one?
if [ "$1" = "--help" ]
if (( $# != 3 ))
The bracket [ is a built-in command of the shell; you can also call it as test:
if [ a = b ]
then ...
equals:
if test a = b
then ...
The syntax of the test command is rather text-oriented (see the bash man page for details at chapter CONDITIONAL EXPRESSIONS).
The braces {…} are shell syntax and used for grouping commands (without creating a subshell):
{ date; ls; echo $$; } > 1>&2
This will execute date, ls, and echo $$ and redirect all their output to stderr.
The parenthesis (…) are shell-syntax and used for creating a subshell:
(date; ls; echo $$) > 1>&2
Like above but the PID ($$) given out is that of the subshell.
The difference between grouping and subshell is delicate (and out of scope here).
The doubled brackets [[…]] are shell syntax but otherwise behave like the single bracket [ command. The only difference is for using < etc. for string comparison and locale support.
The doubled parentheses ((…)) are equivalent to using the let builtin shell command. They basically allow number-oriented expressions to be evaluated (ARITHMETIC EVALUATION). < and > sort numerically (instead of lexicographically) etc. Also, in some constructs like for ((i=0; i<10; i++)); do echo "$i"; done they are used as a fixed syntax.
Dollar-parenthesis $(…) result in the output of the command they enclose:
echo "$(date)" # a complicated way to execute date
Dollar-brackets $[…] are deprecated and should be replaced by dollar-double-parentheses.
Dollar-double-parentheses $((…)) result in the value of the numerical expression they enclose:
echo "$((4 + 3 * 2))" # should print 10
Dollar-braces ${…} result in the variable expansion they enclose. In the simplest case this is just a variable name, then they evaluate to the variable value:
a=foo
echo "${a}" # prints foo
This can (and often is) abbreviated by stripping the braces: $a
But it also can be more complex like ${a:-"today is $(date)"}. See Parameter Expansion in the bash man page for details.
Redirection-parenthesis <(…) and >(…) create a subprocess, a file descriptor its output/input is associated with, and a pseudo file associated with that descriptor. It can be used to pass the output of a program as a seeming file to another program: diff <(sleep 1; date) <(sleep 2; date)

ksh - var = $var + 1 returns 1+1 string

My code:
RETVAL1=-1
if [ $RETVAL1 -le 0 ] ; then
RETVAL1=$RETVAL1+1
print "RETVAL1: $RETVAL1"
fi
And it prints RETVAL1: -1+1
Any idea how to repair it please?
To perform arithmetic operation, use the let command: let RETVAL1=RETVAL1+1
Moreover, enclosing the expression between $(( and )) would also interpret it as an arithmetic operation. echo $((RETVAL+1))
Use the let command. This command performs arithmic operations. The + operator performs string addition.
Use it like this:
let RETVAL1=RETVAL1+1
You can also use the expr command for more general expressions.
One way:
((RETVAL1=RETVAL1+1))
Shell variables don't work like variables in most programming languages. If you want to add 1 to an integer stored in a variable, you'll need an arithmetic expression. I'm no ksh wizard, but the usual Bourne-derived-shell arithmetic expression syntax is:
RETVAL1=$((RETVAL1 + 1))
or
((RETVAL1 = RETVAL1 + 1))

How to compare two floating-point values in shell script

I had to do a division in shell script and the best way was:
result1=`echo "scale=3; ($var1 / $total) * 100"| bc -l`
result2=`echo "scale=3; ($var2 / $total) * 100"| bc -l`
but I want to compare the values of $result1 and $result2
Using if test $result1 -lt $result2 or if [ $result1 -gt $result2 ] didn't work :(
Any idea how to do that?
You can compare floating-point numbers using expr(1):
: nr#yorkie 3724 ; expr 3.1 '<' 3.3
1
: nr#yorkie 3725 ; expr 3.1 '<' 3.09
0
You can also have bc do the comparisons as well as the calculations:
if [ "$(echo $result1 '<' $result2 | bc -l)" -eq 1 ];then ... fi
Finally, ksh93 can do arithmetic evaluation $(($result1 < $result2)) with floating-point numbers, although bash cannot.
note that you've gotta be a bit careful when dealing with floating point numbers and if you are testing for equality you really want to decide on some precision and then compare using that. Something like:
if (abs(x1-x2) < 0.0001) then equal # pseudo-code
the reason being that with computers we're dealing with limited-precision binary fractions not true mathematical reals. Limiting the precision in bc with the scale=3 will have this effect.
I'd also advise against trying to do this stuff in shell script. It's not that you can't do it but you'll have to fork off lots of little sub commands to do the tricky bits and that's slow to execute and generally a pain to write - you spend most of your time trying to get the shell to do what you want rather than writing the code you really want. Drop into a more sophisticated scripting language instead; my language of choice is perl but there are others. like this...
echo $var1 $var2 $total | perl -ne 'my ($var1, $var2, $tot) = split /\s+/; if ($var1/$tot == $var2/$tot) { print "equal\n"; }'
also note that you're dividing by the same value ($total in your question) so the whole comparison can be done against the numerators (var1 and var2) provided $total is positive
Posting a new answer since I cannot yet comment...
#Norman Ramsey's answer is not quite accurate:
expr will perform an integer or string comparison, not a floating-point comparison.
Here's what the man page says:
expr1 {=, >, >=, <, <=, !=} expr2
Return the results of integer comparison if both arguments are integers; otherwise, returns the results of string comparison using the locale-specific collation sequence.
(just try expr 8.9 '<' 10 and get 0 where it should be 1).
bcworks great, but isn't always installed.
So another alternative is using perl -e:
perl -e 'print expression' will print 1 if expression is true and nothing (empty string) otherwise.
e.g. perl -e 'print 8.9 < 10' - prints "1", while perl -e 'print 2>4' prints nothing.
And when used in if statement:
if [ $(perl -e "print $result1 < $result2") ];then ... fi

KornShell Printf - Padding a string

I'm attempting to write a KornShell (ksh) function that uses printf to pad a string to a certain width.
Examples:
Call
padSpaces Hello 10
Output
'Hello '
I currently have:
padSpaces(){
WIDTH=$2
FORMAT="%-${WIDTH}.${WIDTH}s"
printf $FORMAT $1
}
Edit: This seems to be working, in and of itself, but when I assign this in the script it seems to lose all but the first space.
TEXT=`padSpaces "TEST" 10`
TEXT="${TEXT}A"
echo ${TEXT}
Output:
TEST A
I'm also open to suggestions that don't use printf. What I'm really trying to get at is a way to make a fixed width file from ksh.
Your function works fine for me. Your assignment won't work with spaces around the equal sign. It should be:
SOME_STRING=$(padSpaces TEST 10)
I took the liberty of replacing the backticks, too.
You don't show how you are using the variable or how you obtain the output you showed. However, your problem may be that you need to quote your variables. Here's a demonstration:
$ SOME_STRING=$(padSpaces TEST 10)
$ sq=\'
$ echo $sq$SOME_STRING$sq
'TEST '
$ echo "$sq$SOME_STRING$sq"
'TEST '
Are you aware that you define a function called padSpaces, yet call one named padString? Anyway, try this:
padString() {
WIDTH=$2
FORMAT="%-${WIDTH}s"
printf $FORMAT $1
}
Or, the more compact:
padString() {
printf "%-${2}s" $1
}
The minus sign tells printf to left align (instead of the default right alignment). As the manpage states about the command printf format [ arg ... ],
The arguments arg are printed on standard output in accordance with the
ANSI-C formatting rules associated with the format string format.
(I just installed ksh to test this code; it works on my machineTM.)

How can I pass a complete argument list in bash while keeping mulitword arguments together?

I am having some issues with word-splitting in bash variable expansion. I want to be able to store an argument list in a variable and run it, but any quoted multiword arguments aren't evaluating how I expected them to.
I'll explain my problem with an example. Lets say I had a function decho that printed each positional parameter on it's own line:
#!/bin/bash -u
while [ $# -gt 0 ]; do
echo $1
shift
done
Ok, if I go decho a b "c d" I get:
[~]$ decho a b "c d"
a
b
c d
Which is what I expect and want. But on the other hand if I get the arguments list from a variable I get this:
[~]$ args='a b "c d"'
[~]$ decho $args
a
b
"c
d"
Which is not what I want. I can go:
[~]$ echo decho $args | bash
a
b
c d
But that seems a little clunky. Is there a better way to make the expansion of $args in decho $args be word-split the way I expected?
You can use:
eval decho $args
You can move the eval inside the script:
#!/bin/bash -u
eval set -- $*
for i;
do
echo $i;
done
Now you can do:
$ args='a b "c d"'
$ decho $args
a
b
c d
but you'll have to quote the arguments if you pass them on the CL:
$ decho 'a b "c d"'
a
b
c d
Have you tried:
for arg in "$#"
do
echo "arg $i:$arg:"
let "i+=1"
done
Should yield something like:
arg 1: a
arg 2: c d
in your case.
Straight from memory, no guarantee :-)
hmmm.. eval decho $args works too:
[~]$ eval decho $args
a
b
c d
And I may be able to do something with bash arrays using "${array[#]}" (which works like "$#"), but then I would have to write code to load the array, which would be a pain.
It is fundamentally flawed to attempt to pass an argument list stored in a variable, to a command.
Presumably, if you have code somewhere to create a variable containing the intended args. for a command, then you can change it to instead store the args into an array variable:
decho_argv=(a b 'c d') # <-- easy!
Then, rather than changing the command "decho" to accommodate the args taken from a plain variable (which will break its ability to handle normal args) you can do:
decho "${decho_argv[#]}" # USE DOUBLE QUOTES!!!
However, if you are the situation where you are trying to take arbitrary input which is expected to be string fields corresponding to intended command positional arguments, and you want to pass those arguments to a command, then you should instead of using a variable, read the data into an array.
Note that suggestions which offer the use of eval to set positional parameters with the contents of an ordinary variable are extremely dangerous.
Because, exposing the contents of a variable to the quote-removal and word-splitting on the command-line affords no way to protect against shell metachars in the string in the variable from causing havoc.
E.g., imagine in the following example if the word "man" was replaced with the two words "rm" and "-rf" and the final arg word was "*":
Do Not Do This:
> args='arg1 ; man arg4'
> eval set -- $args
No manual entry for arg4
> eval set -- "$args" # This is no better
No manual entry for arg4
> eval "set -- $args" # Still hopeless
No manual entry for arg4
> eval "set -- '$args'" # making it safe also makes it not work at all!
> echo "$1"
arg1 ; man arg4

Resources