Calculating days left to a specific date - bash

I'm having trouble figuring out a way to calculate amount of days left to a specific date (passed as an argument). I tried this, however it doesn't even work correctly with any date of 2021.
days=$[$(date +%j -d $1)-$(date +%j $now)];
if (( $days < 0 ))
then
echo "error";
exit 1;
fi
echo "Theres" $days "left to this date.";
Does anyone have an idea on how I could fix it?

There are a few problems I see.
I don't recognize the $[...] syntax and can't find a shell that does (see the comments, below, where user #KamilCuk explains this further).
The if syntax is wrong, possibly swapped with the previous line.
Because of the parentheses, what's read as a less-than sign (<) is going to try to redirect input to a program.
The answer will always print out.
As you point out, there's no chance of a Julian day working with different years.
Try something like this, instead.
#!/bin/sh
Split up the dates for easier debugging, first, and also use `+%s to get "UNIX time" seconds since the start of 1970.
now=$(date +%s $now)
target=$(date +%s -d $1)
days=$(($target - $now));
Fix the conditional syntax.
if [ $days -lt 0 ]
then
echo error
exit 1
Put the output into an else clause.
else
Since we have the answer in seconds, divide by the number of seconds in a typical day.
days=$(($days / 86400))
echo "There are $days days left to this date."
fi
I also cleaned up the echo syntax for clarity.
Note that this still isn't perfect. Depending on your definition of "one day," there are going to be cases where the answer from this script differs from what you want; in that case, you'll need to adjust $target to match a particular time of day. In addition, not every day is 86400 seconds long, because of daylight savings and leap seconds. But with those caveats, it should work well enough and adding those to a script sounds like more work than "how many days?" should warrant.
If you want to see the steps it takes for debugging, run it with sh -x date.sh '2022-12-31' (with your script's name and date), since the -x argument tells the shell to give you a "trace" of intermediate steps.

Related

Get today's date minus one Year in Unix (AIX)

I need to find today's date and then subtract a year and format that date into the YYYY-MM-dd format.
I am able to accomplish this with a script I wrote, but apparently it is only compatible with bash. I need it to be compatible with AIX.
lastYear=`date +'%Y-%m-%d' -d 'last year'`
searchDate="$lastYear 00.00.00";
echo "Retrieving data start from $searchDate"
myquery="myquery >= '$searchDate'"
The result when run on an AIX machine is that it only passes the "00:00:00" part of the $searchDate, the date does not prefix before the time as I hoped. What is the safest way to write this for the most compatibility across Linux/Unix variations?
Thank you!
Why make it so complicated?
#!/bin/ksh
typeset -i year=$( date +%Y )
(( year -= 1 ))
typeset rest=$( date +%m-%d )
echo "${year}-${rest}"
This should work in any shell. If you use sh replace the
$( ... )
with back tics
` ... `
But for bash and ksh I use $( ... ) -- just personal preference.
Checkout Updated Section Below
Original Answer
Try this. It uses the -v flag to display the result of adjusting the current date by negative one year -1y
searchDate=$(date -v-1y +'%Y-%m-%d')
echo "Retrieving data start from $searchDate"
myquery="myquery >= '$searchDate'"
Here is the output:
Retrieving data start from 2017-06-21
Note: I did try to run the lines you provided above in a script file with a bash shebang, but ran into an illegal time format error and the output was 00:00:00. The script I provided above runs cleanly on my unix system.
Hope this helps. Good luck!
Updated Section
Visit ShellCheck.net
It's a nice resource for testing code compatibility between sh, bash, dash, and ksh(AIX's default) shells.
Identifying the Actual Issue
When I entered your code, it clearly identified syntax that is considered legacy and suggested how to fix it, and gave this link with an example and a great explanation. Here's the output:
lastYear=`date +'%Y-%m-%d' -d 'last year'`
^__Use $(..) instead of legacy `..`.
So, it looks like you might be able to use the code you wrote, by making one small change:
lastYear=$(date +'%Y-%m-%d' -d 'last year')
Note: This question already has an accepted answer, but I wanted to share this resource, because it may help others trouble-shoot shell compatibility issues like this in the future.

Rename file from Day of the Year to YYMMDD0000

I have a file with this name
ims2015255_4km_GIS_v1.3.png so it shows the 255 day of year 2015.
I want to auto rename this file to read like this
SNC_obs_YYMMDD0000.png The time to be always 0000
Thanks a lot.
Parsing the original name is fairly easy.
input=ims2015255_4km_GIS_v1.3.png
[[ $input =~ ims(....)(...)_ ]] && year=${BASH_REMATCH[1]} day=${BASH_REMATCH[2]}
Converting that into a new date might be trickier. If you have GNU date, though, you can use
output=$(date --date "$year-1-1 + $(($day - 1)) days" +"SNC_obs_%Y%m%d0000.png")
If you aren't using bash and are limited to POSIX-specified features, try the following to set year and day.
tmp=${input#ims}
tmp=${tmp%%_*}
year=${tmp%???}
day=${tmp#????}

Unique Linux filename, sortable by time

Previously I was using uuidgen to create unique filenames that I then need to iterate over by date/time via a bash script. I've since found that simply looping over said files via 'ls -l' will not suffice because evidently I can only trust the OS to keep timestamp resolution in seconds (nonoseconds is all zero when viewing files via stat on this particular filesystem and kernel)
So I then though maybe I could just use something like date +%s%N for my filename. This will print the seconds since 1970 followed by the current nanoseconds.
I'm possibly over-engineering this at this point, but these are files generated on high-usage enterprise systems so I don't really want to simply trust the nanosecond timestamp on the (admittedly very small) chance two files are generated in the same nanosecond and we get a collision.
I believe the uuidgen script has logic baked in to handle this occurrence so it's still guaranteed to be unique in that case (correct me if I'm wrong there... I read that someplace I think but the googles are failing me right now).
So... I'm considering something like
FILENAME=`date +%s`-`uuidgen -t`
echo $FILENAME
to ensure I create a unique filename that can then be iterated over with a simple 'ls' and who's name can be trusted to both be unique and sequential by time.
Any better ideas or flaws with this direction?
If you order your date format by year, month (zero padded), day (zero padded), hour (zero padded), minute (zero padded), then you can sort by time easily:
FILENAME=`date '+%Y-%m-%d-%H-%M'`-`uuidgen -t`
echo $FILENAME
or
FILENAME=`date '+%Y-%m-%d-%H-%M'`-`uuidgen -t | head -c 5`
echo $FILENAME
Which would give you:
2015-02-23-08-37-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
or
2015-02-23-08-37-xxxxx
# the same as above, but shorter unique string
You can choose other delimiters for the date/time besides - as you wish, as long as they're within the valid characters for Linux file name.
You will need %N for precision (nanoseconds):
filename=$(date +%s.%N)_$(uuidgen -t); echo $filename
1424699882.086602550_fb575f02-bb63-11e4-ac75-8ca982a9f0aa
BTW if you use %N and you're not using multiple threads, it should be unique enough.
You could take what TIAGO said about %N precision, and combine it with taskset
You can find some info here: http://manpages.ubuntu.com/manpages/hardy/man1/taskset.1.html
and then run your script
taskset --cpu-list 1 my_script
Never tested this, but, it should run your script only on the first core of your CPU. I'm thinking that if your script runs on your first CPU core, combined with date %N (nanoseconds) + uuidgen there's no way you can get duplicate filenames.

Getting the name of the folder and comparing to integer in bash

Ok, so what I'm trying to do is, all my backup folders are named dates 03-07-13. So I'm trying to select the day, and if it's greater than or equal to 7 days old, it will delete. This is what I have so far, but it's not working.
DATE=$(date +"%d")
for i in /media/backupdrive/*; do
DAY=${i:22:2}
if [ "$DAY" -ge "7" ]
then
echo "day greater than 7";
fi
done
the 22:2 cuts off the /media/backupdrive/00-
00 represents the month
Right now It's just checking if it's greater than 7, if it is, it prints it out.
EDIT: The problem was resolved. I want to thank you all for helping a bash beginner. Thank you again!
Per a screenshot given in a comment, your actual code uses the following:
DAY=${i:22:2}
if [ "$day" -ge "7" ]
Emphasis on the capitalization-differences between DAY and $day. When this runs, it's trying to compare an empty-string to a string (or "numbers" via the -ge) and this will cause the error you're receiving.
Try updating your if statement to use the uppercase version:
if [ "$DAY" -ge "7" ]
It seems you want to delete files that are older than 7 days. The find command can find those files for you, and optionally delete them:
find /media/backupdrive -mtime +7 # Files that are older than 7 days
find /media/backupdrive -mtime +7 -delete # ... and delete them
Using the 'DAY' variable opens you up to "just rolled over" issues.
Some alternatives:
change the folder format so that it is more descriptive.
add a meta file in each folder that gives a time value that is easier to parse and work with.
have an index for the backup folders containing said data.
The time format I generally use incorporates the following:
[epoch seconds]-[YYYY][MM][DD]-[HH]:[MM]:[SS]
This lets you do things like asking for backups that are 7 days old from right now. You would do the math against the epoch seconds, which avoids the confusion of days rolling over.
Basically, the epoch seconds is for making time calcs easier. The other time stamp bits makes it human readable. The ordering makes it so that it sorts correctly in a folder listing.
EDIT:
In the event your backup path ever changes:
DAYtmp=${i: -8:5}
DAY=${DAYtmp: -2}
This will yield the DAY from the folder name if the parent paths change in length.

Script for finding average runtime of a program

I found partial solutions on several sites, so I pulled several parts together, but I still couldn't figure it out.
Here is what I am doing:
I am running a simple java program from Terminal, and need to find the average runtime for the program.
What I am doing is running the command several times, finding the total time, and then dividing that total time by the number of times I ran the program.
I would also like to acquire the output of the program rather than displaying it on standard output.
Here is my current code and the output.
Shell Script:
startTime=$(date +%s%N)
for ((i = 0; i < $runTimes; i++))
do
java Program test.txt > /dev/null
done
endTime=$(date +%s%N)
timeDiff=$(( $endTime - $startTime ))
timeAvg=$(( $timeDiff / $numTimes ))
echo "Avg Time Taken: "
echo $timeAvg
Output:
./run: line 12: 1305249784N: value too great for base (error token is "1305249784N")
The line number 12 is off because this code is part of a larger file.
The line number 12 is the line with timeDiff being evaluated.
I appreciate any help, and apologize if this question is redundant or off-topic.
On my machine, I don't see what the %N format for date is getting you, as the value seems to be 7 zeros, BUT it is making a much bigger number to evaluate in the math, i.e. 1305250833570000000. Do you really need nano-second precision? I'll bet if you go with just %s it will be fine.
Otherwise you look to be on the right track.
P.S.
Oh yeah, minor point,
echo "Avg Time Taken: $timeAvg"
Is a a simpler way to achieve your required output ;-)
Option 2. You could take out the date calculations all together, and turn your loop into a small script. Then you can use a built-in feature of the shell
time myJavaTest.sh
Will give you details like
real 0m0.049s
user 0m0.016s
sys 0m0.015s
I hope this helps.

Resources