0

I am monitoring CPU usage and based on that I want to display message. I have written following script:

#!/bin/bash

typeset -F limit1=1.0  # Declare variables as floats.
typeset -F limit2=2.0
echo "-------------------------------------------"
echo "Server_Name   CPU(%)"
echo "-------------------------------------------"
for server in `more /opt/scripts/server-list1.txt`
do
    scpu1=$( ssh $server cat /proc/stat | awk '/cpu/{printf("%.1f\n"),
    ($2+$4)*100/($2+$4+$5)}' |  awk  '{print $0}' | head -1)
    echo "$server   $scpu1"
done | column -t
echo "-------------------------------------------"
for server in `more /opt/scripts/server-list2.txt`
do
    scpu2=$( ssh $server cat /proc/stat | awk '/cpu/{printf("%.2f\n"),
    ($2+$4)*100/($2+$4+$5)}' |  awk '{print $0}' | head -1)
    echo "$server   $scpu2"
done | column -t
echo "-------------------------------------------"
if [[ $scpu1 -ge $limit2 && $scpu2 -le $limit1 ]] ; then
    echo " CPU utilization of node1 is above 2% and node2 is below 1%"
elif [[ $scpu1 -le $limit1  &&  $scpu2 -ge $limit2 ]] ; then
    echo " CPU utilization of node2 is above 2% and node1 is below 1%"
elif [[ $scpu1 -ge $limit2  &&  $scpu2 -ge $limit2 ]] ; then
    echo "CPU utilization of node1 and node2 is above 2% "
else
    echo " Nothing to do"
fi

Though my node1 CPU is less than 2% and node2 CPU is greater than 1% it is giving output as: CPU utilization of node1 is above 2% and node2 is below 1%. Irrespective of condition it is giving the same output as above.

dan1st
  • 12,568
  • 8
  • 34
  • 67
