Get avg of cpu temp's as a one line script - bash

I need to average the temperatures of the four cpu cores on my system. I am obtaining the temperatures of the individual cores using the command:
sysctl -a | awk '/temperature/ {print $2;}'
This spits out the following output:
53.0C
53.0C
52.0C
52.0C
I then pass this to sed and tr and with some script-fu I ended up with the following-one liner:
echo `sysctl -a | awk '/temperature/ {print $2;}' | sed s/C// | tr '\n' '+' | sed 's/\(.*\)+/\1/'` | bc`
which then results in:
210
I now simply need to divide 210/4 to get my average but am stumped on how to achieve this as an extension to the one-liner that I have already brewed up. And due to some other constraints, I need to keep this as a one-liner.
I am sure there's a simpler way to achieve what I am after, any pointers are appreciated!

With awk:
sysctl -a | awk -F '[ C]' '/temperature/{sum+=$2} END{print sum/NF}'
Output:
52.5
See: 8 Powerful Awk Built-in Variables – FS, OFS, RS, ORS, NR, NF, FILENAME, FNR

You can add parenthesis and division by 4 around your expression with:
{ echo -n "("; tr '\n' '+'; echo -n")/4"; }
The final result is:
echo `sysctl -a | awk '/temperature/ {print $2;}' | sed s/C// | { echo -n "("; tr '\n' '+'; echo -n")/4"; } | sed 's/\(.*\)+/\1/'` | bc`

Related

Print output of two commands in one line

