0

I am following this guide to try and setup a lemonbar config: http://blog.z3bra.org/2014/04/meeting-at-the-bar.html

Now almost everything works flawlessly except for this line:

bc <<< "scale=2; 100 - $f / $t * 100" | cut -d. -f1

In this function:

memused() {
    read t f <<< `grep -E 'Mem(Total|Free)' /proc/meminfo |awk '{print $2}'`
    bc <<< "scale=2; 100 - $f / $t * 100" | cut -d. -f1
}

I get the following error (standard_in) 1: syntax error

Now from what I can tell I should be adding double brackets around the equation (()) but I have tried a ton of different arrangements and nothing fixes the error.

Kusalananda
  • 320,670
  • 36
  • 633
  • 936
Otis Wright
  • 121
  • 6

3 Answers3

3

awk can do all of read's, cut's, grep's and bc's jobs here:

 awk -F': *' '
   $1 == "MemTotal" {t = $2}
   $1 == "MemFree" {f = $2}
   END {printf "%.1f%%\n", (t - f) * 100 / t}' /proc/meminfo

Beware that not all awk implementations will output 40,5% instead of 40.5 in locales where comma is used as the decimal radix (GNU awk only does it in POSIX mode like when $POSIXLY_CORRECT is in the environment). Use LC_ALL=C awk... to force the decimal radix to period/dot.

Change %.1f to %.0f if you don't want a decimal part at all. That will round to the nearest integer. Use %d instead if you want to truncate the decimal part (get 99% instead of 100% for 99.999).

Your read t f <<< `grep -E 'Mem(Total|Free)' /proc/meminfo |awk '{print $2}'` would have worked in older versions of bash assuming an unmodified $IFS.

In cmd <<< `code`, bash used to split the output of code on characters of $IFS (which by default includes newline) and join them with the first character of $IFS (by default space) before feeding as stdin to the command. So in your case, those two lines of awk's output would end-up being fed as two words on one line as expected by read t f. That surprising behaviour (and also differing from the original implementation in zsh) was fixed in bash-4.4.

In bash-4.4 and above, you'd need { read t; read f; } <<< "$(awk...)" (here adding quotes around $(...) so it also works in bash-4.3 and before, still assuming the default value of $IFS).

Jeff Schaller
  • 66,199
  • 35
  • 114
  • 250
Stéphane Chazelas
  • 522,931
  • 91
  • 1,010
  • 1,501
1

Abstract:

Portable, In one line and using printf:

awk '/MemTotal/{t=$2}; /MemFree/{f=$2}; END{printf("%d\n",(1-f/t)*100)}'

Only Shell (bash):

memused() { : # Print the (integer) value of % of free memory
            local ret ref a t f 
            ret='MemTotal:[ \t]*([0-9]+)';              # Regex for Total memory.
            ref='MemFree:[ \t]*([0-9]+)';               # Regex for Free  memory.
            a=$(</proc/meminfo)                         # Get meminfo in var (a).
            [[ $a =~ $ret ]] && t="${BASH_REMATCH[1]}"  # Get Total memory.
            [[ $a =~ $ref ]] && f="${BASH_REMATCH[1]}"  # Get Free  memory.
            printf '%s\n' "$(( 100 - 100*f/t )) %"      # Print integer % value.
          }

Description

The reason for the complaint of bc is that it is only getting one var.

To reproduce:

$ t=150 ; f='' ; bc <<< "scale=2; 100 - $f / $t * 100"
(standard_in) 1: syntax error

One way to avoid that error is to set a default value:

$ t=150; f=''; bc <<< "scale=2; 100 - ${f:-0} / ${t:-0} * 100"
100

The reason for only one value is that the read is getting the values in separate lines (in bash 4.4). A solution that works well for all bash versions is to use -d '':

$ read -d '' t f <<< "`grep -E 'Mem(Total|Free)' /proc/meminfo |awk '{print $2}'`"
$ echo "total=<$t>     free=<$f>"
total=<1922764>     free=<424360>

And that also works whether the command expansion is quoted (as it should be) "`grep … '`" or not.

But there is no reason to call grep, awk, bc and finally cut. One call to awk could do it all:

</proc/meminfo awk '/MemTotal/{t=$2}
                    /MemFree/ {f=$2}
                    END{
                        print( (1-f/t)*100 )
                    }
                   '

In one line and using printf:

awk '/MemTotal/{t=$2};/MemFree/{f=$2};END{printf("%d\n",(1-f/t)*100)}' </proc/meminfo

If you want something that works faster and only use the shell (albeit bash):

memused() {  : # Print the (integer) value of % of free memory
             local ret ref a t f 
             ret='MemTotal:[ \t]*([0-9]+)';              # Regex for Total memory.
             ref='MemFree:[ \t]*([0-9]+)';               # Regex for Free  memory.
             a=$(</proc/meminfo)                         # Get meminfo in var (a).
             [[ $a =~ $ret ]] && t="${BASH_REMATCH[1]}"  # Get Total memory.
             [[ $a =~ $ref ]] && f="${BASH_REMATCH[1]}"  # Get Free  memory.
             printf '%s\n' "$(( 100 - 100*f/t )) %"      # Print integer % value.
   }
0

The string read by read command only contain 1 field, where 2 is expected to fill both t and f variable.

You can solve it by adding | tr '\n' ' ' after the awk command.

But it would be better to simplify it to just:

read t f <<< $(awk '/MemTotal/{t=$2}/MemFree/{f=$2}END{print t,f}' /proc/meminfo)
oliv
  • 2,586
  • 9
  • 14