Bash format uptime to show days, hours, minutes - linux

I'm using uptime in bash in order to get the current runtime of the machine. I need to grab the time and display a format like 2 days, 12 hours, 23 minutes.

My uptime produces output that looks like:
$ uptime
12:49:10 up 25 days, 21:30, 28 users, load average: 0.50, 0.66, 0.52
To convert that to your format:
$ uptime | awk -F'( |,|:)+' '{print $6,$7",",$8,"hours,",$9,"minutes."}'
25 days, 21 hours, 34 minutes.
How it works
-F'( |,|:)+'
awk divides its input up into fields. This tells awk to use any combination of one or more of space, comma, or colon as the field separator.
print $6,$7",",$8,"hours,",$9,"minutes."
This tells awk to print the sixth field and seventh fields (separated by a space) followed by a comma, the 8th field, the string hours, the ninth field, and, lastly, the string minutes..
Handling computers with short uptimes using sed
Starting from a reboot, my uptime produces output like:
03:14:20 up 1 min, 2 users, load average: 2.28, 1.29, 0.50
04:12:29 up 59 min, 5 users, load average: 0.06, 0.08, 0.48
05:14:09 up 2:01, 5 users, load average: 0.13, 0.10, 0.45
03:13:19 up 1 day, 0 min, 8 users, load average: 0.01, 0.04, 0.05
04:13:19 up 1 day, 1:00, 8 users, load average: 0.02, 0.05, 0.21
12:49:10 up 25 days, 21:30, 28 users, load average: 0.50, 0.66, 0.52
The following sed command handles these formats:
uptime | sed -E 's/^[^,]*up *//; s/, *[[:digit:]]* users.*//; s/min/minutes/; s/([[:digit:]]+):0?([[:digit:]]+)/\1 hours, \2 minutes/'
With the above times, this produces:
1 minutes
59 minutes
2 hours, 1 minutes
1 day, 0 minutes
1 day, 1 hours, 0 minutes
25 days, 21 hours, 30 minutes
How it works
-E turns on extended regular expression syntax. (On older GNU seds, use -r in place of -E)
s/^[^,]*up *//
This substitutes command removes all text up to up.
s/, *[[:digit:]]* users.*//
This substitute command removes the user count and all text which follows it.
s/min/minutes/
This replaces min with minutes.
s/([[:digit:]]+):0?([[:digit:]]+)/\1 hours, \2 minutes/'
If the line contains a time in the hh:mm format, this separates the hours from the minutes and replaces it with hh hours, mm minutes.
Handling computers with short uptimes using awk
uptime | awk -F'( |,|:)+' '{d=h=m=0; if ($7=="min") m=$6; else {if ($7~/^day/) {d=$6;h=$8;m=$9} else {h=$6;m=$7}}} {print d+0,"days,",h+0,"hours,",m+0,"minutes."}'
On the same test cases as above, this produces:
0 days, 0 hours, 1 minutes.
0 days, 0 hours, 59 minutes.
0 days, 2 hours, 1 minutes.
1 days, 0 hours, 0 minutes.
1 days, 1 hours, 0 minutes.
25 days, 21 hours, 30 minutes.
For those who prefer awk code spread out over multiple lines:
uptime | awk -F'( |,|:)+' '{
d=h=m=0;
if ($7=="min")
m=$6;
else {
if ($7~/^day/) { d=$6; h=$8; m=$9}
else {h=$6;m=$7}
}
}
{
print d+0,"days,",h+0,"hours,",m+0,"minutes."
}'

Just vor completeness... what's about:
$ uptime -p
up 2 weeks, 3 days, 14 hours, 27 minutes

Solution: In order to get the linux uptime in seconds, Go to bash and type cat /proc/uptime.Parse the first number and convert it according to your requirement.
From RedHat documentation:
This file contains information detailing how long the system has been on since its last restart. The output of /proc/uptime is quite minimal:
350735.47 234388.90
The First number is the total number of seconds the system has been
up.
The Second number is how much of that time the machine has spent
idle, in
seconds.

