Inconsistent results when comparing numeric variables in bash if statement [duplicate] - bash

This question already has answers here:
Value too great for base (error token is "09")
(7 answers)
Value too great for base (error token is "08") [duplicate]
(1 answer)
Closed last month.
I have a bash script that performs commands on files based on dates contained in the file names. I'd like certain commands to run only for months greater than the current month, so I attempted to add an if statement that compares the variables $day (containing the numeric value of the month, taken from the for loop's parameter) and $thismonth (populated using date).
For most values of month the script works as expected, but when it loops through 08 and 09 it produces errors, e.g. value too great for base (error token is "08"). Is there some difference between the values 08 and 09 and all the others...? Or what other mistake have I made?
Minimal example test.sh:
Note: I'm a scripting novice so please feel free to correct any mistakes or inefficiencies even if not causing this particular issue. Also, parameters of the for loop came from an answer to How to loop through dates using Bash?.
for d in 02{01..28} {04,06,09,11}{01..30} {01,03,05,07,08,10,12}{01..31}
do
day=${d: -2}
month=${d::2}
thismonth=$(date +%m)
if [[ "$month" -gt "$thismonth" ]]; then
echo day is $day
echo the month is $month
echo this month is $thismonth
echo Greater than this month
echo "" # to make output more easily readable
else
echo day is $day
echo the month is $month
echo this month is $thismonth
echo NOT greater than this month
echo "" # to make output more easily readable
fi
done
Expected results:
# for values of `month` between `02` and `12`
day is <value of $day>
the month is <value between 02 and 12>
this month is 01
Greater than this month
# when `month` is `01`:
day is <value of $day>
the month is 01
this month is 01
NOT greater than this month
Actual results are as expected for all values of month except 08 and 09. Excerpted output (repetitive lines removed):
day is 01
the month is 02
this month is 01
Greater than this month
[ . . . ]
day is 30
the month is 06
this month is 01
Greater than this month
./test.sh: line 7: [[: 09: value too great for base (error token is "09")
day is 01
the month is 09
this month is 01
NOT greater than this month
[ . . . ]
./test.sh: line 7: [[: 09: value too great for base (error token is "09")
day is 30
the month is 09
this month is 01
NOT greater than this month
day is 01
the month is 11
this month is 01
Greater than this month
[ . . . ]
day is 30
the month is 11
this month is 01
Greater than this month
day is 01
the month is 01
this month is 01
NOT greater than this month
[ . . . ]
day is 31
the month is 01
this month is 01
NOT greater than this month
day is 01
the month is 03
this month is 01
Greater than this month
[ . . . ]
day is 31
the month is 07
this month is 01
Greater than this month
./test.sh: line 7: [[: 08: value too great for base (error token is "08")
day is 01
the month is 08
this month is 01
NOT greater than this month
[ . . . ]
./test.sh: line 7: [[: 08: value too great for base (error token is "08")
day is 31
the month is 08
this month is 01
NOT greater than this month
day is 01
the month is 10
this month is 01
Greater than this month
[ . . . ]
day is 31
the month is 12
this month is 01
Greater than this month
This seemed especially surprising since these months are in different groups of the for loop parameter (i.e. apparently unrelated to the fact that one loops through days 01 to 30 vs 01 to 31 so also not somehow related to number of days in thismonth or something like that).
The full script (which checks for files named with dates formatted yyyymmdd and removes certain those with certain dates) worked fine in the latter part of last year when I wanted to see results for all dates, but gave the above odd results when I added the if statement in the minimal example in order to prevent output results for months greater than the current one (since no files yet exist for dates later than today).
bash --version is GNU bash, version 5.1.4(1)-release on Debian GNU/Linux 11 (bullseye) (actually running Proxmox kernel where uname -srv is Linux 5.15.39-4-pve #1 SMP PVE 5.15.39-4 (Mon, 08 Aug 2022 15:11:15 +0200) in case that's somehow relevant).

Related

Subtract hours from date object in bash

I need to find a time X hours before the previous midnight, thus I would like to subtract X hours from a date object.
Example
# Finding the previous midnight
date -d "yesterday 23:59:59"
Mon Jul 11 00:00:00 CEST 2022
What I want
I would like to find the date X hours before this midnight
x=4
Mon Jul 10 20:00:00 CEST 2022
You can use this date command:
date -d "today 0 -4 hours"
Here:
today 0: gets midnight date-time for today's date
-4 hours: subtracts 4 hours from midnight time
It seems one can just write the following:
date --date 'yesterday 23:59:59 CEST -4 hours'

Get saturday date for the given input date using bash script

By using the below command, it will return the last saturday date.
date +"%b-%d-%Y" -d "last saturday"
Sep-01-2018
I want to pass input date as parameter, which should return the last saturday's date in bash script.
Aug-08-2018 -----> Aug-04-2018
Jun-04-2018 -----> Jun-02-2018
Get a negative number that will be the number of days to subtract. We use 13, because Saturday is 6, and 6 + 7 = 13. This will get us the Saturday one or two weeks ahead. Then we modulo 7, to ensure it is NEXT Saturday, then subtract 7 to make it LAST Saturday. Then we put that diff into the date string:
$ date_str="Aug-08-2018"
$ diff=$(( (13 - $(date +"%u" -d ${date_str})) % 7 - 7))
$ date -d "${date_str} ${diff} days"
Sat Aug 4 00:00:00 EDT 2018

Determining week number on a 4 week cycle in bash script

what I want is a cycle of 4 weeks in a bash script
My question is: How do I know this week's number in the cycle.
week x monday : echo one
week x+1 monday : echo two
week x+2 monday : echo three
week x+3 monday : echo four
and again
week x+4 monday : echo one
and so on
what I have is the epoch
(UTC), Thursday, 1 January 1970
consequently
(UTC), monday, 5 January 1970 (I can set this to echo 1)
Any suggestions? Converting dates is no problem. Just a general idea is ok.
I think you are expecting do something like this, with GNU date,
start_date=$(date -d "1970-01-05" '+%s') # Corresponding to 1
end_date=$(date -d "2017-01-02" '+%s') # Current week
Number of weeks between the dates
numberOfWeeks=$(( ( end_date - start_date )/(60*60*24*7) ))
printf "%s\n" "$numberOfWeeks"
2452
Now to determine which week this corresponds to, do
printf "The current week %s belongs to week %d" "$(date)" "$(((numberOfWeeks%4) + 1))"
The current week Mon, Jan 02, 2017 4:47:09 PM belongs to week 1
For further weeks down the line, say. 4th Monday of March 2017, using the above computation, i.e. with
end_date=$(date -d "2017-03-27" '+%s')
printf "The week %s belongs to week %d" "$(date -d "2017-03-27")" "$(((numberOfWeeks%4) + 1))"
The week Mon, Mar 27, 2017 12:00:00 AM belongs to week 1
Another example for the 3rd Monday or March 2017,
end_date=$(date -d "2017-03-20" '+%s')
printf "The week %s belongs to week %d" "$(date -d "2017-03-20")" "$(((numberOfWeeks%4) + 1))"
The week Mon, Mar 20, 2017 12:00:00 AM belongs to week 4
You can format the date output for showing the week number:
function printweek {
weeknr=$(date '+%V' -d "+$1 weeks")
echo "$((weeknr%4))"
}
# Test
for week in 0 1 2 3 4 5 6 30 31 32 33; do
echo "Week offset from today ${week} => $(printweek ${week})"
done
This will work when you start over counting each year (first week 1 again). When you want to continue counting on 1 Januari, the script will be more difficult. You can look at the solution of #Inian.
Another option might be looking at the output of the last run, and add one %4 to the weeknumber of the last run.

How do I get the month one calendar month ago in bash? [duplicate]

This question already has answers here:
Using `date` command to get previous, current and next month
(5 answers)
Closed 8 years ago.
I have been using the command:
date --date='1 months ago' +%b
To get the month name of the month it was a month ago, but have realised today as it is the 31st that this command actually gives me the month name it was 4 weeks ago.
Is there any way to get the calendar month that it was 1 month ago, or indeed n months ago as I can see that the discrepancy will be greater as the number of months is longer.
Date calculations that depend on the number of days in the month are tricky. A hybrid approach, using month numbers and a lookup table, will probably work best.
months=("" Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)
echo ${months[$(date +%m) - 1 ]}
[[ $(date +%d) == "31" ]] && date -d'-31 day' +%b || date -d'-1 month' +%b
test with today:
kent$ date
Thu Jul 31 17:34:27 CEST 2014
kent$ [[ $(date +%d) == "31" ]] && date -d'-31 day' +%b || date -d'-1 month' +%b
Jun
try this one line
#if the month before 30 days is the same of the actual month ,then return the month before 31 days
[[ `date --date='30 day ago' +%b` == `date +%b` ]] && echo `date --date='31 day ago' +%b` || echo `date --date='30 day ago' +%b`

using awk to do exact match in a file

i'm just wondering how can we use awk to do exact matches.
for eg
$ cal 09 09 2009
September 2009
Su Mo Tu We Th Fr Sa
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
$ cal 09 09 2009 | awk '{day="9"; col=index($0,day); print col }'
17
0
0
11
20
0
8
0
As you can see the above command outputs the index number of all the lines that contain the string/number "9", is there a way to make awk output index number in only the 4th line of cal output above.??? may be an even more elegant solution?
I'm using awk to get the day name using the cal command. here's the whole line of code:
$ dayOfWeek=$(cal $day $month $year | awk '{day='$day'; split("Sunday Monday Tuesday Wednesday Thursday Friday Saturday", array); column=index($o,day); dow=int((column+2)/3); print array[dow]}')
The problem with the above code is that if multiple matches are found then i get multiple results, whereas i want it to output only one result.
Thanks!
Limit the call to index() to only those lines which have your "day" surrounded by spaces:
awk -v day=$day 'BEGIN{split("Sunday Monday Tuesday Wednesday Thursday Friday Saturday", array)} $0 ~ "\\<"day"\\>"{for(i=1;i<=NF;i++)if($i == day){print array[i]}}'
Proof of Concept
$ cal 02 1956
February 1956
Su Mo Tu We Th Fr Sa
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29
$ day=18; cal 02 1956 | awk -v day=$day 'BEGIN{split("Sunday Monday Tuesday Wednesday Thursday Friday Saturday", array)} $0 ~ "\\<"day"\\>"{for(i=1;i<=NF;i++)if($i == day){print array[i]}}'
Saturday
Update
If all you are looking for is to get the day of the week from a certain date, you should really be using the date command like so:
$ day=9;month=9;year=2009;
$ dayOfWeek=$(date +%A -d "$day/$month/$year")
$ echo $dayOfWeek
Wednesday
you wrote
cal 09 09 2009
I'm not aware of a version of cal that accepts day of month as an input,
only
cal ${mon} (optional) ${year} (optional)
But, that doesn't affect your main issue.
you wrote
is there a way to make awk output index number in only the 4th line of cal output above.?
NR (Num Rec) is your friend
and there are numerous ways to use it.
cal 09 09 2009 | awk 'NR==4{day="9"; col=index($0,day); print col }'
OR
cal 09 09 2009 | awk '{day="9"; if (NR==4) {col=index($0,day); print col } }'
ALSO
In awk, if you have variable assignments that should be used throughout your whole program, then it is better to use the BEGIN section so that the assignment is only performed once. Not a big deal in you example, but why set bad habits ;-)?
HENCE
cal 09 2009 | awk 'BEGIN{day="9"}; NR==4 {col=index($0,day); print col }'
FINALLY
It is not completely clear what problem you are trying to solve. Are you sure you always want to grab line 4? If not, then how do you propose to solve that?
Problems stated as " 1. I am trying to do X. 2. Here is my input. 3. Here is my output. 4. Here is the code that generated that output" are much easier to respond to.
It looks like you're trying to do date calculations. You can be much more robust and general solutions by using the gnu date command. I have seen numerous useful discussions of this tagged as bash, shell, (date?).
I hope this helps.
This is so much easier to do in a language that has time functionality built-in. Tcl is great for that, but many other languages are too:
$ echo 'puts [clock format [clock scan 9/9/2009] -format %a]' | tclsh
Wed
If you want awk to only output for line 4, restrict the rule to line 4:
$ awk 'NR == 4 { ... }'

Resources