Integer being interpreted as a variable - bash

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).

Related

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.

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

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

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

Arithmetic syntax error with shell script

I am trying to call the function "warn" if the calculation is TRUE. I am still not quite comfortable with the syntax, would like some tips on how to fix the last line.
if [ "$noproc" -gt 0 ]; then
echo "WARNING: NoProc at $noproc for $process processes." >> $log
elif [ "$max" -ge 11 ]; then
[ $(($max - $total)) -lt 6 && [ $idle -le $(($max \* 0.25 | bc -l)) ] ] | warn $total $process $max $idle
The error I get: line 97: [: missing ` ] '
If your tagging for this question is correct and you're genuinely using bash (which is to say that your script starts with #!/bin/bash, or if not started via a shebang you use bash yourscript rather than sh yourscript), you might as well take advantage of it.
# extended bash math syntax
if (( (max - total) < 6 )) && (( idle <= (max / 4) )); then
warn "$total" "$process" "$max" "$idle"
fi
If, for whatever reason, you don't want to use (( )), you can still use [[ ]], which gives you a test context with its own extended syntax:
# extended bash test syntax
if [[ $((max - total)) -lt 6 && $idle -le $(bc -l <<<"$max*0.25") ]]; then
warn "$total" "$process" "$max" "$idle"
fi
...whereas if you want to be compatible with POSIX sh, you need to end the test before you can put in a shell-level logical-AND operator.
# corrected POSIX-compliant test syntax
if [ "$((max - total))" -lt 6 ] && [ "$idle" -le "$(bc -l <<<"$max*0.25")" ]; then
warn "$total" "$process" "$max" "$idle"
fi
To understand why, let's look at how your original command would parse, if you changed the (utterly incorrect) | symbol to && instead:
# Equivalent (longer form) of the original code, with pipe corrected to logical-AND
if [ $(($max - $total)) -lt 6; then
if [ $idle -le $(($max \* 0.25 | bc -l)) ] ]; then
warn $total $process $max $idle
fi
fi
Note that this is running, as a single command, [ $(($max - $total)) -lt 6.
[ is not special shell syntax -- it's just a command. In older shells it was actually /usr/bin/[; today, there's also a [ builtin as well, but other than being faster to execute, it behaves exactly the same way as it would have were it executing the old, external command.
That [ command expects to be passed a ] as its last argument, since there's no ] after the -lt 6, you get a syntax error and it exits.
Similarly, your code would then (if the first command succeeded) run [ $idle -le $(($max \* 0.25 | bc -l)) ] ]. Here, you have a [ command passed two ]s on the end; it simply doesn't know what to do with the second one.
You can't nest [ invocations. Even if you could, a && (b) === a && b in logic.
You can't use commands in arithmetic expansions.
Bash's [[ is safer than [.
Use More Quotes™.
Result:
[[ "$(($max - $total))" -lt 6 ]] && [[ "$idle" -le "$(bc -l <<< "$max * 0.25")" ]] | warn "$total $process $max $idle"

Bash Shell: What is the differences in syntax?

I've seen two ways in tutorials to do syntax for if statements in BASH shell:
This one wouldn't work unless I put quotes around the variable and added additional [ and ]:
if [[ "$step" -eq 0 ]]
This one worked without putting quotes around the variable and the additional [ and ] weren't needed:
if [ $step -ge 1 ] && [ $step -le 52 ]
Which is correct and best practice? What are the differences? Thanks!
"When referencing a variable, it is generally advisable to enclose its name in double quotes" -- http://tldp.org/LDP/abs/html/quotingvar.html
if [ $step -ge 1 ] && [ $step -le 52 ] can be replaced as
if [ "$step" -ge 1 -a "$step" -le 52 ]
if [[ "$step" -eq 0 ]] can be replaced as if [ "$step" -eq 0 ]
Also, suppose you have the following script:
#!/bin/bash
if [ $x -eq 0 ]
then
echo "hello"
fi
You get this error when you run the script -- example.sh: line 2: [: -eq: unary operator expected
But using if [ "$x" -eq 0 ]
You get a different error when you run the script -- example.sh: line 2: [: : integer expression expected
Thus, it is always best to put variables inside quotes...
if [[ .... ]] syntax is particularly useful when you have regex in the condition statement -- http://honglus.blogspot.com/2010/03/regular-expression-in-condition.html
EDIT: When we deal with strings --
#!/bin/bash
if [ $x = "name" ]
then
echo "hello"
fi
You get this error when you run the script -- example.sh: line 2: [: =: unary operator expected
But, if you use if [ "$x" = "name" ] it runs fine (i.e. no errors ) and if statement is evaluated as false, as value of x is null which does not match name.

Resources