Bash Script Help - Tar not working with variables? - linux

Am trying to backup set folders based on yesterday's date by using a bash script and CRON task.
The folder structure of the site is like this:
/home/admin/domains/mysite.com/public_html/media/2014/March
And I would want to back-up that folder to this file:
/home/admin/domains/mysite.com/public_html/bk/mediabackup-March-2014.tar.gz
So created this script:
#!/bin/bash
NOW=$(date -d "12 hours ago" '+%m')
NOWYEAR=$(date -d "12 hours ago" '+%Y')
MONTHS=(Dummy January February March April May June July August September October November December)
NOWMONTH=${MONTHS[3]}
FILE="/home/admin/domains/mysite.com/public_html/bk/mediabackup-$NOWMONTH-$NOWYEAR.tar.gz"
PATH="/home/admin/domains/mysite.com/public_html/media/$NOWYEAR/$NOWMONTH"
tar -zcvf $FILE $PATH
When I run this script though shell, I would do this:
bash script.sh
And it could come up "command not found" - not sure why it's not working?
Any help would be great, thanks :)

By overriding the shell's built-in PATH variable, you are causing it to not find the tar command. Use another variable name, and generally, refrain from using uppercase variable names.

No need to hard-code the month names:
read year month < <(date -d "12 hours ago" "+%Y %B")
echo "$month-$year"
March-2014

Related

Comparing last modified date of a file and current date in shell script

I am trying to create a new file if the last modified date of the previously created file is less then the current date. Below is the code i tried but not sure getting what value as difference between last modified date and current date.
Code
LAST_MODIFIED_DATE=$(stat -c %y ${APPDIR}/data/XYZ.csv)
echo "LAST_MODIFIED_DATE ${LAST_MODIFIED_DATE}"
NOW=$(date +%s)
echo "NOW ${NOW}"
let diff=${NOW}-${LAST_MODIFIED_DATE}
echo "Diff ${diff}"
Result
LAST_MODIFIED_DATE 2021-08-03 10:30:56.627022878 -0500
NOW 1629354883
Diff 1629354883-2021-08-03 10:30:56.627022878 -0500
You should convert LAST_MODIFIED_DATE in seconds since 1970-01-01 first:
NOW="$(date +%s)"
LAST_MODIFIED_DATE="$(stat -c %y ${APPDIR}/data/XYZ.csv)"
LAST_MODIFIED_DATE_EPOCH="$(date +%s --date="$LAST_MODIFIED_DATE")"
diff=$(($NOW - $LAST_MODIFIED_DATE_EPOCH))
echo "Diff ${diff}"
But diff will be all the time positive unless the file ${APPDIR}/data/XYZ.csv was created with a date in the future.
If you want to test against a different date (for instance to check whether the file has been created in the last hour or not) you can modify the definition of NOW:
NOW="$(date +%s --date '1 hour ago')"

Interpolate bash command in Heroku scheduler

How can I interpolate a bash command in a Heroku scheduler command?
I have a command that runs everyday & it takes a day. Now I want to make it dynamic using bash date command e.g
cli "$(date --date "7 day ago")"
For today it will be Sat Apr 27 22:36:46 +06 2019 and for tomorrow it will be Sun Apr 28 22:36:46 +06 2019.
How can I achieve this?
Stack Overflow's syntax highlighting makes the problem pretty clear. You're nesting double quotes inside double quotes without escaping them:
cli "$(date --date "7 day ago")"
This is interpreted as three arguments:
"$(date --date "7
day
ago")"
Replace the innner ones with single quotes and it should work:
cli "$(date --date '7 day ago')"
Another alternative would be to escape the inner quotes with backslashes, but IMO using single quotes is more readable.

Bash command to archive files daily based on date added

