decrement the variable value by 1 in shell - 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

Related

Bash script error / integer expression expected [duplicate]

To satisfy function requirements I have to retrieve a parameter which is a duty cycle (0-100% in 0.01%).
For test, I wrote something simple like :
#!/bin/bash
#
if [ "$1" -lt 0 ] || [ "$1" -gt 100 ]
then
echo "bad param"
else
echo "ok"
fi
I obtain :
root#:~# ./test.sh 11
ok
root#:~# ./test.sh 11,01
./test.sh: line 4: [: 11,01: integer expression expected
./test.sh: line 4: [: 11,01: integer expression expected
bad param
How to realise this kind of test ?
bash can only operate in integer arithmetics. If you need floats, use an external tool like bc:
if (( $( bc <<< "($1 < 0) || ($1 > 100) " ) )) ; then
...
fi
I would rather switch to a more powerful language for the whole script, though (like Perl)/

How to return passed arguments in function in shell script?

Below is code which I developed. I am passing four arguments in function and want to return variable output which I am passing to that function as a argument number four. I am getting below mentioned error.
test.sh
output='PASS'
A=(1.0,1.0,1.0,1.0,0.0,1.0)
T=(1,2,3,4,5,6)
function compare(){
j=0
for i in "$2"
do
if [[ "$3" = '1.0' ]]
then
"$4"="PASS"
echo -e "\nA:$3 and T:$i sec" >> "$1"
else
"$4"="FAIL"
echo -e "\nA:$3 and T:$i sec" >> "$1"
fi
j=$(( $j + 1 ))
done
return "$4"
}
result=compare $1 ${A[#]} ${T[#]} output
echo "result:$result"
When I call ./test.sh file.txt, I get the following error:
./test.sh: line 13: output=FAILED: command not found
./test.sh: line 18: return: output: numeric argument required
result=
Lots of problems here:
trying to assign a value to a variable value (this is the cause of the "output=FAIL" error you see)
passing arrays as first-class values
collecting output of the function
It's still not clear how A and T are related (settle down, techbros), but it looks like T contains the index you want to look up in A.
#!/bin/bash
# validate the bash version at least 4.4 here.
function compare(){
local file=$1
local -n arrayA=$2
local -n arrayT=$3
local -n resultVar=$4
resultVar=PASS
for i in "${arrayT[#]}"; do
if [[ "${arrayA[i]}" != '1.0' ]]; then
resultVar=FAIL
# normally I would return here, but you want to log the whole array
fi
printf "A:%f and T:%d sec\n" "${arrayA[i]}" "$i" >> "$file"
done
}
output='PASS'
T=(1 2 3 4 5 6)
A=([1]=1.0 1.0 1.0 1.0 0.0 1.0) # force A to start with index 1, corresponding to the first element of T
compare "$1" A T output
echo "result:$output"

expr and if in 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

Getting the line count of a file in a shell script with wc failing

my script check if the arguments are files or folders
if it is a file, he count the number of lines
after that, if the number of lines is great then 20 or less he do some instructions
the problem is in this instructionn= cat $a | wc -l
My script:
#!/usr/bin/env bash
echo 'Hello this is the test of' `date`
echo 'arguments number is ' $#
if [ $# -eq 4 ]
then
for a in $#
do
if [ -d $a ]
then
ls $a > /tmp/contenu
echo "contenu modified"
elif [ -f $a ]
then
# this instruction must set a numeric value into n
echo "my bad instruction"
n= cat $a | wc -l
echo "number of lines = " $n
# using the numeric value in a test (n must be numeric and takes the number of lines in the current file)
if [ $n -eq 0 ]
then
echo "empty file"
elif [ $n -gt 20 ]
then
echo ` head -n 10 $a `
else
cat $a
fi
else
echo "no file or directory found"
fi
done
else
echo "args number must be 4"
fi
This is the output of the execution of the incorrect instruction
my bad instruction
5
number of lines =
ExamenEx2.sh: line 19: [: -eq : opérateur unaire attendu
The line n= cat $a | wc -l is an offending instruction. Always remember that bash shell scripting is extremely case-sensitive. Your command is interpreted by the shell as having to run two separate commands
n= cat $a | wc -l
#^^ ^^^^^^^^^^^^^^
#1 2
The first part just stores an empty string to the variable n and the next prints the line count of the file stored in variable a. Notice that the shell does not throw errors for this. Because it is not violating the syntax (just the semantics are wrong). But the line count is never assigned to the variable n.
The error is seen when the conditional if [ $n -eq 0 ] is hit when you are doing a comparison with an empty variable on the LHS.
You wanted to run a command and store its output, you need command-substitution($(..)) for that. Assuming the $a contains a name of a file just do
n=$(wc -l < "$a")
Note, that I've removed the useless cat usage and piping it to wc. But wc can read from an input stream directly.
Also note that you have multiple bad practices in your script. Remember to do the following
Always double-quote the shell variables - "$#", "$#", [ -f "$a" ], [ -d "$a" ]
Don't use the `` for command-substitution, because it is not easily nestable and you might have issues related to quoting also.
You can use conditional expression [[ if you are sure if the script is running under bash in which a variable containing spaces can be used without quoting on the LHS

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.

Resources