I made a universal shell script, for systems which support uptime -p like newer linux and for those that don't, like Mac OS X.
#!/bin/sh
uptime -p >/dev/null 2>&1
if [ "$?" -eq 0 ]; then
# Supports most Linux distro
# when the machine is up for less than '0' minutes then
# 'uptime -p' returns ONLY 'up', so we need to set a default value
UP_SET_OR_EMPTY=$(uptime -p | awk -F 'up ' '{print $2}')
UP=${UP_SET_OR_EMPTY:-'less than a minute'}
else
# Supports Mac OS X, Debian 7, etc
UP=$(uptime | sed -E 's/^[^,]*up *//; s/mins/minutes/; s/hrs?/hours/;
s/([[:digit:]]+):0?([[:digit:]]+)/\1 hours, \2 minutes/;
s/^1 hours/1 hour/; s/ 1 hours/ 1 hour/;
s/min,/minutes,/; s/ 0 minutes,/ less than a minute,/; s/ 1 minutes/ 1 minute/;
s/ / /; s/, *[[:digit:]]* users?.*//')
fi
echo "up $UP"
Gist
Referenced John1024 answer with my own customizations.

For this:
0 days, 0 hours, 1 minutes.
0 days, 0 hours, 59 minutes.
0 days, 2 hours, 1 minutes.
1 days, 0 hours, 0 minutes.
1 days, 1 hours, 0 minutes.
25 days, 21 hours, 30 minutes
More simple is:
uptime -p | cut -d " " -f2-

For the sake of variety, here's an example with sed:
My raw output:
$ uptime
15:44:56 up 3 days, 22:58, 7 users, load average: 0.48, 0.40, 0.31
Converted output:
$uptime|sed 's/.*\([0-9]\+ days\), \([0-9]\+\):\([0-9]\+\).*/\1, \2 hours, \3 minutes./'
3 days, 22 hours, 58 minutes.

This answer is pretty specific for the uptime shipped in OS X, but takes into account any case of output.
#!/bin/bash
INFO=`uptime`
echo $INFO | awk -F'[ ,:\t\n]+' '{
msg = "↑ "
if ($5 == "day" || $5 == "days") { # up for a day or more
msg = msg $4 " " $5 ", "
n = $6
o = $7
} else {
n = $4
o = $5
}
if (int(o) == 0) { # words evaluate to zero
msg = msg int(n)" "o
} else { # hh:mm format
msg = msg int(n)" hr"
if (n > 1) { msg = msg "s" }
msg = msg ", " int(o) " min"
if (o > 1) { msg = msg "s" }
}
print "[", msg, "]"
}'
Some example possible outputs:
22:49 up 24 secs, 2 users, load averages: 8.37 2.09 0.76
[ ↑ 24 secs ]
22:50 up 1 min, 2 users, load averages: 5.59 2.39 0.95
[ ↑ 1 min ]
23:39 up 51 mins, 3 users, load averages: 2.18 1.94 1.74
[ ↑ 51 mins ]
23:54 up 1:06, 3 users, load averages: 3.67 2.57 2.07
[ ↑ 1 hr, 6 mins ]
16:20 up 120 days, 10:46, 3 users, load averages: 1.21 2.88 0.80
[ ↑ 120 days, 10 hrs, 46 mins ]

uptime_minutes() {
set `uptime -p`
local minutes=0
shift
while [ -n "$1" ]; do
case $2 in
day*)
((minutes+=$1*1440))
;;
hour*)
((minutes+=$1*60))
;;
minute*)
((minutes+=$1))
;;
esac
shift
shift
done
echo $minutes
}

Related

Get date with same day in month