I have a suite of scripts that involve downloading files from a remote server and then parsing them. Each night, I would like to create an archive of the files downloaded that day.
Some constraints are:
Downloading from a Windows server to an Ubuntu server.
Inability to delete files on the remote server.
Require the date added to the local directory, not the date the file was created.
I have deduplication running at the downloading stage; however, (using ncftp), the check involves comparing the remote and local directories. A strategy is to create a new folder each day, download files into it and then tar it sometime after midnight. A problem arises in that the first scheduled download on the new day will grab ALL files on the remote server because the new local folder is empty.
Because of the constraints, I considered simply archiving files based on "date added" to a central folder. This works very well using a Mac because HFS+ stores extended metadata such as date created and date added. So I can combine a tar command with something like below:
mdls -name kMDItemFSName -name kMDItemDateAdded -raw *.xml | \
xargs -0 -I {} echo {} | \
sed 'N;s/\n/ /' | \
but there doesn't seem to be an analogue under linux (at least not with EXT4 that I am aware of).
I am open to any form of solution to get around doubling up files into a subsequent day. The end result should be an archives directory full of tar.gz files looking something like:
files_$(date +"%Y-%m-%d").tar.gz
Depending on the method that is used to backup the files, the modified or changed date should reflect the time it was copied - for example if you used cp -p to back them up, the modified date would not change but the changed date would reflect the time of copy.
You can get this information using the stat command:
stat <filename>
which will return the following (along with other file related info not shown):
Access: 2016-05-28 20:35:03.153214170 -0400
Modify: 2016-05-28 20:34:59.456122913 -0400
Change: 2016-05-29 01:39:52.070336376 -0400
This output is from a file that I copied using cp -p at the time shown as 'change'.
You can get just the change time by calling stat with a specified format:
stat -c '%z' <filename>
2016-05-29 01:39:56.037433640 -0400
or with capital Z for that time in seconds since epoch. You could combine that with the date command to pull out just the date (or use grep, etc)
date -d "`stat -c '%z' <filename>" -I
2016-05-29
The command find can be used to find files by time frame, in this case using the flags -cmin 'changed minutes', -mmin 'modified minutes', or unlikely, -amin 'accessed minutes'. The sequence of commands to get the minutes since midnight is a little ugly, but it works.
We have to pass find an argument of "minutes since a file was last changed" (or modified, if that criteria works). So first you have to calculate the minutes since midnight, then run find.
min_since_mid=$(echo $(( $(date +%s) - $(date -d "(date -I) 0" +%s) )) / 60 | bc)
Unrolling that a bit:
$(date +%s) == seconds since epoch until 'now'
"(date -I) 0" == todays date in format "YYYY-MM-DD 0" with 0 indicating 0 seconds into the day
$(date -d "(date -I 0" +%s)) == seconds from epoch until today at midnight
Then we (effectively) echo ( $now - $midnight ) / 60 to bc to convert the results into minutes.
The find call is passed the minutes since midnight with a leading '-' indicating up to X minutes ago. A'+' would indicate X minutes or more ago.
find /path/to/base/folder -cmin -"$min_since_mid"
The actual answer
Finally to create a tgz archive of files in the given directory (and subdirectories) that have been changed since midnight today, use these two commands:
min_since_mid=$(echo $(( $(date +%s) - $(date -d "(date -I) 0" +%s) )) / 60 | bc)
find /path/to/base/folder -cmin -"${min_since_mid:-0}" -print0 -exec tar czvf /path/to/new/tarball.tgz {} +
The -print0 argument to find tells it to delimit the files with a null string which will prevent issues with spaces in names, among other things.
The only thing I'm not sure on is you should use the changed time (-cmin), the modified time (-mmin) or the accessed time (-amin). Take a look at your backup files and see which field accurately reflects the date/time of the backup - I would think changed time, but I'm not certain.
Update: changed -"$min_since_mid" to -"${min_since_mid:-0}" so that if min_since_mid isn't set you won't error out with invalid argument - you just won't get any results. You could also surround the find with an if statement to block the call if that variable isn't set properly.

Get yesterday's date in bash on Linux, DST-safe

