Best Way to Get File Modified Time in Seconds - shell

This seems to be a classic case of non-standard features on various platforms.
Quite simply, I want a universally (or at least widely) supported method for getting the modified time of a file as a unix timestamp in seconds.
Now I know of various ways to do this with stat but most are platform specific; for example stat -c %Y $file works for some, but won't work on OS X (and presumably other FreeBSD systems) which uses stat -f %m $file instead.
Likewise, some platforms support date -r $file +%s, however OS X/FreeBSD again does not as the -r option seems to just be an alternate to using +%s for getting a unix timestamp, rather than the reference file option as on other platforms.
The other alternative I'm familiar with is to use find with the -printf option, but again this is not widely supported. The last method I know of is parsing ls which, aside from being an unpleasant thing to have to do, is not something I believe can (or at least should) be relied upon either.
So, is there a more compatible method for getting a file's modified time? Currently I'm just throwing different variations of stat into a script and running them until one exits with a status of zero, but this is far from ideal, even if I cache the successful command to run first in future.

Since it seems like there might not be a "correct" solution I figured I'd post my current one for comparison:
if stat -c %Y . >/dev/null 2>&1; then
get_modified_time() { stat -c %Y "$1" 2>/dev/null; }
elif stat -f %m . >/dev/null 2>&1; then
get_modified_time() { stat -f %m "$1" 2>/dev/null; }
elif date -r . +%s >/dev/null 2>&1; then
get_modified_time() { date -r "$1" +%s 2>/dev/null; }
else
echo 'get_modified_time() is unsupported' >&2
get_modified_time() { printf '%s' 0; }
fi
[edit]
I'm updating this to reflect the more up to date version of the code I use, basically it tests the two main stat methods and a somewhat common date method in any attempt to get the modified time for the current working directory, and if one of the methods succeeds it creates a function encapsulating it for use later in the script.
This method differs from the previous one I posted since it always does some processing, even if get_modified_time is never called, but it's more efficiently overall if you do need to call it most of the time. It also lets you catch an unsupported platform earlier on.
If you prefer the function that only tests functions when it is called, then here's the other form:
get_modified_time() {
modified_time=$(stat -c %Y "$1" 2> /dev/null)
if [ "$?" -ne 0 ]; then
modified_time=$(stat -f %m "$1" 2> /dev/null)
if [ "$?" -ne 0 ]; then
modified_time=$(date -r "$1" +%s 2> /dev/null)
[ "$?" -ne 0 ] && modified_time=0
fi
fi
echo "$modified_time"
}

Why so complicated?
after not finding something on the web I simply read the manual of ls
man ls
which gave me
ls --time-style=full-iso -l
and change time in the format hh:mm:ss.sssssssss
As
ls --time-style=+FORMAT -l
while FORMAT is used like with date (see man date)
ls --time-style=+%c
will give you local date and time with seconds as an integer (without decimal point).
You can strip off additional ls information (e.g. file name, owner...) when you pipe through awk.

Just ask the operating system for its name, and go from there. Alternatively, write a C program, or use Python or something else that's pretty common and more standardized: How to get file creation & modification date/times in Python?

Related

Check file size is larger than 1.6GB

I have the following step in my Bash code which checks to see if a file is bigger than 1.6GB:
#!/bin/sh
SIZE_THRESHOLD=1717986918
if [[ $(find /home/methuselah/file -type f -size +${SIZE_THRESHOLD}c 2>/dev/null) ]]; then
somecmdbecausefileisbigger
else
somecmdbecausefileissmaller
fi
The command somecmdbecausefileisbigger is never triggered even when the file size is greater than 1.6GB. Why is this?
I don't know why your find command doesn't work, but I do know a simpler way to do this:
if [ $(stat -f %z /home/methuselah/file) -gt ${SIZE_THRESHOLD} ]; then
Though unfortunately you have to replace -f %z with -c %s on Linux (the former works on BSD and MacOS).
Just use stat (note that your version of stat may differ; check your man page for details):
if [ "$(stat -c '%s' /home/methuselah/file)" -gt "$SIZE_THRESHOLD" ]; then

Unix script - Comparing number of filename date with my single input date

