I'll add an Awk approach. First I'll directly answer your question:
uptime |
awk 'BEGIN {FS=","} # Use commas as the field separator
function trim(s,d) {gsub("(^ +| +$|"d")", "", s); return s} # Function to trim spaces and delete pattern d in input s
{
days=trim($1, ".* up ") # Trim spaces and remove everything before and including " up " on 1st field
h_m=trim($2) # Trim spaces on 2nd field
users=trim($3) # . . .
l1=trim($4, ".*: ")
l2=trim($5)
l3=trim($6)
printf "%s %s,%s,%s %s %s,\n", days, h_m, users, l1, l2, l3
}'
So, the one-liner with most unnecessary whitespace, comments, and variables removed; trim renamed to t; and not stripping (non-existent) trailing spaces would be:
uptime | awk 'BEGIN{FS=","}function t(s,d){gsub("(^ +|"d")","",s);return s}{printf "%s %s,%s,%s %s %s,\n",t($1,".* up "),t($2),t($3),t($4,".*: "),t($5),t($6)}'
Also there's the sed-only approach:
uptime | sed -E -e 's/(.* up|[^,]*: )//g' -e 's/, +/,/g' -e 's/(([0-9]+ days),)?([^,]*),/\2 \3,/' -e 's/^ +//' -e 's/([^,]+),([^,]+),([^,]+)$/\1 \2 \3,/'
Uptime Only
For anyone like me who only cares about the uptime in this case, here's the way to get output that will look like uptime: 32d 7h 9m (at 13:38:22). The values for time, days, hours, and minutes are stored in variables, so you can adjust the printf statement at the end to make it print however you want.
NOTE: This is more or less an example of how you would parse the output of uptime in Awk to get day, hour, minute, and time variables to use. This can be expanded upon to include the rest of the uptime output. You can see it gets a bit complex because uptime is fancy and will output 7 min instead of 00:07 if it detects that, etc. Skip below if you just want uptime in a smallish command.
uptime |
awk 'BEGIN {FS=","} # Use commas as the field separator
# Function to trim spaces and delete pattern d in input s
function trim(s,d) {gsub("(^ +| +$|"d")", "", s); return s}
# Function to set hours and mins variables from time in x:xx format (split on : then trim leading 0 from mins)
function CoSp(s) {split(trim(s), B, /:/); sub(/^0/, "", B[2]); if(B[2]==""){B[2]=0} hours=B[1]; mins=B[2]}
{
up=" up "; # Set variable for long pattern that is needed frequently
days=0; hours=0; mins=0 # Set variables so that printf is more readable
if ($1 ~ /.*days.*/) { # If it has days
split(trim($1, " days"), A, up) # Trim spaces and remove " days" on 1st field and split on " up "
days=A[2]
if ($2 ~ /hrs/) { # If only hours
hours=trim($2, " hrs")
} else if ($2 ~ /min/) { # If only minutes
mins=trim($2, " min")
} else { # If has hours and minutes
CoSp($2)
}
} else if ($1 ~ /hrs/) { # If only hours
split(trim($1, " hrs"), A, up)
hours=A[2]
} else if ($1 ~ /min/) { # If only minutes
split(trim($1, " min"), A, up)
mins=A[2]
} else {
split(trim($1), A, up)
CoSp(A[2])
}
time=A[1];
printf "uptime: %sd %sh %sm (at %s)\n", days, hours, mins, time
}'
You can get it smaller if you don't care about capturing the time, but at that point it's better to just calculate based off /proc/uptime (as referenced in another answer). This will get you the uptime in the form uptime: 27d 9h 13m 13s 320ms:
awk 'FNR==1{ # Only process 1st line of file
seconds=$1
ms=seconds*1000%1000
secs=int(seconds%60)
mins=int(seconds/60)
hours=int(mins/60)
days=int(hours/24)
hours=hours%24
mins=mins%60
printf "uptime: %sd %sh %sm %ss %sms\n", days, hours, mins, secs, ms
}' /proc/uptime
This is the one-liner for that, without unnecessary whitespace and comments:
awk 'FNR==1{ms=$1*1000%1000;secs=int($1%60);mins=int($1/60);hours=int(mins/60);days=int(hours/24);hours=hours%24;mins=mins%60;printf "uptime: %sd %sh %sm %ss %sms\n", days, hours, mins, secs, ms}' /proc/uptime
And if you can safely assume that /proc/uptime only ever has 1 line, you reduce the variables to just their first letter, and you modify the math a bit, you get this one-liner:
awk '{ms=$1*1000%1000;s=int($1%60);m=int($1/60%60);h=int($1/3600%24);d=int($1/86400);printf "uptime: %sd %sh %sm %ss %sms\n", d, h, m, s, ms}' /proc/uptime
And you can always remove any calculations you don't care about. For example if you want to remove milliseconds and round seconds (instead of just removing the int() from around seconds and leaving it as a float):
awk '{s=int($1%60+.5);m=int($1/60%60);h=int($1/3600%24);d=int($1/86400);printf "uptime: %sd %sh %sm %ss %sms\n", d, h, m, s}' /proc/uptime