Priyanka
  • 101
  • 1
  • 9
  • Not clear, could you please use CODE TAGS properly. Then could you please explain your problem in a concise manner so that we could understand it better, kindly do let us know once you edit your post. – RavinderSingh13 Feb 03 '20 at 05:22
  • Till CPU monitoring part script is working fine. But if condition is not working, in the sense in the above script i have 4 conditions, i know 2nd condition is true but it is giving the first condition output. Irrespective of the condition in if, it is giving the same output as: CPU utilization of node1 is above 2% and node2 is below 1% – Priyanka Feb 03 '20 at 05:50
  • 1
    [ShellCheck](https://www.shellcheck.net) correctly identifies this problem as a variation of https://stackoverflow.com/questions/16854280/a-variable-modified-inside-a-while-loop-is-not-remembered . Your variables assignments are forgotten because they happen in subshells. – that other guy Feb 03 '20 at 06:11

2 Answers2

0

&& is a bash builtin.

However, [ is also a shell builtin that redirects to the test command.

If you look in the manual of test (man test), you can see the following:

EXPRESSION1 -a EXPRESSION2
    both EXPRESSION1 and EXPRESSION2 are true

This means, that you should use -a should be used instead of && inside of [ condition ].

However, as the manual states later:

NOTE: Binary -a and -o are inherently ambiguous.  Use 'test EXPR1  &&  test  EXPR2'  or  'test
EXPR1 || test EXPR2' instead.

This means, the best way of doing this is:

[[ $scpu1 -ge $limit2 ]] && [[ $scpu2 -le $limit1 ]]

As @that other guy points out in the comments of the question:

ShellCheck correctly identifies this problem as a variation of stackoverflow.com/questions/16854280/… . Your variables assignments are forgotten because they happen in subshells.

You assign variables inside for loops, which is ok but the output of the loop is piped to column -t. This pipe creates 2 subshells (for the command before and after the pipe) and the variables of subshells is discarded after the subshell ends.

For example, this happens to scpu1 here:

for server in `more /opt/scripts/server-list1.txt`
do
    scpu1=$( ssh $server cat /proc/stat | awk '/cpu/{printf("%.1f\n"),
    ($2+$4)*100/($2+$4+$5)}' |  awk  '{print $0}' | head -1)
    echo "$server   $scpu1"
done | column -t

As the answer of the linked post of @that other guy suggests, you can use output redirection(which does not create subshells) for this:

for server in `more /opt/scripts/server-list1.txt`
do
    scpu1=$( ssh $server cat /proc/stat | awk '/cpu/{printf("%.1f\n"),
    ($2+$4)*100/($2+$4+$5)}' |  awk  '{print $0}' | head -1)
    echo "$server   $scpu1"
done <<< "$(column -t)"

Note that $() is a newer version of ``

dan1st
  • 12,568
  • 8
  • 34
  • 67
  • I have tried this " [[ $scpu1 -ge $limit2 ]] && [[ $scpu2 -le $limit1 ]] " also, still i am facing the same issue. – Priyanka Feb 03 '20 at 06:23
  • Finishes the script if you add `set -e` at the beginning? This makes the script quit if any errors(return codes that do not equal `0`) occur. – dan1st Feb 03 '20 at 06:26
  • Also, I've added a reference to the comment of @[that other guy](https://stackoverflow.com/users/1899640/that-other-guy) explaining another problem with your code. – dan1st Feb 03 '20 at 06:32
  • Thanks a lot for your help. I will check for that. – Priyanka Feb 03 '20 at 06:34
  • I have removed the for loop and cpu usage is assigning to one variable. It giving output as: ------------------------------------------- Server_Name CPU(%) ------------------------------------------- CPU Usage of server1: 1.6 CPU Usage of server2: 3.21 script.sh: line 14: [[: 1.6: syntax error: invalid arithmetic operator (error token is ".6") script.sh: line 18: [[: 1.6: syntax error: invalid arithmetic operator (error token is ".6") script.sh: line 22: [[: 1.6: syntax error: invalid arithmetic operator (error token is ".6") Nothing to do – Priyanka Feb 03 '20 at 07:10
  • you might need to quote the variables with `"`. – dan1st Feb 03 '20 at 08:08
  • but there may be problems with the floating point numbers. – dan1st Feb 03 '20 at 08:08
0

The answer highlight additional problems on top of the issues raised by @dan1st answer, in particular, the usage of | columns

From the comment on typeset -F limit1=1.0 # Declare variables as floats., it implies that the script is expecting floating point math (comparison, etc). bash (like most other sh varients) does NOT support arithmetic or comparison on floating point, only integers.

Consider one of two options: performing all math in integers including the load calculation and the limit1/limit2 values). Alternatively, if it's critical to have more precision (fractions of percent), consider scaling all numbers by a factor of 10 (or 100, if that level of precision is needed).

The '-F' of typeset prevent the display of the function body. It is only useful if the function is printed with typedef -p ..., otherwise it is ignored.

#!/bin/bash


   ### No -F 
typeset limit1=1
typeset limit2=2
   ###
echo "-------------------------------------------"
echo "Server_Name   CPU(%)"
echo "-------------------------------------------"
for server in `more /opt/scripts/server-list1.txt`
do
    scpu1=$( ssh $server cat /proc/stat | awk '/cpu/{printf("%.1f\n"),
    ($2+$4)*100/($2+$4+$5)}' |  awk  '{print $0}' | head -1)
    echo "$server   $scpu1"
done | column -t
echo "-------------------------------------------"
for server in `more /opt/scripts/server-list2.txt`
do
    scpu2=$( ssh $server cat /proc/stat | awk '/cpu/{printf("%.2f\n"),
    ($2+$4)*100/($2+$4+$5)}' |  awk '{print $0}' | head -1)
    echo "$server   $scpu2"
done | column -t
echo "-------------------------------------------"

   ### Calculate load (as integer), and compare
load1=$(printf '%.0f' $scpu1)
load2=$(printf '%.0f' $scpu2)

if [[ $load1 -ge $limit2 && $load2 -le $limit1 ]] ; then
    echo " CPU utilization of node1 is above 2% and node2 is below 1%"
elif [[ $load1 -le $limit1  &&  $load2 -ge $limit2 ]] ; then
    echo " CPU utilization of node2 is above 2% and node1 is below 1%"
elif [[ $load11 -ge $limit2  &&  $load2 -ge $limit2 ]] ; then
    echo "CPU utilization of node1 and node2 is above 2% "
else
    echo " Nothing to do"
fi

Also worth looking into few items:

  • Script iterate over list of servers (server-list1.txt), but uses the load of the last server only for the message. If you expect multiple servers to be named, consider averaging, or taking the maximum/minimum value as needed.
  • Using more is appropriate for interactive commands, Instead of more /opt/scripts/server-list2.txt, to iterate over list of servers from a file use for server in $(</opt/scripts/server-list2.txt)
dash-o
  • 13,723
  • 1
  • 10
  • 37
  • I just tried with the above script but the output is ------------------------------------------- Server_Name CPU(%) ------------------------------------------- node1 1.6 ------------------------------------------- node2 3.21 ------------------------------------------- CPU utilization of node1 is above 2% and node2 is below 1% I am new to scripting and not getting what is wrong with the script. – Priyanka Feb 03 '20 at 08:50
  • Could you please let me know what might be the reason for this error: script.sh: line 14: ((: 1.6: syntax error: invalid arithmetic operator (error token is ".6"). here 1.6 is the output cpu usage of node1 – Priyanka Feb 03 '20 at 08:56