shell script - why code executing in infinite loop [duplicate] - shell

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

Related

What is the difference between -lt and < in shell?

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

Parameters work properly when remove their quoting

I am puzzled about the verbose of quoting in the script. Take an example from the instruction I followed:
min_val=1
max_val=100
int=50
if [[ "$int" =~ ^-?[0-9]+$ ]]; then
if [[ "$int" -ge "$min_val" && "$int" -le "$max_val" ]]; then
echo "$int is within $min_val to $max_val."
else
echo "$int is out of range."
fi
else
echo "int is not an integer." >&2
exit 1
fi
Run it and come by:
$ bash test_integer3.sh
50 is within 1 to 100.
When I removed all the quoting in testing:
if [[ $int =~ ^-?[0-9]+$ ]]; then
if [[ $int -ge $min_val && $int -le $max_val ]]; then
echo "$int is within $min_val to $max_val."
else
echo "$int is out of range."
fi
else
echo "int is not an integer." >&2
exit 1
fi
It's still working properly.
$ bash test_integer3.sh
50 is within 1 to 100.
Why should live with the habit of writing redundant quoting?
The real problem comes when you start to use [ command over [[ in your scripts. [[ is bash's improvement to the [ command. It has several enhancements that make it a better choice to write scripts targeting bash.
One such improvement would be that you no longer have to quote variables because [[ handles empty strings and strings with white-space more intuitively. For example consider your script written with [ for the un-quoted case and for discussions sake, one of your variables is empty
#!/usr/bin/env bash
min_val=
max_val=
int=50
if [[ $int =~ ^-?[0-9]+$ ]]; then
if [ $int -ge $min_val -a $int -le $max_val ]; then
echo "$int is within $min_val to $max_val."
else
echo "$int is out of range."
fi
else
echo "int is not an integer." >&2
exit 1
fi
One thing to note is I've re-written the combined conditional using the -a syntax since [ does not support && operator within but could be combined using && as [ $int -ge $min_val ] && [ $int -le $max_val ]
You would see things going bad and seeing errors as below which means that one of the conditionals involving -le is gone wrong on seeing an empty string.
1_script.sh: line 7: [: -a: integer expression expected
50 is out of range.
whereas with same code for undefined variables and replacing the expression to use [[ would gracefully handle the empty strings to produce just an incorrect result as
50 is out of range.
So to sum it up, from the many advantages over using [[, the particular advantage on your case is to handle variables if there could be empty strings in your conditionals.
Quoting is used to to stop the word splitting. In the case above it is not necessary but consider a case like this: you have a directory and having theses files file1.txt, file2.txt, old.txt and file1 old.txt.
If you wish to remove the file file1 old.txt and run the command
rm file1 old.txt
then it will remove the file old.txt instead of what you expected.
In your piece of code you don't need quotes as you discovered. However, using quotes is considered "good practice" because unexpected things can happen without quotes. For example if you run the code with int equal to say "foo bar" you might get some strange results without quotes.
It is like the double and triple equals in JavaScript. You could probably get away with only double equals but some unexpected results might occur.

Integer being interpreted as a variable

I am working on an audio visual art installation, using a Raspberry Pi with Raspbian Jessie. The audio or video is started after login via a bash script. This script runs fine on its own.
My issue has been with trying to schedule the loop to run only during certain times of the day using an if statement. This has been placed at the beginning on the loop to check the time before a new file would start.
HOUR=$(date +"%H")
echo "The hour is $HOUR"
if [ $HOUR -gt 22 || $HOUR-lt 8 ]
then
sleep 60
continue
fi
I have declared HOUR as an integer with declare -i HOUR at the beginning of the script.
The script outputs this error, then continues.
./start_audio.sh: line 49: 13: command not found
I believe the variable is set correctly because $HOUR [13] is shown in the error. So why is $hour being interpreted as a command when it has been declared as an integer?
Tried changes like adding quotes or removing spaces, as answers to other questions have suggested.
Enclose your variables in double quotes (not required here, but as a general best practice) and and separate out the checks into two with a || in between (-o is mostly deprecated):
if [ "$HOUR" -gt 22 ] || [ "$HOUR" -lt 8 ]
Or, you can use [[ ]]:
if [[ "$HOUR" -gt 22 || "$HOUR" -lt 8 ]]
Even better, use arithmetic operator, (( ... )):
if ((HOUR > 22 || HOUR < 8))
Related:
How to use double or single brackets, parentheses, curly braces
The shell builtin [ (test) or the external one does not support short circuit evaluation operators within their construct.
You need to use the bash keyword, [[:
[[ $HOUR -gt 22 || $HOUR -lt 8 ]]
Or break the logic in two parts:
[ $HOUR -gt 22 ] || [ $HOUR -lt 8 ]
While we are at it, you should quote your variable expansions to prevent word splitting and pathname expansion:
[ "$HOUR" -gt 22 ] || [ "$HOUR" -lt 8 ]
I have assumed that $HOUR-lt 8 is a typo as it should be $HOUR -lt 8.
I do not think you can use the || operator in a standard test [ ].
Try replacing || by -o, or using the double-bracketed [[ ]] conditional (bash-specific).

if statement with and shell script

I am trying to make a if statement where if the hour is between 13 and 23 it will set the hour back to 12. Now below is a snippet of my shell script code:
#!/bin/bash
HOUR=$1
if [ $HOUR > 13 ] && [ $HOUR < 23 ];
then
$HOUR=12
fi
Now I am getting errors when I run this script. How can I tweak this script to get the desired conditions specified above?
if [ $HOUR -gt 13 -a $HOUR -lt 23 ]
then
HOUR=12
fi
Use -gt and -lt instead of > < (redirection operations)
Use -a for AND in the same expression
Fix typo $HOURS=12 (removed $)
You need to evaluate that expression arithmetically rather than textually. Try something like this:
#!/bin/bash
HOUR=$1
if (( $HOUR > 13 )) && (( $HOUR < 23 ));
then
HOUR=12
fi
To use < or > in a Bash test, you need the [[...]] form of test construct. HOWEVER, that does not do what you think it does. Those are lexicographical tests.
You can either use -lt and -gt for arithmetic comparisons:
$ H=14
$ [[ $H -gt 13 && $H -lt 23 ]]; echo $?
0
(0 means True in this case...)
Or, use the ((...)) for arithmetic test construct. Then you can use a C style ternary assignment to test and assign in one step:
$ H=20
$ (( H = H>13 && H<23 ? 12 : H )); echo $H
12
Shorter version:
#!/bin/bash
HOUR=$1
[[ $HOUR > 13 && $HOUR < 23 ]] && HOUR=12

Shell script - No such file error

#!/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.

Resources