expr and if in bash - bash

What's wrong with this code? If I input the number 25, it output failed instead of lol. Am I missing something?
read -p "Enter number : " num
if [ `expr $num > 5` ]
then
echo "lol"
else
echo "failed"
fi

The code
if [ `expr $num > 5` ]
actually does not do want you think. It will run expr $num > 5, so evaluate parameter and redirect the out to a file named 5 ("put 25 in a file named 5") and if will evaluate the return code of the previous expression.
If the code if meant to check evaluate if a number is bigger than 5, replace
if [ `expr $num > 5` ]
with
[ "$num" -gt 5 ]
-gt stands for greater than

#babtistemm's answer gives you the suggested solution but in case you insist on using (the bit oldish) expr for some reason:
read -p "Enter number : " num
if expr "$num" '>' 5 >/dev/null
then
echo "lol"
else
echo "failed"
fi
Notes:
You need to quote > so that the shell does not interpret it as redirecting the stdout. You could also use \>.
It is good practice to add double quotes to $num as well, so that expr will interpret it as one expression, thus limiting the chances of a very bad bug or a malicious user hacking your program. (Best would be to do a sanity-check on $num before using it, e.g. checking if it is an integer.)
This solution necessitates calling a new process, expr, which costs a lot more resource from the OS than using the test shell command only.
If you omit the >/dev/null, you will also get a 0 or 1 printed (meaning false or true), the stdout of expr. But independently of that, expr sets its exit status, $? according to the result of the expression, which is tested then by the if. (A side remark: if you try to echo $? after calling expr, it may come at a surprise first that $? = 0 means true/success as exit status, and $? != 0 means false by convention.)

you can use an arithmetic expression in bash:
if (( num > 5 )); then ...
In the manual, see https://www.gnu.org/software/bash/manual/bash.html#Conditional-Constructs