I want to get all dates with the same day of week.
inputDate="2021/08/25"
That means I should get all the same day of week as inputDate.
outputDates="2021/08/04,2021/08/11,2021/08/18,2021/08/25"
I only got this so far..
inputDate="2021/08/25"
dd=$(date -d "$inputDate" +"%Y/%m/%d")
So what I'm planning is to do "date -7" and loop 5 times forward and backward and collect it then check if value of month is still the same with inputDate if not then drop it
Do you have any way to do this?
Using only shell, the easyest way to get all weekdays from a month is by using cal command:
cal -n1 8 2021
outputs:
August 2021
Su Mo Tu We Th Fr Sa
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Then you can filter using sed, awk or other tools to reach your goal.
Example:
year=2021
month=8
day=25
weekday_number="$(date -d$year-$month-$day +%w)"
cn=$(($weekday_number + 1))
cal -n1 $month $year |
sed -r 's/(..)\s/\1\t/g;s/ +//g' |
awk -v cn=$cn -F'\t' 'NR<3 || $cn == "" {next} {print $cn}' |
while read wday; do
echo $year/$month/$wday
done
outputs:
2021/8/4
2021/8/11
2021/8/18
2021/8/25
Without using cal or ncal...
#!/bin/bash
inputDate="2021/08/25"
dow=$(date -d "$inputDate" +"%a")
month=$(date -d "$inputDate" +"%m")
outputDates=""
for x in $(seq 0 9)
do
validDate=$(date -d "$x $dow 5 week ago" +"%Y/%m/%d" | grep "/$month/")
if [ ! -z $validDate ]
then
if [ ! -z $outputDates ]
then
outputDates="$outputDates,$validDate"
else
outputDates="$validDate"
fi
fi
done
echo "$outputDates"
This script outputs:
2021/08/04,2021/08/11,2021/08/18,2021/08/25

bash script to get the spent time from a file