I have a shell script that runs on Linux and uses this call to get yesterday's date in YYYY-MM-DD format:
date -d "1 day ago" '+%Y-%m-%d'
It works most of the time, but when the script ran yesterday morning at 2013-03-11 0:35 CDT it returned "2013-03-09" instead of "2013-03-10".
Presumably daylight saving time (which started yesterday) is to blame. I'm guessing the way "1 day ago" is implemented it subtracted 24 hours, and 24 hours before 2013-03-11 0:35 CDT was 2013-03-09 23:35 CST, which led to the result of "2013-03-09".
So what's a good DST-safe way to get yesterday's date in bash on Linux?
I think this should work, irrespective of how often and when you run it ...
date -d "yesterday 13:00" '+%Y-%m-%d'
Under Mac OSX date works slightly different:
For yesterday
date -v-1d +%F
For Last week
date -v-1w +%F
This should also work, but perhaps it is too much:
date -d #$(( $(date +"%s") - 86400)) +"%Y-%m-%d"
If you are certain that the script runs in the first hours of the day, you can simply do
date -d "12 hours ago" '+%Y-%m-%d'
BTW, if the script runs daily at 00:35 (via crontab?) you should ask yourself what will happen if a DST change falls in that hour; the script could not run, or run twice in some cases. Modern implementations of cron are quite clever in this regard, though.
Here a solution that will work with Solaris and AIX as well.
Manipulating the Timezone is possible for changing the clock some hours.
Due to the daylight saving time, 24 hours ago can be today or the day before yesterday.
You are sure that yesterday is 20 or 30 hours ago. Which one? Well, the most recent one that is not today.
echo -e "$(TZ=GMT+30 date +%Y-%m-%d)\n$(TZ=GMT+20 date +%Y-%m-%d)" | grep -v $(date +%Y-%m-%d) | tail -1
The -e parameter used in the echo command is needed with bash, but will not work with ksh.
In ksh you can use the same command without the -e flag.
When your script will be used in different environments, you can start the script with #!/bin/ksh or #!/bin/bash. You could also replace the \n by a newline:
echo "$(TZ=GMT+30 date +%Y-%m-%d)
$(TZ=GMT+20 date +%Y-%m-%d)" | grep -v $(date +%Y-%m-%d) | tail -1
date -d "yesterday" '+%Y-%m-%d'
To use this later:
date=$(date -d "yesterday" '+%Y-%m-%d')
you can use
date -d "30 days ago" +"%d/%m/%Y"
to get the date from 30 days ago, similarly you can replace 30 with x amount of days
Just use date and trusty seconds:
As you rightly point out, a lot of the details about the underlying computation are hidden if you rely on English time arithmetic. E.g. -d yesterday, and -d 1 day ago will have different behaviour.
Instead, you can reliably depend on the (precisely documented) seconds since the unix epoch UTC, and bash arithmetic to obtain the moment you want:
date -d #$(( $(date +"%s") - 24*3600)) +"%Y-%m-%d"
This was pointed out in another answer. This form is more portable across platforms with different date command line flags, is language-independent (e.g. "yesterday" vs "hier" in French locale), and frankly (in the long-term) will be easier to remember, because well, you know it already. You might otherwise keep asking yourself: "Was it -d 2 hours ago or -d 2 hour ago again?" or "Is it -d yesterday or -d 1 day ago that I want?"). The only tricky bit here is the #.
Armed with bash and nothing else:
Bash solely on bash, you can also get yesterday's time, via the printf builtin:
%(datefmt)T
causes printf to output the date-time string resulting from using
datefmt as a format string for strftime(3). The corresponding argu‐
ment is an integer representing the number of seconds since the
epoch. Two special argument values may be used: -1 represents the
current time, and -2 represents the time the shell was invoked.
If no argument is specified, conversion behaves as if -1 had
been given.
This is an exception to the usual printf behavior.
So,
# inner printf gets you the current unix time in seconds
# outer printf spits it out according to the format
printf "%(%Y-%m-%d)T\n" $(( $(printf "%(%s)T" -1) - 24*3600 ))
or, equivalently with a temp variable (outer subshell optional, but keeps environment vars clean).
(
now=$(printf "%(%s)T" -1);
printf "%(%Y-%m-%d)T\n" $((now - 24*3600));
)
Note: despite the manpage stating that no argument to the %()T formatter will assume a default -1, i seem to get a 0 instead (thank you, bash manual version 4.3.48)
You can use:
date -d "yesterday 13:55" '+%Y-%m-%d'
Or whatever time you want to retrieve will retrieved by bash.
For month:
date -d "30 days ago" '+%Y-%m-%d'
As this question is tagged bash "DST safe":
And using fork to date command implie delay, there is a simple and more efficient way using pure bash built-in:
printf -v tznow '%(%z %s)T' -1
TZ=${tznow% *} printf -v yesterday '%(%Y-%m-%d)T' $(( ${tznow#* } - 86400 ))
echo $yesterday
This is a lot quicker on more system friendly than having to fork to date.
From bash version 5.0, there is a new variable $EPOCHSECONDS
printf -v tz '%(%z)T' -1
TZ=$tz printf -v yesterday '%(%Y-%m-%d)T' $(( EPOCHSECONDS - 86400 ))
echo $yesterday

bash - extract day of week of variable

What is the syntax to extract the day of the week from a stored date variable?
The dateinfile format is always [alphanum]_YYYYMMDD.
In this pseudocode example, trying to get dayofweek to store Saturday:
#! /bin/bash
dateinfile="P_20090530"
dayofweek="$dateinfile -u +%A"
[me#home]$ date --date=${dateinfile#?_} "+%A"
Saturday
Or, to put it as you've requested:
[me#home]$ dayofweek=$(date --date=${dateinfile#?_} "+%A")
[me#home]$ echo $dayofweek
Saturday
date -d $(echo $dateinfile | cut -f2 -d_) -u +%A
The inner expression separates the 20090530 from P_20090530, and the outer one extracts the day of week from that date
I needed the same output recently and googled upon this, but I had the same problem stating Bad Substition error.
I then read the date manual and made my version up as follows:
#! /bin/sh
dateinfile="P_20090530"
dayofweek=`date --reference $dateinfile +%A`
echo $dayofweek

Resources