I'm got this working:
while sleep 5s
do
lscpu | grep 'CPU MHz:' | cut -d ':' -f 2 | awk '{$1=$1};1' && grep 'cpu ' /proc/stat | awk '{usage=($2+$4)*100/($2+$4+$5)} END {print usage "%"}'
done
And it gives me the following output:
1601.058
3.4811%
1452.514
3.48059%
1993.800
3.48006%
2085.585
3.47955%
2757.776
3.47902%
1370.237
3.47851%
1497.903
3.47798%
But I'd really like to get the two values onto a single line. Every time I try to do this I run into a double / single quote variable issue. Granted I pulled some of this awk stuff from online so I'm not really up to speed on that. I just want to print per line, CPU clock and load ever 5 seconds.
Can you help me find a better way to do that?
You may use process substitution to run lscpu and cat /proc/stat and feed to single command. No need to use pipes.
while sleep 5; do
awk '/CPU MHz:/{printf "%s ", $NF} /cpu /{print ($2+$4)*100/($2+$4+$5)"%"}' <(lscpu) /proc/stat
done
If there is only one input command:
date| awk '{print $1}'
Wed
OR
awk '{print $NF}' <(date)
2019
If more then one command: Example , get the year of of the two date command in same line. (not very useful example, only for sake of demo)
awk '{printf "%s ", $1=NF}END{print ""}' <(date) <(date)
2019 2019
pipe the output of the 2 commands into paste
while sleep 5; do
lscpu | awk -F':[[:blank:]]+' '$1 == "CPU MHz" {print $2}'
awk '$1 == "cpu" {printf "%.4f%%\n", ($2+$4)*100/($2+$4+$5)}' /proc/stat
done | paste - -
The 2 columns will be separated by a tab.
Writing this for readability rather than efficiency, you might consider something like:
while sleep 5; do
cpu_pct=$(lscpu | awk -F': +' '/CPU MHz:/ { print $2 }')
usage=$(awk '/cpu / {usage=($2+$4)*100/($2+$4+$5)} END {print usage "%"}' /proc/stat)
printf '%s\n' "$cpu_pct $usage"
done
Command substitutions implicitly trim trailing newlines, so if lscpu | awk has output that ends in a newline, var=$(lscpu | awk) removes it; thereafter, you can use "$var" without that newline showing up.
All you need to do is change the newline on the first line to a different separator. Something like:
lscpu | ... | tr \\n : && grep ...
You can also use echo -n $(command_with_stdout). The -n switch specifies that the new line (\n) will be omitted.
while sleep 5s; do
echo -n $( lscpu | grep 'CPU MHz:' | cut -d ':' -f 2 | awk '{$1=$1};1' )
echo -n ' **** '
echo $( grep 'cpu ' /proc/stat | awk '{usage=($2+$4)*100/($2+$4+$5)} END {print usage "%"}' )
done
Or the same representation in one line:
while sleep 5s; do echo -n $( lscpu | grep 'CPU MHz:' | cut -d ':' -f 2 | awk '{$1=$1};1' ); echo -n ' **** '; echo $( grep 'cpu ' /proc/stat | awk '{usage=($2+$4)*100/($2+$4+$5)} END {print usage "%"}' ); done
EDIT: (remove -n switch from echo according to Charles Duffy's comment)
while sleep 5s; do echo "$( lscpu | grep 'CPU MHz:' | cut -d ':' -f 2 | awk '{$1=$1};1' ) **** $( grep 'cpu ' /proc/stat | awk '{usage=($2+$4)*100/($2+$4+$5)} END {print usage "%"}' )"; done

Count the number of whitespaces in a file

File test
musically us
challenged a goat that day
spartacus was his name
ba ba ba blacksheep
grep -oic "[\s]*" test
grep -oic "[ ]*" test
grep -oic "[\t]*" test
grep -oic "[\n]*" test
All give me 4, when I expect 11
grep --version -> grep (BSD grep) 2.5.1-FreeBSD
Running this on OSX Sierra 10.12
Repeating spaces should not be counted as one space.
If you are open to tricks and alternatives you might like this one:
$ awk '{print --NF}' <(tr -d '\n' <file)
11
Above solution will count "whitespace" between words. As a result for a string of 'fifteen--> <--spaces' awk will measure 1, like grep.
If you need to count actual single spaces you can use this :
$ awk -F"[ ]" '{print --NF}' <<<"fifteen--> <--spaces"
15
$ awk -F"[ ]" '{print --NF}' <<<" 2 4 6 8 10"
10
$ awk -F"[ ]" '{print --NF}' <(tr -d '\n' <file)
11
One step forward, to count single spaces and tabs:
$ awk -F"[ ]|\t" '{print --NF}' <(echo -e " 2 4 6 8 10\t12 14")
13
tr is generally better for this (in most cases):
tr -d -C ' ' <file | wc -c
The grep solution relies on the fact that the output of grep -o is newline-separated — it will fail miserably for example in the following type of circumstance where there might be multiple spaces:
v='fifteen--> <--spaces'
echo "$v" | grep -o -E ' +' | wc -l
echo "$v" | tr -d -C ' ' | wc -c
grep only returns 1, when it should be 15.
EDIT: If you wanted to count multiple characters (eg. TAB and SPACE) you could use:
tr -dC $'[ \t]' <<< $'one \t' | wc -c
Just use awk:
$ awk -v RS=' ' 'END{print NR-1}' file
11
or if you want to handle empty files gracefully:
$ awk -v RS=' ' 'END{print NR - (NR?1:0)}' /dev/null
0
The -c option counts the number of lines that match, not individual matches. Use grep -o and then pipe to wc -l, which will count the number of lines.
grep -o ' ' test | wc -l

Extract one number from string in bash

I have this string:
1024.00 MB transferred (912.48 MB/sec)
and I need to get only the number 912.48 and transform it in 912,48 with a bash script.
I tried to do sed 's/[^0-9.]*//g' but in this way i get 1024.00 912.18.
How can I do it?
So far, every answer here is using external tools (sed, awk, grep, tr, etc) rather than sticking to native bash functionality. Since spinning up external processes has a significant constant-time performance impact, it's generally undesirable when only processing a single line of content (for long streams of content, an external tool will often be more efficient).
This one uses built-ins only:
# one-time setup: set the regex
re='[(]([0-9.]+) MB/sec[)]'
string='1024.00 MB transferred (912.48 MB/sec)'
if [[ $string =~ $re ]]; then # run enclosed code only if regex matches
val=${BASH_REMATCH[1]} # refer to first (and only) match group
val_with_comma=${val//./,} # replace "." with "," in that group
echo "${val_with_comma}" # ...and emit our output
fi
...yielding:
912,48
A combination of awk and sed:
str='1024.00 MB transferred (912.48 MB/sec)'
echo "$str" | awk '{print $4}' | sed 's/(//;s/\./,/'
912,48
Or entirely with awk:
echo "$str" | awk '{sub("[(]","");sub("[.]",",");print $4}'
this should work
$ sed -r 's/.*\(([0-9.]+).*/\1/;s/\./,/'
echo "1024.00 MB transferred (912.48 MB/sec)" | cut -f2 -d'(' | cut -f1 -d' ' | sed 's/\./,/'
echo "1024.00 MB transferred (912.48 MB/sec)" | cut -d " " -f4 | tr "." "," | tr -d "("
Another of the near-infinite possibilities:
read x y < <(tr -dc '[0-9. ]' <<< "1024.00 MB transferred (912.48 MB/sec)")
echo ${y}
or
grep -oP '(?<=\()[\d.]+' <<< "1024.00 MB transferred (912.48 MB/sec)"
Here is an awk to get the job done:
s='1024.00 MB transferred (912.48 MB/sec)'
awk -F '[() ]+' '{sub(/\./, ",", $4); print $4}' <<< "$s"
912,48
wow, so many answers :)
here's mine, should be pretty fast:
grep -o '([^ ]\+' | tail -c+2 | tr '.' ','

Bash math - Dividing a bunch of rows using for statement

Example file:
25 Firstname1 Lastname1 domain1.com planname #1.00 USD Monthly Active 04/24/2016 Edit
1068 Firstname2 Lastname2 domain2.com planname #7.95 USD Annually Active 05/09/2016 Edit
3888 Firstname3 Lastname3 domain3.com planname #19.95 USD Biennially Active 05/04/2016 Edit
I am extracting just the price and billing cycle and am converting the billing cycles into numerical value this way I can divide the price by the billing cycle to get a cost per month.
When using the for statement, its adding line breaks which is breaking the math.
Code:
for i in `cat asd | cut -d "#" -f 2 | awk '{print $1, $3}' | sed 's/Monthly/\/ 1/g' | sed 's/Annually/\/ 12/g' | sed 's/Biennially/\/ 24/g' |grep -Ev 0.00` ; do echo $i | bc -l' ; done
I would prefer to be able to get 1 answer meaning all the rows get divided up then added together to get one final answer.
All those calls to cat, cut, awk, sed, grep and bc - what a waste.
This is a mis-named post, because you are not using Bash to do any calculations. The reason is that bash, unlike korn shell (ksh), does not support floating point. So you fall back to utilities like bc. Hold on though, awk supports floating point as well.
awk is a programming language in its own right. This just uses one instance of awk. I have embedded it inside a bash script because you are probably doing other stuff, but with a little adjustment it could be stand-alone with #!/bin/awk at the top:
infile='asd'
# -f - means "read the program from stdin"
# << '_END_' is a here document. Redirect stdin from here to the label _END_
awk -f - "$infile" << '_END_'
BEGIN {
# an associative array for the billing cycles
cycles["Monthly"] = 1
cycles["Annually"] = 12
cycles["Biennially"] = 24
}
{
sub(/#/,"",$6) # Remove the # from the amount
total += $6/cycles[$8] # divide amount by the billing cycle, add to total
}
END { print total }
_END_
Don't you think this is simpler to understand and maintain? It's also more efficient. This awk script is probably a good exercise for an awk 101 training course.
You could do something like this: (If you are totally set on a single line)
cat asd | cut -d "#" -f 2 | awk '{print $1, $3}' | sed 's/Monthly/\/ 1/g' | sed 's/Annually/\/ 12/g' | sed 's/Biennially/\/ 24/g' | grep -Ev 0.00 | while IFS= read -r line; do echo "$line" | bc -l; done | tr '\n' '+' | sed 's/+$/\n/' | bc -l
But this would be way more clear:
tmp=$(mktemp)
cat asd | cut -d "#" -f 2 | awk '{print $1, $3}' | sed 's/Monthly/\/ 1/g' | sed 's/Annually/\/ 12/g' | sed 's/Biennially/\/ 24/g' | grep -Ev 0.00 > $tmp
tmp2=$(mktemp)
cat $tmp | while IFS= read -r line; do
echo "$line" | bc -l >> $tmp2
done
# Actual output
cat $tmp2 | tr '\n' '+' | sed 's/+$/\n/' | bc -l
rm $tmp $tmp2

Use Awk to extract substring

Given a hostname in format of aaa0.bbb.ccc, I want to extract the first substring before ., that is, aaa0 in this case. I use following awk script to do so,
echo aaa0.bbb.ccc | awk '{if (match($0, /\./)) {print substr($0, 0, RSTART - 1)}}'
While the script running on one machine A produces aaa0, running on machine B produces only aaa, without 0 in the end. Both machine runs Ubuntu/Linaro, but A runs newer version of awk(gawk with version 3.1.8 while B with older awk (mawk with version 1.2)
I am asking in general, how to write a compatible awk script that performs the same functionality ...
You just want to set the field separator as . using the -F option and print the first field:
$ echo aaa0.bbb.ccc | awk -F'.' '{print $1}'
aaa0
Same thing but using cut:
$ echo aaa0.bbb.ccc | cut -d'.' -f1
aaa0
Or with sed:
$ echo aaa0.bbb.ccc | sed 's/[.].*//'
aaa0
Even grep:
$ echo aaa0.bbb.ccc | grep -o '^[^.]*'
aaa0
Or just use cut:
echo aaa0.bbb.ccc | cut -d'.' -f1
I am asking in general, how to write a compatible awk script that
performs the same functionality ...
To solve the problem in your quesiton is easy. (check others' answer).
If you want to write an awk script, which portable to any awk implementations and versions (gawk/nawk/mawk...) it is really hard, even if with --posix (gawk)
for example:
some awk works on string in terms of characters, some with bytes
some supports \x escape, some not
FS interpreter works differently
keywords/reserved words abbreviation restriction
some operator restriction e.g. **
even same awk impl. (gawk for example), the version 4.0 and 3.x have difference too.
the implementation of certain functions are also different. (your problem is one example, see below)
well all the points above are just spoken in general. Back to your problem, you problem is only related to fundamental feature of awk. awk '{print $x}' the line like that will work all awks.
There are two reasons why your awk line behaves differently on gawk and mawk:
your used substr() function wrongly. this is the main cause. you have substr($0, 0, RSTART - 1) the 0 should be 1, no matter which awk do you use. awk array, string idx etc are 1-based.
gawk and mawk implemented substr() differently.
You don't need awk for this...
echo aaa0.bbb.ccc | cut -d. -f1
cut -d. -f1 <<< aaa0.bbb.ccc
echo aaa0.bbb.ccc | { IFS=. read a _ ; echo $a ; }
{ IFS=. read a _ ; echo $a ; } <<< aaa0.bbb.ccc
x=aaa0.bbb.ccc; echo ${x/.*/}
Heavier options:
sed:
echo aaa0.bbb.ccc | sed 's/\..*//'
sed 's/\..*//' <<< aaa0.bbb.ccc
awk:
echo aaa0.bbb.ccc | awk -F. '{print $1}'
awk -F. '{print $1}' <<< aaa0.bbb.ccc
You do not need any external command at all, just use Parameter Expansion in bash:
hostname=aaa0.bbb.ccc
echo ${hostname%%.*}
if you don't want to change the input field separator, then it's possible to use split function:
echo "some aaa0.bbb.ccc text" | awk '{split($2, a, "."); print a[1]}'
documentation:
split(string, array [, fieldsep [, seps ] ])
Divide string into pieces separated by fieldsep
and store the pieces in array and the separator
strings in the seps array.
awk is still the cleanest approach :
mawk NF=1 FS='[.]' <<< aaa0.bbb.ccc
aaa0
If there's stuff before or after :
mawk ++NF FS='[.].+$|^[^ ]* ' OFS= <<< 'some aaa0.bbb.ccc text'
mawk '$!NF=$2' FS='[ .]' <<< 'some aaa0.bbb.ccc text'
aaa0

Resources