I have a log file that shows switch time between my scripts:
Tue Oct 24 11:57:54 IRST 2017 Script switched from abc to XYZ
Tue Oct 24 14:03:41 IRST 2017 Script switched from XYZ to ZEN
Tue Oct 24 15:43:16 IRST 2017 Script switched from ZEN to XYZ
Tue Oct 24 17:07:25 IRST 2017 Script switched from XYZ to ZEN
Tue Oct 24 18:40:48 IRST 2017 Script switched from ZEN to XLS
Tue Oct 24 19:52:26 IRST 2017 Script switched from XLS to XYZ
Tue Oct 24 20:20:30 IRST 2017 Script switched from XYZ to ZEN
Tue Oct 24 20:36:06 IRST 2017 Script switched from ZEN to XLS
Tue Oct 24 21:01:03 IRST 2017 Script switched from XLS to XYZ
Tue Oct 24 21:47:47 IRST 2017 Script switched from XYZ to ZEN
How do I get total time spent on each script with bash
So the output shows like this:
abc 2 hours 30 min 40 sec
XYZ 3 hours 23 min 45 sec
zen ...
XLS ...
Assuming you have a log file named test.txt, following script should work,
#!/bin/bash
dtime=0
sname=""
while read line
do
_dtime=$(echo "$line" | awk '{print $1,$2,$3,$4}')
_sname=$(echo "$line" | awk '{print $10}')
_dtimesec=$(date +%s -d "$_dtime")
_timediff=$(( _dtimesec - dtime ))
[ "x$sname" != "x" ] && printf "$sname %d hours %d minutes %d seconds\n" $(($_timediff/3600)) $(($_timediff%3600/60)) $(($_timediff%60))
dtime=$_dtimesec
sname=$_sname
done < test.txt
This will produce an output like the following:
]$ ./test
abc 2 hours 5 minutes 47 seconds
XYZ 1 hours 39 minutes 35 seconds
ZEN 1 hours 24 minutes 9 seconds
XYZ 1 hours 33 minutes 23 seconds
ZEN 1 hours 11 minutes 38 seconds
XLS 0 hours 28 minutes 4 seconds
XYZ 0 hours 15 minutes 36 seconds
ZEN 0 hours 24 minutes 57 seconds
XLS 0 hours 46 minutes 44 seconds
EDIT
In order to find total amount of time spent by each script, this modified script should do the job:
#!/bin/bash
dtime=0
sname=""
namearr=()
timearr=()
while read line
do
_dtime=$(echo "$line" | awk '{print $1,$2,$3,$4}')
_sname=$(echo "$line" | awk '{print $10}')
_dtimesec=$(date +%s -d "$_dtime")
_timediff=$(( _dtimesec - dtime ))
_rc=1
for n in "${!namearr[#]}"
do
if [ "${namearr[$n]}" == "$_sname" ]; then
export _rc=$?
export ind=$n
break;
else
export _rc=1
fi
done
if [ $_rc -eq 0 ]; then
timearr[$ind]=$(( ${timearr[$ind]} + _timediff ))
else
if [ $dtime -eq 0 ] && [ "x$sname" == "x" ]; then
:
else
namearr+=($_sname)
timearr+=($_timediff)
fi
fi
dtime=$_dtimesec
sname=$_sname
done < test.txt
echo "Total time spent by each script:"
echo
for i in "${!namearr[#]}"
do
_gtime=${timearr[$i]}
printf "${namearr[$i]} %d hours %d minutes %d seconds\n" $(($_gtime/3600)) $(($_gtime%3600/60)) $(($_gtime%60))
done
Result:
$ ./test
Total time spent by each script:
XYZ 4 hours 44 minutes 44 seconds
ZEN 3 hours 28 minutes 34 seconds
XLS 1 hours 36 minutes 35 seconds
You can use the following gawk program:
time_spent.awk
BEGIN {
months["Jan"] = "01"
months["Feb"] = "02"
months["Mar"] = "03"
months["Apr"] = "04"
months["May"] = "05"
months["Jun"] = "06"
months["Jul"] = "07"
months["Aug"] = "08"
months["Seb"] = "09"
months["Oct"] = "10"
months["Nov"] = "11"
months["Dec"] = "12"
}
{
split($4, time, ":")
# mktime() manual: https://www.gnu.org/software/gawk/manual/html_node/Time-Functions.html
now = mktime($6" "months[$2]" "$3" "time[1]" "time[2]" "time[3])
prv = $(NF-2)
cur = $(NF)
start[cur] = now
spent[prv]+=start[prv]?now-start[prv]:0
}
END {
for(i in spent) {
printf "%s seconds spent in %s\n", spent[i], i
}
}
Save it into a file time_spent.awk and execute it like this:
gawk -f time_spent.awk input.log
Output from the above example:
5795 seconds spent in XLS
0 seconds spent in abc
17084 seconds spent in XYZ
12514 seconds spent in ZEN
#!/usr/bin/env python
import sys
from time import strptime
from datetime import datetime
intervals = (
('weeks', 604800), # 60 * 60 * 24 * 7
('days', 86400), # 60 * 60 * 24
('hours', 3600), # 60 * 60
('minutes', 60),
('seconds', 1),
)
def display_time(seconds, granularity=2):
result = []
for name, count in intervals:
value = seconds // count
if value:
seconds -= value * count
if value == 1:
name = name.rstrip('s')
result.append("{} {}".format(value, name))
return ' '.join(result[:granularity])
with open(sys.argv[1], "rb") as df:
lines = df.readlines()
totals = {}
for i in range(len(lines)-1):
(_,t1,t2,t3,_,t4,_,_,_,_,_,scr) = lines[i].strip().split(' ')
st = datetime.strptime(' '.join([t1,t2,t3,t4]), "%b %d %H:%M:%S %Y")
(_,t1,t2,t3,_,t4,_,_,_,_,_,_) = lines[i+1].strip().split(' ')
et = datetime.strptime(' '.join([t1,t2,t3,t4]), "%b %d %H:%M:%S %Y")
if scr not in totals:
totals[scr] = 0
totals[scr] += (et-st).seconds
print("{} {}".format(scr,display_time((et-st).seconds, 3)))
print("\nTotals:")
for scr in totals:
print("{} {}".format(scr,display_time(totals[scr], 3)))
Here is the output, assuming your times are in a file named logfile:
$ ./times.py logfile
XYZ 2 hours 5 minutes 47 seconds
ZEN 1 hour 39 minutes 35 seconds
XYZ 1 hour 24 minutes 9 seconds
ZEN 1 hour 33 minutes 23 seconds
XLS 1 hour 11 minutes 38 seconds
XYZ 28 minutes 4 seconds
ZEN 15 minutes 36 seconds
XLS 24 minutes 57 seconds
XYZ 46 minutes 44 seconds
Totals:
XLS 1 hour 36 minutes 35 seconds
XYZ 4 hours 44 minutes 44 seconds
ZEN 3 hours 28 minutes 34 seconds
$
Note: I lifted the handy display_time function from here: Python function to convert seconds into minutes, hours, and days.

