Search code examples
bashcommand-substitution

How to preserve nested command substitutions inside a watch command


I'm utilizing a watch command that has multiple commands strung together and they all work except one piece of it. Even when ran by itself it does not have the desired output.

Here is the watch command being ran.
watch -n 5 echo "$(echo "$(uptime | awk '{print$11}' | sed 's/,//g')/$(nproc)*100" | bc -l | cut -c-5)% overall CPU utilization"

The output shows the cpu utilization at the time of execution but it does not update.
Every 5.0s: echo 4.750% overall CPU utilization

I'm almost certain it's a quoting issue somehow but I've been reading around and trying different things and can just not get it to work.
Here are some of the different versions of the command I've tried:

Using single quotes on the inside command sub

watch -n 5 echo "$(echo '$(uptime | awk '{print$11}' | sed 's/,//g')/$(nproc)*100' | bc -l | cut -c-5)% overall CPU utilization"
Every 5.0s: echo % overall CPU utilization % overall CPU utilization

Escaping the single quotes on the inside command sub

watch -n 5 echo "$(echo \'$(uptime | awk '{print$11}' | sed 's/,//g')/$(nproc)*100\' | bc -l | cut -c-5)% overall CPU utilization"
Every 5.0s: echo % overall CPU utilization % overall CPU utilization

Using single quotes on both sets of command sub, and escaping the inside set

watch -n 5 echo '$(echo \'$(uptime | awk '{print$11}' | sed 's/,//g')/$(nproc)*100\' | bc -l | cut -c-5)% overall CPU utilization'
-bash: syntax error near unexpected token )'`

Using single quotes on both sets of command sub (closest to desired output so far)

watch -n 5 echo '$(echo '$(uptime | awk '{print$11}' | sed 's/,//g')/$(nproc)*100' | bc -l | cut -c-5)% overall CPU utilization'
Every 5.0s: echo $(echo 0.24/8*100 | bc -l | cut -c-5)% overall CPU utilization 3.000% overall CPU utilization

If I can determine how to get it to re-run $(uptime | awk '{print$11}' | sed 's/,//g') this each time then it should work.


Solution

  • I'm not sure what the watch part should do, but you can vastly simplify this by refactoring all the processing into a single Awk script.

    Something like this?

    watch -n 5 'uptime | awk -v n="$(nproc)" "{cpu=\$11; gsub(/,/, \"\", cpu); printf \"%0.3f%% overall CPU utilization\\n\", cpu/n*100 }"'
    

    The quoting is slightly pesky; you might want to save the single-quoted code as a script, which allows you to switch to single quotes around the Awk code and get rid of the backslashes.