Same in a short line:
read -p 'Enter a number: ' num
(( num > 5 )) && echo lol || echo fail
could be condensed:
read -p 'Enter a number: ' num;((num>5))&&echo lol||echo fail
This syntaxe will work if first command success!
read -p 'Enter a number: ' num
((num > 5)) && {
echo lol
/bin/wrongcommand
:
} || echo fail
Could output:
lol
bash: /bin/wrongcommand: No such file or directory
But no fail because of : (is an alias for true) will alway success.
You could of course group command for fail:
read -p 'Enter a number: ' num
((num > 5)) && {
echo lol
/bin/wrongcommand
:
} || {
echo fail
other command
}
Could be written:
read -p 'Enter a number: ' num;((num>5))&&{ echo lol;/bin/wrongcommand;:;}||{ echo fail;other command;}
You could group commands between {  and ;} (care about the space after first {!)
Enter a number: 4
fail
bash: other: command not found
Enter a number: 7
lol
bash: /bin/wrongcommand: No such file or directory

Related

Bash Script stuck looping

I'm trying to write a script that runs another script which fails rarely until it fails.
Here is the script that fails rarely:
#!/usr/bin/bash
n=$(( RANDOM % 100 ))
if (( n == 42 )) ; then
echo "$n Something went wrong"
>&2 echo "The error was using magic numbers"
exit 1
fi
echo "$n Everything went according to plan"
Here is the script that should run the previous script until it fails:
#!/usr/bin/bash
script_path="/tmp/missing/l2q3.sh"
found=0
counter=0
while (( $found == 0 )); do
output=(bash $script_path)
if (( $output == 42 Something went wrong )); then
found=1
fi
((counter++))
if (( $found == 1 )); then
echo "Number 42 was found after $counter tries"
fi
done
when I try running the second script I get stuck in an infinite loop saying there is a syntax error on line 11 and that something is wrong with 42 Something went wrong. I've tried with "42 Something went wrong" aswell and still stuck in a loop.
The form (( )) is arithemetic only, so you cannot test a string inside.
To test a string, you have to use the [[ ]] version:
[[ $output == "42 Something went wrong" ]] && echo ok
ok
You can use the program execution as the test for a while/until/if (etc.)
Assuming your script returns a valid 0 error code on success, and nonzero on any other circumstance, then -
$: cat tst
#!/bin/bash
trap 'rm -fr $tmp' EXIT
tmp=$(mktemp)
while /tmp/missing/l2q3.sh >$tmp; do let ++ctr; done
grep -q "^42 Something went wrong" $tmp &&
echo "Number 42 was found after $ctr tries"
In use:
$: ./tst
The error was using magic numbers
Number 42 was found after 229 tries
here are 3 steps to move forward.
add a return value at the end of the first script
exit 0
make your first script has executable rights
$ chmod a+x /tmp/missing/12q3.sh
instead of while loop you may use until, which would run until it returns success i.e. 0
until /tmp/missing/l2q3.sh; do ((counter++)) done
for other if statements please use square brackets [ single or double [[.

decrement the variable value by 1 in shell

what is the correct syntax to decrement/decrease the variable value by 1 in while loop using /bin/sh and not using /bin/bash script
I used following but does not work
a=15
((a=a-1)) // not working
((a--)) // not working
EDIT 1
i=0
a=[]
b=15
while [ $a == [] ] && [ "$i" -le 15 ]
do
echo " Waiting ."
sleep 60s
((i=i+1))
b=`expr $b- 1`
a=`some command`
done
still getting following error
sh: was: unknown operand /bin/sh: exit: line 186: Illegal number: -1
Arithmetic substitution is spelled $(( )) and expands to the result. If you just need the side effect (e.g. increment), use it in a null command:
a=15
: $((--a))
echo $a
Note that shell arithmetic is integer only.
$ a=15
$ a=`expr $a - 1`
$ echo $a
14

read -p inside the function in bash script

I am trying to write the function including read -p, however, for some reason the read -p always show first before other command, although other commands are before read -p. Here is my code:
function try {
temp=10
echo "$temp"
while [[ $temp -gt 0 ]]
do
read -p "what num do you want?" num
echo "$num"
temp=$((temp - num))
echo $temp
done
}
run=`try`
echo "$run"
As the above code, I expected to see value of temp before statement "what num do you want?". However, here what I got:
what num do you want?5
what num do you want?5
10
5
5
5
0
Can anyone help me to solve my problem. Thanks in advance
Duplicate each line in your function which contains echo and append >&2 to the new lines to redirect stdout to stderr.

Absolute value of a number

I want to take the absolute of a number by the following code in bash:
#!/bin/bash
echo "Enter the first file name: "
read first
echo "Enter the second file name: "
read second
s1=$(stat --format=%s "$first")
s2=$(stat -c '%s' "$second")
res= expr $s2 - $s1
if [ "$res" -lt 0 ]
then
res=$res \* -1
fi
echo $res
Now the problem I am facing is in the if statement, no matter what I changes it always goes in the if, I tried to put [[ ]] around the statement but nothing.
Here is the error:
./p6.sh: line 13: [: : integer expression expected
You might just take ${var#-}.
${var#Pattern} Remove from $var the shortest part of $Pattern that matches the front end of $var. tdlp
Example:
s2=5; s1=4
s3=$((s1-s2))
echo $s3
-1
echo ${s3#-}
1
$ s2=5 s1=4
$ echo $s2 $s1
5 4
$ res= expr $s2 - $s1
1
$ echo $res
What's actually happening on the fourth line is that res is being set to nothing and exported for the expr command. Thus, when you run [ "$res" -lt 0 ] res is expanding to nothing and you see the error.
You could just use an arithmetic expression:
$ (( res=s2-s1 ))
$ echo $res
1
Arithmetic context guarantees the result will be an integer, so even if all your terms are undefined to begin with, you will get an integer result (namely zero).
$ (( res = whoknows - whocares )); echo $res
0
Alternatively, you can tell the shell that res is an integer by declaring it as such:
$ declare -i res
$ res=s2-s1
The interesting thing here is that the right hand side of an assignment is treated in arithmetic context, so you don't need the $ for the expansions.
I know this thread is WAY old at this point, but I wanted to share a function I wrote that could help with this:
abs() {
[[ $[ $# ] -lt 0 ]] && echo "$[ ($#) * -1 ]" || echo "$[ $# ]"
}
This will take any mathematical/numeric expression as an argument and return the absolute value. For instance: abs -4 => 4 or abs 5-8 => 3
A workaround: try to eliminate the minus sign.
with sed
x=-12
x=$( sed "s/-//" <<< $x )
echo $x
12
Checking the first character with parameter expansion
x=-12
[[ ${x:0:1} = '-' ]] && x=${x:1} || :
echo $x
12
This syntax is a ternary opeartor. The colon ':' is the do-nothing instruction.
or substitute the '-' sign with nothing (again parameter expansion)
x=-12
echo ${x/-/}
12
Personally, scripting bash appears easier to me when I think string-first.
I translated this solution to bash. I like it more than the accepted string manipulation method or other conditionals because it keeps the abs() process inside the mathematical section
abs_x=$(( x * ((x>0) - (x<0)) ))
x=-3
abs_x= -3 * (0-1) = 3
x=4
abs_x= 4 * (1-0) = 4
For the purist, assuming bash and a relatively recent one (I tested on 4.2 and 5.1):
abs() {
declare -i _value
_value=$1
(( _value < 0 )) && _value=$(( _value * -1 ))
printf "%d\n" $_value
}
If you don't care about the math and only the result matters, you may use
echo $res | awk -F- '{print $NF}'
The simplest solution:
res="${res/#-}"
Deletes only one / occurrence if - is at the first # character.

If statement in shell

I'm making a script that executes a file a certain amount of times, then if the file executes properly, it generates another file, and this last file I need to store it in a directory. Example: I execute it like this: ./shell 5 DIR
Then, the crpit shell executes another file called simulation 5 times and it creates a directory and an output file for each time the file simulation was executed correctly.
The thing is, I have to put an if statement for the parameters they send when they execute the file, and I don't know how to, here's the code I have:
#!/bin/bash
if [ $# == 3 || $# == 2 ]; then
c=0
i=0
e=0
cont=0
while [ $c -le $0 ]
do
./simula cont RES
e = $?
if[ e == 0 ]; then
if[ $# == 3 ]; then
chmod RES $2
mkdir $1$c
mv RES $1$c/.
(( c++ ))
else
(( i++ ))
(( cont++ ))
done
echo Shan generat $c simulacions correctes.
echo Hi ha hagut $i simulacions erronies.
else
echo Nombre de parametres incorrecte: $#.
fi
So I must have either 2 or 3 paremeters when executing the file called shell, and thats why i have that if in the second line of code, however it keeps giving me an error:
./shell: line 12: syntax error near unexpected token `then'
./shell: line 12: ` if[ e == 0 ]; then'
I'm new to scripts and I have no idea what am I doing wrong, any clues?
Spacing is very important in shell scripts, it's not like programming languages where
if(x>y) and if ( x > y ) are the same thing.
In particular, if and [ are two different commands:
if[ e == 0 ] needs to be if [ e == 0 ]
More precisely, if is a shell keyword as are then, else, and fi
[ is either a shell builtin or an external program (or both) depending on your shell and is an alias for test. You can find out more with the which and type commands.
$ type [
[ is a shell builtin
$ which [
/bin/[
$ type fi
fi is a shell keyword
$ which fi
$
Does it works if you try if [ $e == 0 ]; instead of if[ e == 0 ];
When assigning variables, Bash doesn't allow leaving spaces in between the = sign like is common practice in other languages.
There must be a space separating reserved words like if.
So if [ $# == 3 ] is valid where if[ $# == 3 ] is not.
In Bash, it's important to use $ in front of variable names when you're calling the variable after it's been assigned.
So [ e == 0 ] would test to see if the literal string e is the same as the string 0. To test if the string assigned to variable e is the same as the string 0, use [ $e == 0 ] .
When testing integers, you can use -eq instead of ==. See man test for more information.
Also, $0 expands to the name of the script file, so you're while loop will never end if the script name is not 0 as the script name will not change while your script is running.

Resources