I'm studying shell scripting and don't understand the difference between -eq and ==, -lt and <, -gt and >, so on.
I'm trying to write a while loop printing out from 0 to 9
num=0
while [ $num -lt 10 ]; do
echo "$num"
((num++))
done
This code works but when I change -lt to <, it says No such file or directory.
num=0
while [ $num < 10 ]; do
echo "$num"
((num++))
done
What is the issue with < here? Do I always have to go for -lt in while loops? Is there a general way to do while loops? Appreciate if you can help.
Shell scripting has been always different when it comes to syntax.
so when you say -lt it means less than (<).so when you write your code it works totally fine
while [ $num -lt 10 ]; do
echo "$num"
((num++))
done
But when you use < this in the shell script it is used to read input from file or directory. So here in your case, it will search for the name of the file which is inside the $num variable
In simple words
-lt is Less than which is used for condition checking
< is used for Reading input from the files.
In commandline
< means read input from file
for example
grep "myname" < data.txt
also,
> redirect output to a file
for example ls > lists.txt
when executing $num < 10
it checking for a file named 10
The command [ specifies that -lt should be used to compare two integers. Expecting < to do anything useful is simply wishful.
Coincidentally, the character < is a metacharacter in bash used for input redirection. The error you get is due to the file 10 not existing in your cwd.
You can use '<' with double parentheses (integers) or curly brace (strings)
num=0
while (( $num < 10 )); do
echo "$num"
((num++))
done
and for strings
str="a"
while [[ $str < "aaaaa" ]]; do
echo "$str"
str+="a"
done
Related
Below my script for up to n prime numbers. When I run it, it always shows an error that command not found in line 12 and 18 both. What am I doing wrong?
clear
echo"enter the number upto which you want prime numbers : "
read n
for((i=1;i<=n;i++))
do
flag=0
for((j=2;j<i;j++))
do
if [expr $i % $j-eq 0]
then
flag=1
fi
done
if [$flag-eq 0]
then
echo $i
fi
done
As pointed out in comments, you must use spaces around [ and ], as well as the comparison operators. Even more safe when using [ and ] is quoting your variables to avoid word splitting (not actually required in this specific case, though).
Additionally, you want to compare the output of expr to 0, so you have to use command substitution:
if [ $(expr "$i" % "$j") -eq 0 ]
and
if [ "$flag" -eq 0 ]
Since you're using Bash, you can use the (( )) compound command:
if (( i % j == 0 ))
and
if (( flag == 0 ))
No expr needed, no command substitution, no quoting required, no $ required, and the comparison operators have their "normal", expected meaning.
There are a number of syntax errors other than the brackets of if statement. Kindly go through the piece of code below. I have checked it running on my system.
#!/bin/sh
echo "enter the number upto which you want prime numbers : "
read n
for((i=1;i<=n;i++))
do
flag=0
for((j=2;j<i;j++))
do
if [ `expr $i % $j` -eq 0 ]
then flag=1
fi
done
if [ $flag -eq 0 ]
then echo $i
fi
done
I wrote a bash script that performs a curl call only during business hours. For some reason, the hourly comparison fails when I add an "-a" operator (and for some reason my bash does not recognize "&&").
Though the script is much larger, here is the relevant piece:
HOUR=`date +%k`
if [ $HOUR > 7 -a $HOUR < 17 ];
then
//do sync
fi
The script gives me the error:
./tracksync: (last line): Cannot open (line number): No such file
However, this comparison does not fail:
if [ $DAY != "SUNDAY" -a $HOUR > 7 ];
then
//do sync
fi
Is my syntax wrong or is this a problem with my bash?
You cannot use < and > in bash scripts as such. Use -lt and -gt for that:
if [ $HOUR -gt 7 -a $HOUR -lt 17 ]
< and > are used by the shell to perform redirection of stdin or stdout.
The comparison that you say is working is actually creating a file named 7 in the current directory.
As for &&, that also has a special meaning for the shell and is used for creating an "AND list" of commands.
The best documentation for all these: man bash (and man test for details on comparison operators)
There are a few answers here but none of them recommend actual numerical context.
Here is how to do it in bash:
if (( hour > 7 && hour < 17 )); then
...
fi
Note that "$" is not needed to expand variables in numerical context.
I suggest you use quotes around variable references and "standard" operators:
if [ "$HOUR" -gt 7 -a "$HOUR" -lt 17 ]; ...; fi
Try using [[ instead, because it is safer and has more features. Also use -gt and -lt for numeric comparison.
if [[ $HOUR -gt 7 && $HOUR -lt 17 ]]
then
# do something
fi
I wrote a shell script into a file named test.sh, and the code as following:
echo "start"
declare -i i=1
declare -i MAX=99999999;
while ($i < $MAX)
do
# do something
let ++i;
done
echo "done"
The result is:
start
test.sh: line 3: 99999999: No such file or directory
done
I run it on Max os Yosemite 10.10.2 and the terminal is bash.
Where am i wrong?
Okay the problem is with ($i < $MAX) it will not work in bash. In your case it taking < as redirect operator so it is treating < $MAX as you are giving $MAX file redirection to any loop. SO it is treating $MAX as file. That's why the error.
use
[ "$i" -lt "$MAX" ] ### generic POSIX number comparison syntax
or
[[ $i -lt $MAX ]] ### extended (bash/ksh/zsh) number comparison syntax
or
[[ $i < $MAX ]] ### extended string comparison syntax (may give wrong result for numbers)
or
(( i < MAX )) ### extended number comparison syntax
This is my bash scripting code so I want to know How to Rewrite the below Bash script using a “for” loop instead of the “while” loop.
#!/bin/bash
if [ $# -gt 0 ]; then
a=0;
if [ -f RandNos ]; then
rm RandNos;
fi
while [ $a -lt $1 ]
do
a='expr $a + 1';
myrand=$RANDOM;
if [ "$2" "1"]; then
echo "No. $a ==> $myrand";
fi
echo $myrand>>RandNos
done
else
echo "please use with an argument..."
fi
Thanks.
The short of it: for counter-based loops, use the C-like form of the for loop:
for (( a = 0; a < $1; a++ )); do
# ... use $a
done
(This replaces while [ $a -lt $1 ]; do a='expr $a + 1' ...; done.)
See below for more on the rules that apply inside (( ... )).
As for the rest of your code:
Conditional [ "$2" "1"] is broken: it's missing the mandatory space before ]
With that fixed, it'll only work if $2 expands to a unary test operator such as -n.
Perhaps you meant if [[ -z $myrand ]]; then, to check if $RANDOM resulted in a nonempty string?
a='expr $a + 1' - which you don't need anymore with the for loop - doesn't actually invoke expr, because you're using single quotes - you'd need backticks (`) instead, or, preferably, the modern equivalent: $(expr $a + 1). However, with arithmetic evaluation, this could be simplified to (( ++a )).
[ ... ] conditionals work in bash, but they're provided for POSIX compatibility - use [[ ... ]] as the bash-specific alternative, which is more robust, has more features, and is faster.
bash statements only need terminating with ; if you place multiple on a single line
Note that bash considers do ... and then ... separate statements, hence you often see if ...; then and for ...; do.
In general, I encourage you to syntax-check your shell code at http://shellcheck.net - it's a great tool for detecting syntax problems.
Note how different rules apply inside (( ... )) compared to elsewhere in bash:
spaces around the = in the variable assignment are allowed.
referencing a variable without the $ prefix (a++) is allowed.
< performs numerical comparison (whereas inside [[ ... ]] it's lexical) -i.e., it's the more natural equivalent to -lt inside [ ... ] or [[ ... ]].
several other mathematical and even bit-wise operators are supported
...
All these different rules apply when bash operates in an arithmetic context, which applies to (( ... )), $(( ... )), array subscripts, and other cases.
For all the rules, run man bash and read the ARITHMETIC EVALUATION section.
Simply rewriting it with a for loop results in:
#!/bin/bash
if [ $# -gt 0 ]; then
if [ -f RandNos ]; then
rm RandNos;
fi
lim=$(expr $1 - 1)
as=$(seq 0 $lim)
for a in $as
do
a='expr $a + 1';
myrand=$RANDOM;
if [ "$2" "1"]; then # <- Caveat: conditional is BROKEN
echo "No. $a ==> $myrand";
fi
echo $myrand>>RandNos
done
else
echo "please use with an argument..."
fi
But there are several things wrong with the script anyhow. Like the last if statement.
if [ $# -lt 1 ];then
echo "First argument must be number".
exit 1;
fi
for a in `seq $1`
do
...
done
Several things can be improved:
#!/bin/bash
if (( $# )); then # anything but 0 is true
rm -f RandNos # remove if existing, otherwise fail silently
for ((a=0; a<$1; a++)); do
myrand=$RANDOM
# what is the intention here?
(( $2 > 1 )) && echo "No. $a ==> $myrand"
echo "$myrand" >> RandNos
done
else
echo "please use with an argument..."
fi
not sure what your intention was with the [ "$2" "1" ] expression. it is probably not what I made from it.
for ((a=1; a<=$1; a++)); do
may reflect your intended logic better, as you use $a for output only after incrementing it. as pointed out and corrected by #mklement0
!/bin/bash
if [ $# -gt 0 ]; then
a=0;
if [ -f RandNos ]; then
rm RandNos;
fi
for (( i=$a; i<$1; i++ ))
do
myrand=$RANDOM;
if [ "$2" = "1" ]; then
echo "No. $a ==> $myrand";
fi
echo $myrand >> RandNos
done
else
echo "please use with an argument..."
fi
#!/bin/bash
local dept=0
while [ $n < 5 ]
do
echo $n
$n++
done
this code returns error 7: cannot open 5: No such file
Where should I change?
You should use $n -lt 5. Bash reads the < there as redirection, so it tries to open a file named 5 and feed its contents to a command named $n
This works for me:
#!/bin/bash
n=0
while [ $n -lt 5 ]
do
echo $n
let n=$n+1
done
#!/bin/bash
n=0
while [[ "$n" < 5 ]]
do
echo $n
((n++))
done
~
Most portable (POSIX sh-compliant) way is:
#!/bin/sh -ef
n=0
while [ "$n" -lt 5 ]; do
echo "$n"
n=$(($n + 1))
done
Note:
"$n" - quotes around $n help against crashing with missing operand error, if n is not initialized.
[ (AKA test) and -lt - is a safe and fairly portable way to check for simple arithmetic clauses.
$((...)) is a safe and portable way to do arithmetic expansion (i.e. running calculations); note $n inside this expansion - while bash would allow you to use just n, the standard and portable way is to use $n.