Convert asterisk uptime string to date - bash

how can i convert "System uptime: 2 days, 9 hours, 16 minutes, 5 seconds" to a bash date?
I want to compare this date with another one.
Thx in advance.

Here you find a robust way that will convert your duration into the total number of seconds. This is robust as it does not need to have all the quantities listed. I.e. the output can be any of the following:
System uptime: 16 minutes, 2 seconds
System uptime: 12 days, 3 seconds
System uptime: 15minutes,12hours
Create a file convert.awk which contains
BEGIN { a["seconds"]=1; a["minutes"]=60
a["hours"]=3600; a["days"]=86400;
a["weeks"]=7*86400 }
{ tmp=tolower($0) }
{ while (match(tmp,/[0-9]+[^a-z]*[a-z]+/)) {
u=substr(tmp,RSTART,RLENGTH); q=u+0; gsub(/[^a-z]/,"",u)
t+=q*a[u]
tmp=substr(tmp,RSTART+RLENGTH)
}
print t
}
Run the following command:
$ echo "System uptime: 2 days, 9 hours, 16 minutes, 5 seconds" | awk -f convert.awk
206165
$ echo "System uptime: 2 weeks, 9 days, 16 minutes, 5 seconds" | awk -f convert.awk
1988165
How does this work:
When we read a line, eg.
tmp="system uptime: 16 minutes, 2 seconds"
The match command will search for any substring which starts with numbers and ends with a character string. match will automatically set the variables RSTART and RLENGTH to the position of the found substring and the length of it. This way, the command u=substr(tmp,RSTART,RLENGTH) will set
u="16 minutes"
You can now just do an arhtmetic operation on this, to extract the first number. This works with awk as it will convert any string starting with numbers into a number by ignoring anything after it. So q=u+0 creates
q=16
Finally, we remove anything which is not a character from u leading to
u="minutes"
The next step we redefine tmp by removing whatever we already processed.
tmp=", 2 seconds"
and start the process all over.

Assuming that the message will always include all the date components at fixed places, Unix timestamp (seconds past epoch) can be calculated using.
# Set t from command, or file.
t="System uptime: 2 days, 9 hours, 16 minutes, 5 seconds"
# Split into words
read -a p <<< "$t"
# Uptime in seconds
uptime=$((p[2]*24*60*60+p[4]*60*60+p[6]*60+p[8]))
now=$(date '+%s')
up_ts=$((now-uptime))
echo "Timestamp: $up_ts"
# Convert to human readable date.
up_date=$(date +'%Y-%m-%d %H:%M:%S' -d "#$up_ts")
echo "Date: $up_date"
You can also use bash built-in date formatting on the 'up_ts'.
If the format of the uptime message depends on the values (e.g., days is not included if uptime < 1 day), additional parsing will be needed.

Note that the uptime command gets the data from /proc/uptime
So one approach is to get the first field value which is the total elapsed time in seconds and feed it to date command:-
here is an example:-
$ cat /proc/uptime
8743161.15 7738916.50
$ date -d'-8743161.15 seconds'
Sat Jul 6 06:00:37 EDT 2019

Related

How to get last 10 minutes of logs from remote host

