bash - test MM between 01 and 12 - bash

I have $MM. It can be:
01-12
But not 1-12 (i.e. every single digit will have a 0 in front, 1=01, 2=02 etc.)
I need to write a test that ensures that MM is valid, and fail if not. I have tried:
MM=09
if [[ "$MM" -le 0 ]] && [[ "$MM" -ge 13 ]]; then
echo "MM is not between 01 and 12"
fi
errors:
[[: 09: value too great for base (error token is "09")
I have also tried various combinations:
MM=13
if [ $MM -le 0 ] && [ $MM -ge 13 ]; then
echo "MM is not between 01 and 12"
fi
shows nothing when MM is 13 (to be expected)
MM=13
if [ $MM <= 0 ] && [ $MM => 13 ]; then
echo "MM is not between 01 and 12"
fi
errors
=: No such file or directory
I know I am doing something fundamentally wrong here, I'm just not sure what it is. I also need this logic to apply similar checks for:
DD needs to be between 01-31
hh needs to be between 00-23
mm needs to be between 00-59
thanks!

First of all leading 0 makes shell interpret it as octal number and 09 is invalid octal so you will get: 09: value too great for base error.
You can use use BASH regex for this validation:
mm='09'
[[ $mm =~ ^(0[1-9]|1[0-2])$ ]] && echo "valid" || echo "invalid"
valid
mm='9'
[[ $mm =~ ^(0[1-9]|1[0-2])$ ]] && echo "valid" || echo "invalid"
invalid
mm='13'
[[ $mm =~ ^(0[1-9]|1[0-2])$ ]] && echo "valid" || echo "invalid"
invalid
mm='12'
[[ $mm =~ ^(0[1-9]|1[0-2])$ ]] && echo "valid" || echo "invalid"
valid

Just use arithmetic comparison, specifying the radix:
if (( 10#$mm < 1 || 10#$mm > 12 )); then
echo "mm is not between 1 and 12"
fi
or, similarly,
if (( 10#$mm >= 1 && 10#$mm <= 12 )); then
echo "mm is between 1 and 12"
fi
You specify the radix by using radix# in front of the number/variable expansion. Here we need radix 10, hence the 10# in front of the expansion $mm. See Shell Arithmetic in the reference manual (near the end of the section).
The other answer (with regexes) is good, but doesn't carry the proper semantic, and can be really awkward with more complicated ranges.

In case you want sh compatibility, the case statement is handy.
case $MM in ? | ???* | *[!0-9]* | 1[3-9] | [2-9]? | 00) echo Invalid;; esac
This triggers on, in turn, too short, too long, non-numeric, too large smaller than 20, other too large numbers, and all zeros.

Related

Bash nested conditionals show unexpected behavior

please take a look at this:
This should echo X10, but echoes jackpot... can anyone see why it doesn't behave as it should?
Probably just some mistake I made that do not throw errors?
dice1=1
dice2=40
#These two lines are just tests to see if my brain still function:
echo "Is dice 1 less than 2? $(($dice1 < 2))"
echo "Is dice 2 between 6 and 54? $(($dice2 > 5 && $dice2 < 55))"
if [[ $dice1 == 1 ]]
then
if [[ $dice2 < 6 ]]
then
#dice1 has to be equal 1 and dice2 less than 6:
echo "jackpot"
else
#Since dice2 is larger than 5, if smaller than 55
#it should be between 6 and 54...
if [[ $dice2 < 55 ]]
then
echo "X10"
else
echo "X5"
fi
fi
else
echo "Dice one is not equal 1."
fi
When used with [[, the < and > operators sort lexicographically using the current locale.
I see two options.
Do the comparison in arithmetic context:
if (( $dice1 == 1 ))
then
if (( $dice2 < 6 ))
or the old-fashioned way:
if [[ $dice1 -eq 1 ]]
then
if [[ $dice2 -lt 6 ]]

bash [[ for numerical comparison?

We have some scripts that do things like
e=$?
if [[ $e == 123 ]]; then exit 1; fi
They're more complicated than that, it's just an example. My question is using double brackets acceptable to make numerical comparisons this way, is there any disadvantage? I would think it should be double parentheses if (( $e == 123 )) but I don't want to go changing a lot of scripts over nothing.
Thanks
There are a lot of key differences doing it, because == checks for exact string equality, but -eq evaluates both expressions arithmetically before checking for equality.
$ [[ " 1 " -eq 1 ]] && echo equal || echo not
equal
$ (( " 1 " == 1 )) && echo equal || echo not
equal
$ [[ " 1 " = 1 ]] && echo equal || echo not
not
Also, the empty string happens to be numerically equal to zero:
$ [[ "" -eq 0 ]] && echo equal || echo not
equal
$ [[ "" == 0 ]] && echo equal || echo not
not
And a whole other class of differences appears when you bring the comparison operators in - considering < vs -lt, for instance:
$ [[ 2 -lt 10 ]] && echo less || echo not
less
$ (( 2 < 10 )) && echo less || echo not
less
$ [[ 2 < 10 ]] && echo less || echo not
not
This is because the string 2 is alphabetically after the string 10 (since 1 comes before 2), but the number 2 is numerically less than the number 10.
Credits to the original cross site duplicate, with a few updates Is there any major difference when comparing a variable as a string or as an int?
The verdict is to use $((..)) for arithmetic comparisons strictly to avoid interpreting the operands as strings.

Using if and controlling numbers in ksh

I would like to download file with the format cars000.txt, cars003.txt,cars006.txt, till cars105.txt...interval of 3 as you can see
I use the following code in ksh, but after downloading cars012.txt, it fails, it begins to download cars13.txt,...and I don't wish it. What does it fails in the code?
FHR=000
while [ $FHR -le 105 ]
do
file=cars${FHR}.txt
wget http://${dir_target}/${file}
(( FHR = $FHR + 03 ))
echo $FHR
if [[ $FHR -le 10 ]]; then FHR="00"$FHR
else FHR="0"$FHR
fi
done
You should decide: is FHR a string, a decimal or an octal.
You are mixing them currently.
Try the next improvement:
FHR=0
while [ ${FHR} -le 105 ]; do
file=cars${FHR}.txt
(( FHR = FHR + 3 ))
echo Without leading zero: FHR=${FHR}
if [[ $FHR -le 10 ]]; then
echo "FHR=00${FHR}"
else
echo "FHR=0${FHR}"
fi
sleep 1
done
(The next improvement might be using printf or awk and no zero for 102/105)

Bash Value Too Great For Base, cant use date in if statement

I have seen similar situations but couldn't really figure out how to correctly apply the suggested solutions to my situation.
I have a bash script with the following lines:
LAST=$(ssh root#host ls /backup3/mycomp/partition1/ | tail -1)
#get last backup dir (formatted YYYY-MM-DD/). If none exist then get yesterdays Date
if [[ -z "$LAST" || "$LAST" -eq "$TODAY" ]]
then
log "/backup3/$HOST/$NAME/ does not exist, probably first backup or second backup done today."
LAST="$YESTERDAY"
fi
When I run this, I get the following error:
[[: 2014-11-08: value too great for base (error token is "08")
because I am actually searching for physical directory names, I cant just remove the zero. How would I go about making this work?
Replace:
if [[ -z "$LAST" || "$LAST" -eq "$TODAY" ]]
With:
if [[ -z "$LAST" || "$LAST" == "$TODAY" ]]
Since dates are not valid numbers, you want to do string comparison (==) not numeric comparison (-eq).
Discussion
Observe:
$ [[ "10-1" -eq "9" ]] && echo True
True
$ [[ "6+3" -eq "9" ]] && echo True
True
In the numeric context, signaled -eq, the shell is doing arithmetic on the arguments. This means that, for your date 2014-11-08, the shell was taking 2014, subtracting 11, and then trying to subtract 08. The shell treats any number that begins with a 0 as octal. Since 08 is not a valid octal number, you received an error message.
Comparison of [ and [[
As Jonathan Leffler points out, the implied arithmetic feature of [[ is another subtle difference between the old [ and the newer [[. Observe:
$ [[ "6+3" -eq "9" ]] && echo True
True
$ [ "6+3" -eq "9" ] && echo True
bash: [: 6+3: integer expression expected
The implied arithmetic feature of [[ is not present in [.

How can I get the exact number of lines in while read loop?

I used a while read loop in shell script to count and number line by line my file.txt. Now I want to give, inside the loop, the exact number of lines, like if I'm command wc -l. Below is my script.
#!/bin/bash
let count=0
while read cdat ctim clat clon
do
h=${ctim:0:2}; # substring hours from ctim
m=${ctim:3:2};
s=${ctim:6:2};
# echo $j
if [[ $h>=11 ]]; then
if [[ $h<=18 ]] && [[ $s<=00 ]]; then
if [[ $m != 01 ]]; then # spaces around "!=" is necessary
echo "$count $LINE" $cdat $ctim $clat $clon
let count=$count+1
fi
fi
fi
done < cloud.txt
exit
And output contains lines like:
0 2014/04/00 14:44:00 26.12 -23.22
1 2014/11/21 16:05:00 19.56 -05.30
2 2014/01/31 13:55:00 02.00 31.10
3 2014/04/00 14:20:00 17.42 12.14
4 2014/07/25 15:30:00 35.25 05.90
5 2014/05/15 12:07:00 23.95 07.11
6 2014/07/29 17:34:00 44.00 17.43
7 2014/03/20 18:00:00 -11.12 -22.05
8 2014/09/21 12:00:00 06.44 41.55
My question is how to find that the output contains 9 lines?
This does not answer your specific question
if [[ $h>=11 ]]; then
if [[ $h<=18 ]] && [[ $s<=00 ]]; then
All of those tests always return true.
The test, [ and [[ commands act differently based on the number of arguments they see.
All those tests have 1 single argument. In that case, if it's a non-empty string, you have a success return code.
Crucial crucial crucial to put whitespace around the operators.
if [[ $h >= 11 ]]; then
if [[ $h <= 18 ]] && [[ $s <= 00 ]]; then
Question for you: what do you expect this test to do? [[ $s <= 00 ]]
Be aware that these are all lexical comparisions. You probably want this instead:
# if hour is between 11 and 18 inclusive
if (( 10#$h >= 11 && 10#$h <= 18 )); then
You already know this value with $count. Since you're counting from 0, the number you want is $count+1.
If I add the command "wc -l" at the end of my loop, I can get exactly what I wanted, means that number of lines (9 lines). But I want to know if there is a way to get it exactly inside the loop, maybe using the same command "wc -l" or not.
#!/bin/bash
let count=0
while read cdat ctim clat clon
do
h=${ctim:0:2}; # substring hours from ctim
m=${ctim:3:2};
s=${ctim:6:2};
if [[ $h>=11 && $h<=17 ]] || [[ $ctim == "18:00:00" ]]; then
echo "$count $LINE" $cdat $ctim $clat $clon
let count=$count+1
fi
done < cloud.txt | wc -l
exit
The result is exactly: 9
But now how to do it inside the loop?

Resources