Base error when trying to get week number - bash

I'm trying to get the week number of last week. The following command normally had work, but now I'm getting error.
lastweeknumber=$((`date +%V`-1))
bash: 09: value too great for base (error token is "09")
This week number is 09, so I've tried to convert to decimal adding 10# like this $(10#(date +%V)) but it's not working.
How to fix this?

Consider the following, which uses bash's built-in functionality in place of the external date command, and thus requires a recent shell release but is much faster to run (and will behave consistently without depending on a specific version of date).
With that done, though, there's still a need to strip the leading 0 -- which a parameter expansion will do just fine:
printf -v seconds_now '%(%s)T' -1
printf -v weeknum_lastweek '%(%V)T' "$(( seconds_now - (60 * 60 * 24 * 7) ))"
echo "The index of last week is ${weeknum_lastweek#0}"

It is because date +%V returns 09 and shell is interpreting any value starting with 0 as an octal number. Note that 09 is an invalid octal number hence you get that error value too great for base.
You can just force module 10 arithmetic in (( ... )):
echo $(( 10#$(date +%V) - 1 ))
8

Another way that handles wrapping around year correctly:
lastweeknumber=$(date -d "1 week ago" +%V)

Related

Linux: How to round off the date() to nearest interval

I want to round down the minutes to the nearest 15 min interval i.e. 00,15,30,45. I'm currently doing the below:
echo $(date +'%Y/%m/%d/%H/')$((($(($(date +'%M') / 15))-0)*15))
But at the start of the hour between 1-14 minutes, I get "/2021/11/03/21/0" instead of 00.
Also, I'm not sure if this is the best way to do this. Are there any alternatives?
Would you please try the following:
mod=$(( 10#$(date +%M) \% 15 ))
date -d "-${mod} minutes" +%Y/%m/%d/%H/%M
The variable mod holds the remainder of the minutes divided by 15.
Then round down to the nearest 15 minute interval by subtracting mod.
[Edit]
The manpage of crontab says:
Percent-signs (%) in the command, unless
escaped with backslash (), will be changed into newline
characters, and all data after the first % will be sent to
the command as standard input.
If you want to execute the command within crontab, please modify the command as:
mod=$(( 10#$(date +\%M) \% 15 ))
date -d "-${mod} minutes" +\%Y/\%m/\%d/\%H/\%M
[Edit2]
If you want to embed the code in crontab file, please add a line which look like:
0 12 * * * username bash -c 'mod=$(( 10#$(date +\%M) \% 15 )); DATEVAR=$(date -d "-${mod} minutes" +\%Y/\%m/\%d/\%H/\%M); write.sh "$DATEVAR"'
Please modify the execution time/date and the username accordingly.
The default shell to execute crontab command may be /bin/sh. Then you will need to explicitly switch it to /bin/bash to execute bash commands.
My apology that a backslash in front of % 15 (modulo operation) was missing in my previous post.
Another approach:
min=$(printf "%0.2d" $(( ($(date +'%M') / 15) * 15 )))
echo "$(date +'%Y/%m/%d/%H/')$min"
date -d "#$((($(date +%s) + 450) / 900 * 900))"
This uses the properties of integer division to “subtract a modulus” and adds half of the desired interval to (almost) mimic a rounding operation.
A bit of extra sub-second rounding precision (for no good reason) can be achieved by taking %N (nanoseconds) into account. But it will not matter, because the exact half of the rounding interval (450 seconds) is already aligned with the default epoch resolution (1 second). (If the number of seconds in the desired rounding interval was odd, then the following would increase the time rounding precision.)
date -d "#$((($(date +%s%N) + 45*10**10) / (9*10**11) * 900))"
Pure bash, bash version 4.3 or higher:
printf '%(%Y/%m/%d/%H/%M)T\n' "$(( $(printf '%(%s)T') /(15*60)*(15*60) ))"
Using GNU date (any bash version or POSIX shell):
date -d #$(( $(date +%s) /(15*60)*(15*60) )) +%Y/%m/%d/%H/%M
Truncates the current epoch date (seconds since 1970-01-01 00:00:00) to a 15 minute (900 second) interval, then converts to desired format.
Retrieves the current date/time once only.
If you build a date/time from two separate date/times, it can be wrong, when a unit ticks over in between.
The printf date-time format string was added in bash 4.2, and was changed in 4.3 to also print the current time, if no input date was given.
Note that bash arithmetic treats numbers that start with zero as octals, and numbers like 08 and 09 will cause an error (because they are not octal numbers).

Calculating PowerShell ticks in Bash

I am translating a PowerShell script in Bash.
This is how the ticks for current datetime are obtained in PowerShell:
[System.DateTime]::Now.Ticks;
By following the definition of Ticks, this is how I am trying to approximate the same calculation using the date command in bash:
echo $(($(($(date -u '+%s') - $(date -d "0001-01-01T00:00:00.0000000 UTC" '+%s'))) * 10000000 ))
This is what I got the last time I tried:
$ echo $(($(($(date -u '+%s') - $(date -d "0001-01-01T00:00:00.0000000 UTC" '+%s'))) * 10000000 )) ; pwsh -c "[System.DateTime]::Now.Ticks;"
637707117310000000
637707189324310740
In particular, the first 7 digits are identical, but digits in position 8 and 9 are still too different between the two values.
I calculated that this means there is just a 2 hours difference between the 2 values. But why? It cannot be the timezone, since I specified UTC timezone in both date commands, right? What do you think?
Note: my suspects about the timezone are increasing, since I am currently based in UTC+2 (therefore 2 hours difference from UTC), but how is this possible since I explicitly specified UTC as timezone in the date commands?
Solved it! The problem wasn't in the date commands, it was in the PowerShell command, which was using the +2 Timezone (CEST time). To fix this, I am now using UtcNow instead of Now.
This is what I am getting now:
$ echo $(($(($(date -u '+%s') - $(date -d "0001-01-01T00:00:00.0000000 UTC" '+%s'))) * 10000000 )) ; pwsh -c "[System.DateTime]::UtcNow.Ticks;"
637707132410000000
637707132415874110
As you can see, now all the digits are identical, except for the last 7th digits, since I added zeros on purpose to convert from seconds to ticks, as I am not interested in fractions of seconds (for now) and I consider them negligible.
Alternative way
Another way to make the two values identical (still excluding fractions of seconds), is to remove the -u option in the first date command in order to use the current time zone, and replace UTC with +0200 in the second date command. If I do this, I can leave Now on the PowerShell command (instead of replacing it with UtcNow).
By doing this, I am getting:
$ echo $(($(($(date '+%s') - $(date -d "0001-01-01T00:00:00.0000000 +0200" '+%s'))) * 10000000)) ; pwsh -c "[System.DateTime]::Now.Ticks;"
637707218060000000
637707218067248090
If you also want fractions of seconds
I just understood that if you also need to consider fractions of seconds, then you just need to add the result of date '+%N' (nanoseconds) divided by 100 to the calculation, in any of the two approaches shown above.
Since the result of date '+%N' can have some leading zeros, Bash may think it's an octal value. To avoid this, just prepend 10# to explicitly say it is a decimal value.
For example, taking the second approach shown above (the "alternative way"), now I get:
$ echo $(($(($(date '+%s') - $(date -d "0001-01-01T00:00:00.0000000 +0200" '+%s'))) * 10000000 + $((10#$(date '+%N')/100)) ))
637707225953311420

Subtract two variables in unix

This is my script which I am running on UNIX(AIX):
$MON date +"%m"
echo 'expr $MON - 2'
Output:
04
expr $MON - 2
I just want to subtract 2 from my current month and display .
I realize that the default shell on AIX is some variant of ksh which doesn't suffer from the same deficiency as bash in the input base, but it is something to bear in mind if you end up encountering this on another platform.
If this is your input:
$MON date +"%m"
echo 'expr $MON - 2'
then you have several issues.
$MON is unset, which causes the invocation of the line date +"%m", which gives the output 04
the echo command does not execute the expr command as you're using the wrong kind of ticks, which causes the output expr $MON - 2
First, to assign the variable you do VARIABLE=value, in your case this should be:
MON=`date +"%m"`
Don't put a space before or after the = sign.
Secondly, to perform the expr, you need to use the backtick(`)
echo `expr $MON - 2`
However, for most shells, you should use the more modern version of i want to get the result of a command, which is the logic $(command). These can be embedded, which makes them a lot easier to understand (backticks require escaping, and the more backticks the more escaping needed which quickly leads to backslash-palooza).
For bash, you need to ensure that the month is interpreted as a base 10 number, as otherwise once you hit August the code will stop working:
To force the number to be interpreted as a base 10 number, precede it with 10#:
MON=10#$(date +"%m")
echo $(($MON-2))
Examples:
bash-3.2$ month=07
bash-3.2$ echo $(($month + 1))
8
bash-3.2$ month=08
bash-3.2$ echo $(($month + 1))
bash: 08: value too great for base (error token is "08")
bash-3.2$ month=10#08
bash-3.2$ echo $(($month + 1))
9
Try this:
MON=$(date +"%m")
echo $(($MON-2))
You don't need to use expr because bash can perform simple arithmetic as well.
If your shell doesn't support arithmetic expressions, use expr:
expr $MON - 2
In both cases, you will get 2 as the output.
Another option: if you want to see 11 or 12 in January or February, let GNU date do the arithmetic:
date -d "-2 months" +%m

Bash script for renaming files, using modular arithmetic?

I've got a series of files that are namedHHMMSSxxxxxxxxxxxxxxxx.mp3, where HH,MM, and SS are parts of a timestamp and the x's are unique per file.
The timestamp follows a 24 hour form (where 10am is 100000, 12pm is 120000, 6pm is 180000, 10pm is 220000, etc). I'd like to shift each down by 10 hours, so that 10am is 000000, 12pm is 020000, etc.
I know basic BASH commands for renaming and moving, etc, but I can't figure out how to do the modular arithmetic on the filenames.
Any help would be very much appreciated.
#!/bin/bash
for f in *.mp3
do
printf -v newhour '%02d' $(( ( 10#${f:0:2} + 14 ) % 24 ))
echo mv "$f" "$newhour${f:2}"
done
Remove the echo to make it functional.
Explanation:
printf -v newhour '%02d' - this is like sprintf(), the value is stored in the named variable
$(( ( 10#${f:0:2} + 14 ) % 24 )) - 10# forces the number to base 10 (e.g. 08 would otherwise be considered an invalid octal), ${f:0:2} extracts the first two characters (the hour), the rest does the math
"$newhour${f:2}" - prepend the new hour before the substring of the original name, starting at the third character
The easiest way is probably to extract the timestamp and use date to turn it into a number of seconds, do normal math on the result, then convert it back. date -d datestring +format lets you do these conversions.

Bash script, Illegal number: 08

I'm running a pretty simple bash script in ubuntu but have come up with a problem.
If needed I'll post the whole script, but I've narrowed down the problem.
Basically, I want to run some code every 15 seconds, so I started with this:
time=`date +%S`
time2=$((time%15))
if [ $time2 -eq 0 ]
then
etc, etc, etc....
The problem comes up when time is 08 seconds. The script terminates with Illegal number: 08.
Adding to that, when using:
time2=$(($time%15))
instead of the illegal number error, it would terminate with Arithmetic expression: expecting EOF: "08%15"
I'm guessing 08 isn't interpreted as a number. Or there's some base issue, like it thinks it's in octal or something. Any help?
Shortest solution:
time2=$(( ${time#0} % 15 ))
${var#glob} means "$var with glob removed from the beginning if present".
Try using the following flags instead
date +%-S
It says given the -, it won't pad. It has problems with the base, interpreting it as an octal integer.
Anyway, if you want to do something every 15 seconds, i find this one easier to follow:
while ((1)); do
echo do something now...
sleep 15s
done
Force Bash to interpret the number in decimal, no matter how many padded zeros:
time2=$((10#$time % 15))
you're right, it was interpreting it as octal. bourne shells do that for any number with a leading 0 in an Arithmetic Substition:
#~ $ echo $(( 010 ))
8
#~ $ echo $(( 0100 ))
64
#~ $ echo $(( 10#0100 ))
100
#~ $ echo $(( 40#lolwut ))
2213236429
look in the manpage for 'base#' to see all the details about this '#-forcing' thing. you can get pretty ridiculous with it if you want to
Since you're only interested in "every fifteen seconds" rather than running things on the minute exactly you could use date +%s (lowercase s) which will give you the number of seconds since the start of the epoch. This won't have a leading 0 so your script should run fine.
However, I would wonder about your code in a wider context. If the system is running very slow for some reason it could be possible for the script only be run second 14 and then second 16 meaning it will miss an execution.
It might be worth touching a file when you do whatever it is the script does and then performing your action when then last modified date of the file is 15 or more seconds ago.
That does look like it's interpreting it as octal.
Try date +%S | sed -e 's/^0//'

Resources