I'm trying to get the last x minutes of logs from /var/log/maillog from a remote host (I'm using this script within icinga2) but having no luck.
I have tried a few combinations of awk, sed, and grep but none have seemed to work. I thought it was an issue with double quotes vs single quotes but I played around with them and nothing helped.
host=$1
LOG_FILE=/var/log/maillog
hour_segment=$(ssh -o 'StrictHostKeyChecking=no' myUser#${host} 2>/dev/null "sed -n "/^$(date --date='10 minutes ago' '+%b %_d %H:%M')/,\$p" ${LOG_FILE}")
echo "${hour_segment}"
When running the script with bash -x, I get the following output:
bash -x ./myScript.sh host.domain
+ host=host.domain
+ readonly STATE_OK=0
+ STATE_OK=0
+ readonly STATE_WARN=1
+ STATE_WARN=1
+ LOG_FILE=/var/log/maillog
+++ date '--date=10 minutes ago' '+%b %_d %H:%M'
++ ssh -o StrictHostKeyChecking=no myUser#host.domain 'sed -n /^Jan' 8 '12:56/,$p /var/log/maillog'
+ hour_segment=
+ echo ''
Maillog log file output. I'd like $hour_segment to look like the below output also so I can apply filters to it:
head -n 5 /var/log/maillog
Jan 6 04:03:36 hostname imapd: Disconnected, ip=[ip_address], time=5
Jan 6 04:03:36 hostname postfix/smtpd[9501]: warning: unknown[ip_address]: SASL LOGIN authentication failed: authentication failure
Jan 6 04:03:37 hostname imapd: Disconnected, ip=[ip_address], time=5
Jan 6 04:03:37 hostname postfix/smtpd[7812]: warning: unknown[ip_address]: SASL LOGIN authentication failed: authentication failure
Jan 6 04:03:37 hostname postfix/smtpd[7812]: disconnect from unknown[ip_address]
Using GNU awk's time functions:
$ awk '
BEGIN {
m["Jan"]=1 # convert month abbreviations to numbers
# fill in the rest # fill in the rest of the months
m["Dec"]=12
nowy=strftime("%Y") # assume current year, deal with Dec/Jan below
nowm=strftime("%b") # get the month, see above comment
nows=strftime("%s") # current epoch time
}
{ # below we for datespec for mktime
dt=(nowm=="Jan" && $1=="Dec"?nowy-1:nowy) " " m[$1] " " $2 " " gensub(/:/," ","g",$3)
if(mktime(dt)>=nows-600) # if timestamp is less than 600 secs away
print # print it
}' file
Current year is assumed. If it's January and log has Dec we subtract one year from mktime's datespec: (nowm=="Jan" && $1=="Dec"?nowy-1:nowy). Datespec: Jan 6 04:03:37 -> 2019 1 6 04 03 37 and for comparison in epoch form: 1546740217.
Edit: As no one implemeted my specs in the comments I'll do it myself. tac outputs file in reverse and the awk prints records while they are in given time frame (t-now or future) and exits once it meets a date outside of the time frame:
$ tac file | awk -v t=600 ' # time in seconds go here
BEGIN {
m["Jan"]=1
# add more months
m["Dec"]=12
nowy=strftime("%Y")
nowm=strftime("%b")
nows=strftime("%s")
} {
dt=(nowm=="Jan" && $1=="Dec"?nowy-1:nowy) " " m[$1] " " $2 " " gensub(/:/," ","g",$3)
if(mktime(dt)<nows-t) # this changed some
exit
else
print
}'
Coming up with a robust solution that will work 100% bulletproof is very hard since we are missing the most crucial information, the year.
Imagine you want the last 10 minutes of available data on March 01 2020 at 00:05:00. This is a bit annoying since February 29 2020 exists. But in 2019, it does not.
I present here an ugly solution that only looks at the third field (the time) and I will make the following assumptions:
The log-file is sorted by time
There is at least one log every single day!
Under these conditions we can keep track of a sliding window starting from the first available time.
If you safe the following in an file extractLastLog.awk
{ t=substr($3,1,2)*3600 + substr($3,4,2)*60 + substr($3,7,2) + offset}
(t < to) { t+=86400; offset+=86400 }
{ to = t }
(NR==1) { startTime = t; startIndex = NR }
{ a[NR]=$0; b[NR]=t }
{ while ( startTime+timeSpan*60 <= t ) {
delete a[startIndex]
delete b[startIndex]
startIndex++; startTime=b[startIndex]
}
}
END { for(i=startIndex; i<=NR; ++i) print a[i] }
then you can extract the last 23 minutes in the following way:
awk -f extractLastLog.awk -v timeSpan=23 logfile.log
The second condition I gave (There is at least one log every single day!) is needed not to have messed up results. In the above code, I compute the time fairly simple, HH*3600 + MM*60 + SS + offset. But I make the statement that if the current time is smaller than the previous time, it implies we are on a different day hence we update the offset with 86400 seconds. So if you have two entries like:
Jan 09 12:01:02 xxx
Jan 10 12:01:01 xxx
it will work, but this
Jan 09 12:01:00 xxx
Jan 10 12:01:01 xxx
will not work. It will not realize the day changed. Other cases that will fail are:
Jan 08 12:01:02 xxx
Jan 10 12:01:01 xxx
as it does not know that it jumped two days. Corrections for this are not easy due to the months (all thanks to leap years).
As I said, it's ugly, but might work.

How do you add to time value returned by date?

I'm using the date function and trying to add minutes to the returned time (if it exceeds 60 it doesn't matter)
but every time I add to the time it removes the leading 0 and returns an odd value
time=$(date +%R)
time=$(sed -e 's/://g' <<< $time)
start=$(($time + 0051))
echo $start
output should be 4 digit ie 0445
edit: it's being treated as octal because of the leading 0.
To add 51 minutes to the current time with GNU date:
date "+%R" -d "+51 min"
Output (e.g.):
08:18