I am new to Unix scripting, I am trying to create Unix script since one week but I couldn't. Please help me in this.
I have a number of different files more than 100 (all the filenames are different) which the filename contains the date string(ex: 20171101)in the directory. I want compare these filename dates with my input date (today - 10days =20171114),with the files in the directories only using filename string date if it is less than with my input date then I have to delete the file. could anyone please help on this. Thanks
My script:
ten_days_ago=$(date -d "10 days ago" +%Y%m%d)
cd "$destination_dir" ;
ls *.* | awk -F '-' '{print $2}'
ls *.* | awk -F '-' '{print $2}' > removal.txt
while read filedate
do
if [ "$filedate" -lt "$ten_days_ago" ] ; then
cd "$destination_dir" ;
rm *-"$filedate"*
echo "deletion done"
fi
done <removal.txt
this script is working fine. but I need to send a email as well - if the deletion has been done then -one pass email else fail email.
but here within while loop if I am writing the emails then that will iterate
You're probably trying to pipe to mail from the middle of your loop. (Your question should really show this code, otherwise we can't say what's wrong.) A common technique is to redirect the loop's output to a file, and then send that. (Using a temporary file is slightly ugly, but avoids sending an empty message when there is no output from the loop.)
Just loop over the files and decide which to remove.
#!/bin/bash
t=$(mktemp -t tendays.XXXXXXXX) || exit
# Remove temp file if interrupted, or when done
trap 'rm -f "$t"' EXIT HUP INT TERM
ten_days_ago=$(date -d "10 days ago" +%Y%m%d)
for file in *-[1-9]*[1-9]-*; do
date=${file#*-} # strip prefix up through first dash
date=${date%-*} # strip from last dash from the previous result
if [ "$date" -lt "$ten_days_ago" ]; then
rm -v "$file"
fi
done >"$t" 2>&1
test -s "$t" || exit # Quit if empty
mail -s "Removed files" recipient#example.net <"$t"
I removed the (repeated!) cd so this can be run in any directory -- just switch to the directory you want before running the script. This also makes it easier to test in a directory with a set of temporary files.
Collecting the script's standard error also means the mail message will contain any error messages if rm fails for some reason or you have other exceptions.
By the by you should basically never use ls in scripts.

bash: perform action (grep) when file changed

Googled a lot and didnt, susprisingly, find a working solution. Im an engineer, not a programmer. Just need this tool.
So: I have a file "test2.dat" that I want to grep every time it changes.
I dont have inotifywait or when-changed or any similar stuff installed and I dont have the rights to do so (and dont even want to as I would like this script to be working universally).
Any suggestions?
What I tried:
LTIME='stat -c %Z test2.dat'
while true
do
ATIME='stat -c %Z test2.dat'
if [[ "$ATIME" != "$LTIME" ]]
then
grep -i "15 RT" test2.dat > test_grep2.txt
LTIME=$ATIME
fi
sleep 5
done
but that doesn't do basically anything.
Your syntax for command-substitution is wrong. If you are expecting the command to run within the quotes you are wrong. The command-substitution syntax in bash is to do $(cmd)
Also by doing [[ "$ATIME" != "$LTIME" ]] you are doing a literal string comparison which will never work. Once you store LTIME=$ATIME the subsequent comparison of the strings will never be right.
The appropriate syntax for your script should have been,
#!/bin/bash
LTIME=$(stat -c %Z test2.dat)
while true
do
ATIME=$(stat -c %Z test2.dat)
if [[ "$ATIME" != "$LTIME" ]]
then
grep -i "15 RT" test2.dat > test_grep2.txt
LTIME="$ATIME"
fi
sleep 5
done
I would recommend using lower-case letters for variable definitions in bash, just re-used your template in the example above.

Bash: How can I subtract two time strings using bash?

I've crated the next script in order to find a lock file's age:
#!/bin/bash
now=`date +"%T"`
lock="aggregator.lock"
find . -name $lock -type f
if [ $? != 0 ];
then
exit
else
ls -ltrh $lock 2&>/dev/null
fi
if [ $? != 0 ];
then
locktime=`ls -ltrh aggregator.lock |awk -F" " '{print $7}'`
else
echo "File not found"
fi
I have two problems:
The output of ls -ltrh aggregator.lock |awk -F" " '{print $7}'
gives me the time in format HH:MM rather than HH:MM:SS and the output
of date +"%T" gives me the time in format HH:MM:SS (As needed), so
how can I get the modify time of the file with seconds?
I don't know how to subtract between the times... I wanted to do something like $now - $locktime in order to get the seconds delta between both variables, how can it be done?
EDIT:
The meaning of the script is to find how long the lock file existed...
There's this script:
device0="/home/build/aggregator/scripts/aggregator.lock"
if [ -e "$device0" ]
then
echo process is allready running
else
touch $device0
java -Xms6g -Xmx6g -jar /home/build/aggregator/aggregator-1.0-SNAPSHOT-jar-with-dependencies.jar
rm $device0
fi
Which creates the lock file... the only purpose of the creation of the lock file is to give me an indication about how long was the script's run time, just thought this information is needed as well, because I'm not looking for how much time passed since the last modification of the file.
Thanks in advance
To find out how much time ago a file was last modified, you can use
stat -c %Y file
To have the last modification date of the time in seconds since the epoch, and
date +%s
for the current date in the same units. Then simply subtracting them with $(( ... )) will give you the elapsed seconds since the last modification of file. Notice that Unix file system normally do not store the creation date, just the last access/change/modification date.
To convert back to a human-readable format, see for example https://stackoverflow.com/a/12199798/2907484
So basically:
now=$(date +%s)
was=$(stat -c%Y file)
elapsed=$((now - was))
days=$((elapsed/86400))
hours=$(( (elapsed-days*86400)/3600 ))
mins=$(( (elapsed-days*86400 - hours*3600)/60 ))
secs=$(( elapsed - days*86400 - hours*3600 - mins*60 ))
and now you can format the output with echo or printf as you like
printf '%02dd:%02dh:%02dm:%02ds\n' $days $hours $mins $secs
will output
00d:18h:42m:27s
Probably the format %02d is not so appropriate for days --- but you got the idea. You can add weeks, years or whatever easily too.
To see how much time a script/program use to run, you can use the bash builtin command time or the utility /usr/bin/time (be sure to not confound them), or you can follow the idea in this script by using:
was=$(date +%s)
...run your script...
now=$(date +%s)
and continue as above.
If you have stat (which is part of the GNU coreutils package, so you should find it on any GNU/linux system), then you can get the modification time of a file directly in seconds:
stat -c%Y aggregator.lock
Consult man stat for a complete list of the data you can get about a file, and the corresponding format (-c) codes.
That lets you simply write, for example:
elapsed=$(( $(date +%s) - $(stat -c%Y aggregator.lock) ))
($(( ... )) is bash-syntax for Arithmetic Expansion, see man bash)
If you need to do arithmetic, it's better to use Unix timestamps (number of seconds elapsed since Jan 1, 1970). Example using GNU date, but the same idea can be implemented using other versions.
now=$(date +%s)
locktime=$(date +%s --date 13:50:32)
diff=$(( now - lock time ))
Also, it's probably better to use stat to get the modification time of the lock file if possible.
You can make find output the timestamp right away, so try something like this:
locktime=$(find -maxdepth 1 -name aggregator.lock -printf '%T#')
now=$(date +%s)
delta=$(($now - $locktime))
printf "The file was modified %.0f seconds ago" $delta
I've edited my script, and here it is:
#!/bin/bash
#file="/home/build/aggregator/scripts/aggregator.lock"
file="$1"
if [ -e "$file" ]; then
now=$(date +%s)
was=$(stat -c%Y $file)
elapsed=$((now - was))
echo $elapsed
else
echo 0
fi
Then from Zabbix I can run the script and get the number of seconds the file exists or "0" if the file doesn't exist:
[root#zabbix ~]# zabbix_get -s 10.200.X.X -k check.lock[/home/build/aggregator/scripts/aggregator.lock]
14
[root#zabbix ~]#
Thanks everyone for your solutions.

Giving a file/directory the same modification date as another

How do I "copy" the modification date and time from one file/dir to another in Unix-based systems?
You have some options:
Use touch -t STAMP -m file if you want to change the time
Use cp --preserve=timestamps if you're copying the files and want to preserve the time
Use touch -r to set the time to a "reference" file
This will do exactly what you are asking:
touch -r "source_file" "destination_file"
For convenience later on, put the following lines in your .bashrc file:
cptimestamp() {
if [ -z $2 ] ; then
echo "usage: cptimestamp <sourcefile> <destfile>"
exit
fi
touch -d #$(stat -c "%Y" "$1") "$2"
}
Execute "source ~/.bashrc" and you're ready to go. If you prefer a script instead, remove the first and last lines -- then prepend "#!/bin/sh"
You can get the timestamp of source file using stat in unix timestamp format and then propagate it to the destination file using touch -d
src_file=/foo/bar
dst_file=/bar/baz
touch -d #$(stat -c "%Y" "$src_file") "$dst_file"
NOTE: This would only work with GNU coreutils which support the unix timestamp using the prefix # with touch
Use touch; it contains several optional flags that allow you to set such attributes.
If you are using cp, use the -p option to preserve mod times.
cp -p

Resources