Melonlorrd's Blog

Basic Bash Scripting

It is hard to remember bash shell scripting if you try to memorize it. With my horrible memory, that makes it even harder. It is confusing for me to make notes on topics like these too. So, I wrote a script to remember some basic bash scripting I use in general. This script is a monitoring tool that tells you about your CPU, CPU Temperature, Fan Speed, Memory, Swap, Ethernet, and WiFi speeds.

Monitoring CPU Usage

I did not want to use built-in tools like top, so I grabbed info from the proc directory:

1cpu_info=($(grep '^cpu ' /proc/stat)) # extra () converts it to array
2
3total_cpu_time=$((${cpu_info[1]} + ${cpu_info[2]} + ${cpu_info[3]} + ${cpu_info[4]}))
4idle_cpu_time=${cpu_info[4]}
5
6cpu_usage=$((100 - (100 * ($idle_cpu_time - $prev_idle_cpu_time) / ($total_cpu_time - $prev_total_cpu_time))))
7
8echo "CPU Usage: $cpu_usage %"

CPU info 1 contains CPU time for normal user mode processes, CPU info 2 contains CPU time for niced user mode processes (niced ones are processes with custom priorities), CPU info 3 is CPU time for kernel mode processes, and CPU info 4 is CPU time spent idle waiting.

CPU Temperature

I know I could've used lmsensors, but I had my own goals. I use a ThinkPad, so I grabbed data from the proc directory. Also, it checks whether the thinkpad_acpi kernel module is loaded or not to avoid errors.

1if lsmod | grep -q thinkpad_acpi; then
2    cpu_temp=$(cat /proc/acpi/ibm/thermal | grep 'temperatures' | awk '{print $2 " C"}')
3    echo "CPU Temp: $cpu_temp"
4else
5    echo "CPU Temp: NA, thinkpad_acpi module not loaded"
6fi

Fan Speed

This function monitors the CPU fan speed on a ThinkPad by reading data from the proc directory and checks if the thinkpad_acpi kernel module is loaded to avoid errors.

1if lsmod | grep -q thinkpad_acpi; then
2    fan_speed=$(cat /proc/acpi/ibm/fan | awk '/^speed/ {print $2 " RPM"}')
3    echo "Fan Speed: $fan_speed"
4else
5    echo "Fan Speed: NA, thinkpad_acpi module not loaded"
6fi

Monitoring RAM Usage

This function grabs memory usage information from the proc directory, calculates the total and available memory, and then converts it into MB for readability.

1mem_total=$(grep '^MemTotal:' /proc/meminfo | awk '{print $2}')
2mem_free=$(grep '^MemAvailable:' /proc/meminfo | awk '{print $2}')
3
4mem_total_mb=$((mem_total / 1024))
5mem_used_mb=$(( (mem_total - mem_free) / 1024 ))
6
7echo "Memory Used: $mem_used_mb MB / $mem_total_mb MB"

Monitoring Swap Usage

This function checks the swap usage by reading data from the proc directory, calculating the total and free swap memory, and then converting it into MB for readability.

1swap_total=$(grep '^SwapTotal:' /proc/meminfo | awk '{print $2}')
2swap_free=$(grep '^SwapFree:' /proc/meminfo | awk '{print $2}')
3
4swap_total_mb=$((swap_total / 1024))
5swap_used_mb=$(( (swap_total - swap_free) / 1024 ))
6
7echo "Swap Used: $swap_used_mb MB / $swap_total_mb MB"

Ethernet Speed

This function monitors Ethernet usage by checking the bit count from the sys directory. It calculates the difference between the current and previous bit counts to determine the speed, then converts it to Mbps or Kbps for readability.

 1prev_rx_bytes_eth=$(cat /sys/class/net/enp2s0/statistics/rx_bytes)
 2prev_tx_bytes_eth=$(cat /sys/class/net/enp2s0/statistics/tx_bytes)
 3
 4get_ethernet_stats() {
 5    rx_bytes_eth=$(cat /sys/class/net/enp2s0/statistics/rx_bytes)
 6    tx_bytes_eth=$(cat /sys/class/net/enp2s0/statistics/tx_bytes)
 7
 8    diff_rx=$((rx_bytes_eth - prev_rx_bytes_eth))
 9    diff_tx=$((tx_bytes_eth - prev_tx_bytes_eth))
10
11    prev_rx_bytes_eth=$rx_bytes_eth
12    prev_tx_bytes_eth=$tx_bytes_eth
13
14    if [[ $diff_rx -gt 1000000 ]]; then
15        rx_speed=$((diff_rx / 1000000))
16        rx_unit="Mbps"
17    else
18        rx_speed=$((diff_rx / 1000))
19        rx_unit="Kbps"
20    fi
21
22    if [[ $diff_tx -gt 1000000 ]]; then
23        tx_speed=$((diff_tx / 1000000))
24        tx_unit="Mbps"
25    else
26        tx_speed=$((diff_tx / 1000))
27        tx_unit="Kbps"
28    fi
29
30    echo "Up: $tx_speed $tx_unit | Down: $rx_speed $rx_unit"
31}