Bash - convert time interval string to nr. of seconds

I'm trying to convert strings, describing a time interval, to the corresponding number of seconds.
After some experimenting I figured out that I can use date like this:
soon=$(date -d '5 minutes 10 seconds' +%s); now=$(date +%s)
echo $(( $soon-$now ))
but I think there should be an easier way to convert strings like "5 minutes 10 seconds" to the corresponding number of seconds, in this example 310. Is there a way to do this in one command?
Note: although portability would be useful, it isn't my top priority.
You could start at epoch
date -d"1970-01-01 00:00:00 UTC 5 minutes 10 seconds" "+%s"
310
You could also easily sub in times
Time="1 day"
date -d"1970-01-01 00:00:00 UTC $Time" "+%s"
86400
There is one way to do it, without using date command in pure bash (for portability)
Assuming you just have an input string to convert "5 minutes 10 seconds" in a bash variable with a : de-limiter as below.
$ convertString="00:05:10"
$ IFS=: read -r hour minute second <<< "$convertString"
$ secondsValue=$(((hour * 60 + minute) * 60 + second))
$ printf "%s\n" "$secondsValue"
310
You can run the above commands directly on the command-line without the $ mark.
This will do (add the epoch 19700101):
$ date -ud '19700101 5 minutes 10 seconds' +%s
310
It is important to add a -u to avoid local time (and DST) effects.
$ TZ=America/Los_Angeles date -d '19700101 5 minutes 10 seconds' +%s
29110
Note that date could do some math:
$ date -ud '19700101 +5 minutes 10 seconds -47 seconds -1 min' +%s
203
The previous suggestions didn't work properly on alpine linux, so here's a small helper function that is POSIX compliant, is easy to use and also supports calculations (just as a side effect of the implementation).
The function always returns an integer based on the provided parameters.
$ durationToSeconds '<value>' '<fallback>'
$ durationToSeconds "1h 30m"
5400
$ durationToSeconds "$someemptyvar" 1h
3600
$ durationToSeconds "$someemptyvar" "1h 30m"
5400
# Calculations also work
$ durationToSeconds "1h * 3"
10800
$ durationToSeconds "1h - 1h"
0
# And also supports long forms for year, day, hour, minute, second
$ durationToSeconds "3 days 1 hour"
262800
# It's also case insensitive
$ durationToSeconds "3 Days"
259200
function durationToSeconds () {
set -f
normalize () { echo $1 | tr '[:upper:]' '[:lower:]' | tr -d "\"\\\'" | sed 's/years\{0,1\}/y/g; s/months\{0,1\}/m/g; s/days\{0,1\}/d/g; s/hours\{0,1\}/h/g; s/minutes\{0,1\}/m/g; s/min/m/g; s/seconds\{0,1\}/s/g; s/sec/s/g; s/ //g;'; }
local value=$(normalize "$1")
local fallback=$(normalize "$2")
echo $value | grep -v '^[-+*/0-9ydhms]\{0,30\}$' > /dev/null 2>&1
if [ $? -eq 0 ]
then
>&2 echo Invalid duration pattern \"$value\"
else
if [ "$value" = "" ]; then
[ "$fallback" != "" ] && durationToSeconds "$fallback"
else
sedtmpl () { echo "s/\([0-9]\+\)$1/(0\1 * $2)/g;"; }
local template="$(sedtmpl '\( \|$\)' 1) $(sedtmpl y '365 * 86400') $(sedtmpl d 86400) $(sedtmpl h 3600) $(sedtmpl m 60) $(sedtmpl s 1) s/) *(/) + (/g;"
echo $value | sed "$template" | bc
fi
fi
set +f
}
Edit : Yes. I developed for OP after comment and checked on Mac OS X, CentOS and Ubuntu. One liner, POSIX compliant command for converting "X minutes Y seconds" format to seconds. That was the question.
echo $(($(echo "5 minutes 10 seconds" | cut -c1-2)*60 + $(echo "5 minutes 10 seconds" | cut -c1-12 | awk '{print substr($0,11)}')))
OP told me via comment that he wants for "X minutes Y seconds" format not for HH:MM:SS format. The command with date and "+%s" is throwing error on (my) Mac. OP wanted to grab the numerical values from "X minutes Y seconds" format and convert it to seconds. First I extracted the minute in digit (take it as equation A) :
echo "5 minutes 10 seconds" | cut -c1-2)
then I extracted the seconds part (take it as equation B) :
echo "5 minutes 10 seconds" | cut -c1-12 | awk '{print substr($0,11)}'
Now multiply minute by 60 then add with the other :
echo $((equation A)*60) + (equation B))
OP should ask the others to check my developmental version (but working) of command before using it for automatic repeated usage like we do with cron on a production server.
If we want to run this on a log file with values in "X minutes Y seconds" format, we have to change echo "5 minutes 10 seconds" to cat file | ... like command. I kept a gist of it too if I or others ever need we can use it with cat to run on server log files with x minutes y seconds like log format.
Although off-topic (what I understood, question has not much to do with current time), this is not working for POSIX-compliant OS to get current time in seconds :
date -d "1970-01-01 00:00:00 UTC 5 minutes 10 seconds" "+%s"
It will throw error on MacOS X but work on most GNU/Linux distro. That +%s part will throw error on POSIX-compliant OS upon complicated usage. These commands are mostly suitable to get current time in seconds on POSIX compliant to any kind of unix like OS :
awk 'BEGIN{srand(); print srand()}'
perl -le 'print time'
If OP needs can extend it by generating current time in seconds and subtract. I hope it will help.
---- OLD Answer before EDIT ----
You can get the current time without that date -- echo | awk '{print systime();}' or wget -qO- http://www.timeapi.org/utc/now?\\s. Other way to convert time to second is echo "00:20:40.25" | awk -F: '{ print ($1 * 3600) + ($2 * 60) + $3 }'.
The example with printf shown in another answer is near perfect.
That thing you want is always needed by the basic utilities of GNU/Linux - gnu.org/../../../../../Setting-an-Alarm.html
Way to approach really depends how much foolproof way you need.