How to add serial number in shell

I would like to add serial numbers to the following print statement.
for i in 1 5 20 50 100 200 500 1000;do
#I have here some computations for each i, finally mean_${i}=xyz
#for example
mean_1=10.42
mean_5=12.43
mean_20=25.34
mean_50=59.34
mean_100=150.32
mean_200=378.43
mean_500=697.45
mean_1000=1233.54
printf "%5s %10s %10s\n" sl.No. "$i" "mean_${i}" >> ofile.txt
done
I can't able to add the serial numbers.
Desired output
ofile.txt
1 1 10.42
2 5 12.43
3 20 25.34
4 50 59.34
5 100 150.32
6 200 378.43
7 500 697.45
8 1000 1233.54
Assuming you mean "line numbers" -- NR refers to the current line number in awk; however, while awk is frequently used from shell, it is its own independent programming language with its own syntax.
Maintaining an explicit counter is the typical practice, as in the case of ln below:
mean_1=10.42
mean_5=12.43
mean_20=25.34
mean_50=59.34
mean_100=150.32
mean_200=378.43
mean_500=697.45
mean_1000=1233.54
ln=0
for i in 1 5 20 50 100 200 500 100; do
meanvar=mean_$i
printf '%5s %10s %10s\n' "$((++ln))" "$i" "${!meanvar}"
done

Linux sed doesn't replace comma

uptime=$(uptime | sed 's/^.*up//;s/:/ hours and /; s/, load/ minutes, load/g')
The output is:
2 days, 3 hours and 41, load average: 0.04, 0.07, 0.10
I want the output to be like this:
2 days, 3 hours and 41 minutes, load average: 0.04, 0.07, 0.10
How do I do that with sed ?
You can use grouping to capture the hours/mins directly:
echo $(uptime | sed 's/^.*up//;s/\([0-9][0-9]*\):\([0-9][0-9]\)*/\1 hours and \2 minutes/')
You may need to use 2 sed commands:
uptime=$(uptime | sed 's/^.*up//;s/:/ hours and /' |sed 's/,\s\+load/ minutes, load/g' )
Actually it works with only one, it probably had more than one space:
uptime | sed 's/^.*up//;s/:/ hours and /; s/,\s\+load/ minutes, load/g'

print $i unles $i is less than 10. using awk or otherwise

I have some data with a series of values on each line like this:
49.01024263 49.13389087 49.38177387 (more numbers...)
42.71585143 43.48711477 44.25625756 (ect..)
43.18826160 43.15332580 43.13094893
30.69076014 28.74489096 26.85725970
eventually the numbers reach values less than 10, at that point I'd like to delete all the remaining numbers in that line.
so far I have this, but its returning several errors.
awk '{for (i=1;i++)do{if ($i > 10.0 ) print $i ; next ; else ; exit}}' input > output
What could I be doing wrong?
Any better ways to carry out this task?
try this line:
awk '{for(i=1;i<=NF;i++)if($i>10)printf "%s ",$i;else break;print ""}' file
test with an example:
kent$ cat f
30 20 15 9 8
50 40 30 20 7 2000
100 200 300 400 5 444
kent$ awk '{for(i=1;i<=NF;i++)if($i>10)printf "%s ",$i;else break;print ""}' f
30 20 15
50 40 30 20
100 200 300 400

Resources