WiFi Speed

This function monitors WiFi usage similarly to Ethernet, by checking the bit count from the sys directory. It calculates the difference between the current and previous bit counts to determine the speed, then converts it to Mbps or Kbps for readability.

 1prev_rx_bytes_wifi=$(cat /sys/class/net/wlp3s0/statistics/rx_bytes)
 2prev_tx_bytes_wifi=$(cat /sys/class/net/wlp3s0/statistics/tx_bytes)
 3
 4get_wifi_stats() {
 5    rx_bytes_wifi=$(cat /sys/class/net/wlp3s0/statistics/rx_bytes)
 6    tx_bytes_wifi=$(cat /sys/class/net/wlp3s0/statistics/tx_bytes)
 7
 8    diff_rx=$((rx_bytes_wifi - prev_rx_bytes_wifi))
 9    diff_tx=$((tx_bytes_wifi - prev_tx_bytes_wifi))
10
11    prev_rx_bytes_wifi=$rx_bytes_wifi
12    prev_tx_bytes_wifi=$tx_bytes_wifi
13
14    if [[ $diff_rx -gt 1000000 ]]; then
15        rx_speed=$((diff_rx / 1000000))
16        rx_unit="Mbps"
17    else
18        rx_speed=$((diff_rx / 1000))
19        rx_unit="Kbps"
20    fi
21
22    if [[ $diff_tx -gt 1000000 ]]; then
23        tx_speed=$((diff_tx / 1000000))
24        tx_unit="Mbps"
25    else
26        tx_speed=$((diff_tx / 1000))
27        tx_unit="Kbps"
28    fi
29
30    echo "Up: $tx_speed $tx_unit | Down: $rx_speed $rx_unit"
31}

Using the script

You can check out the complete script here.

 1snwzd@kepler ~ > system-status
 2Usage: system-status [option]
 3Options:
 4  cpu       Display CPU usage
 5  temp      Display CPU temperature
 6  fan       Display CPU fan speed
 7  mem       Display Memory usage
 8  swap      Display Swap usage
 9  eth       Display Ethernet usage
10  wifi      Display Wifi usage
11  all       Display all information
12snwzd@kepler ~ > system-status temp
13CPU Temp: 47 C
14CPU Temp: 46 C
15CPU Temp: 46 C
16^C
17snwzd@kepler ~ > system-status mem
18Memory Used: 3711 MB / 11768 MB
19Memory Used: 3712 MB / 11768 MB
20^C
21snwzd@kepler ~ > system-status wifi
22Up: 0 Kbps | Down: 0 Kbps
23Up: 0 Kbps | Down: 0 Kbps
24^C
25snwzd@kepler ~ > system-status all
26CPU Temp: 46 C
27Fan Speed: 1800 RPM
28Memory Used: 3683 MB / 11768 MB
29Swap Used: 631 MB / 8191 MB
30Up: 0 Kbps | Down: 0 Kbps
31Up: 0 Kbps | Down: 0 Kbps
32---
33CPU Usage: 2%
34CPU Temp: 46 C
35Fan Speed: 1800 RPM
36Memory Used: 3684 MB / 11768 MB
37Swap Used: 631 MB / 8191 MB
38Up: 0 Kbps | Down: 0 Kbps
39Up: 0 Kbps | Down: 0 Kbps
40---

Extras

  1. You should write a bash script with set -eux. -e disallows the script to execute further if one of the statements in the script fails. -u treats unset variables as errors and exits. -x prints each command before executing it to stdout.
  2. There is a difference in POSIX-compliant syntax and bash extensions of it, e.g. while writing an if condition, if you use [ condition ], you are using POSIX-compliant syntax. But with bash extensions [[ condition ]], you can do pattern matching ==, regex =~, logical operations && ||, etc.
  3. If you don't want your script to be executed as root, check $EUID, i.e., effective user ID. If it is 0, prevent the script from executing further. Running a script as sudo from a user who is part of the wheel group sets the user ID to 0, which gives administrator privileges to the user. You can check this by executing id -u and then sudo id -u.
  4. You can check the exit status of the previously executed program with $?.

Thankyou for reading my blog.