Subtract date & time from current date & time in bash

Lets say I have below two variables.
!#/bin/bash
MEMFILE=lock-file
date +%s > $MEMFILE
sleep 130
UPTIME= `date +%s`
I want to take the output of ( $UPTIME - $MEMFILE) in minutes and seconds.
Eg:
"Total downtime was 2 minutes and 5 seconds"
Several possibilities:
Subtracting times obtained from date:
#!/bin/bash
startdate=$(date +%s)
sleep 130
enddate=$(date +%s)
timetaken=$((enddate-startdate))
printf 'Total downtime was %d minutes and %d seconds\n' "$((timetaken/60))" "$((timetaken%60))"
The same without the external process date (since Bash 4.2):
#!/bin/bash
printf -v startdate '%(%s)T' -1
sleep 130
printf -v enddate '%(%s)T' -1
timetaken=$((enddate-startdate))
printf 'Total downtime was %d minutes and %d seconds\n' "$((timetaken/60))" "$((timetaken%60))"
The subtraction of times and computing the minutes and seconds are done using arithmetic expansion.
Using Bash's SECONDS variable (probably the best for you):
#!/bin/bash
SECONDS=0 # reset the SECONDS variable
sleep 130
timetaken=$SECONDS
printf 'Total downtime was %d minutes and %d seconds\n' "$((timetaken/60))" "$((timetaken%60))"
After being set to an integer value the special variable SECONDS is incremented each second.
Using Bash's time keyword with an appropriate TIMEFORMAT (here, we won't be able to write the elapsed time as MM minutes and SS seconds; it'll be shown in the form of MmSs, i.e., Total downtime was 2m10s).
#!/bin/bash
TIMEFORMAT='Total downtime was %0lR'
time {
# do your stuff in this block
sleep 130
}
Note that the linked answer already contains a lot of material.

echo to file loop in bash script

say that i am trying to do a echo TZ=GMT-24 date +%Y%m%d >> echoed.
This is in solaris.
Now, i would like to do a loop that reads a specific number of days and echoes with GMT-24/GMT-48 etc... until the number of days ends... this is a 5 times loop.... basicly from monday to friday. i will set this script on crontab that will run in one day and generates that echo output to a file so other script that i already have created can check those dates and work with them.
thanks in advance
This is ksh on Solaris 8:
$ date +%Y%m%d
20130919
$ for i in 1 2 3 4 5; do TZ=GMT-$(($i * 24)) date +%Y%m%d; done
20130920
20130921
20130922
20130923
20130924
$ for i in 1 2 3 4 5; do TZ=GMT+$(($i * 24)) date +%Y%m%d; done
20130918
20130917
20130916
20130915
20130914
To redirect to a file, add > filename after the done keyword

Resources