I am always asking more or less technical questions so I thought maybe I'll start my own thread where I can post about things I am trying to understand in the hope that some of you can share your knowledge, and perhaps some tutorage with me.

What am I trying to do:

I want my barscript.sh (that I am using with sdorfehs wm) to indicate when my speakers are muted.

What I've tried:

xev output of my ThinkPad X230's mute button is as expected: XF86AudioMute
However, because toggling the mute botton on/off outputs the exact same information, I figure I need to write a short script?

So, I discovered the commands to mute and unmute are:
mixerctl -w outputs.master3.mute=on
mixerctl -w outputs.master3.mute=off
So with absolutely little to no scripting knowledge, I whipped up this simple script:

mute.sh:

#!/bin/sh

MUTE=$(mixerctl outputs.master3.mute | sed 's/^outputs.master3.mute=//')

if [ "${MUTE}" = "off" ]; then
    RUN='mixerctl -w outputs.master3.mute=on'
else
    RUN='mixerctl -w outputs.master3.mute=off'
fi

exec "$RUN"

This is clearly where I'm going wrong because the script don't work!

Running mute.sh gives this error:
exec: mixerctl -w outputs.master3.mute=on: not found

Nevertheless, I added this keybinding to my wm config:
definekey top XF86AudioMute exec $HOME/.scripts/mute.sh

Running the command mixerctl -w outputs.master3.mute=on in a terminal works, and after adding the relevant bits to my barscript.sh I do get the desired output in my bar: [68%MUTE]. It's just the mute.sh script that isn't working and my lack of basic shell scripting is clearly the reason why.

For reference, my barscript.sh:

#!/bin/sh

while true; do

# Packages
PKGS="$(pkg_info | wc -l | sed -e 's/^[ \t]*//')"

# Volume
LEV="$(mixerctl outputs.master | sed -e 's|.*,||g')"
VOL="$name$(expr \( $LEV \* 100 \) / 254)"
MUTESTATE=$(mixerctl outputs.master3.mute | sed 's/^outputs.master3.mute=//')
if [ "${MUTESTATE}" = "on" ]; then
    MUTE=' MUTE'
else
    MUTE=''
fi

# Battery
BAT_PERC="$(envstat -s acpibat0:charge | tail -1 | sed -e 's,.*(\([ ]*[0-9]*\)\..*,\1,g')%"
BAT_STATE="$(envstat -d acpibat0 | awk 'FNR == 10 {print $2}')"
if [ "${BAT_STATE}" = "TRUE" ]; then
    STATE='+'
else
    STATE='-'
fi

# Date
D="$(date '+%a %d %b %I:%M')"

# Weather
WTTR="$(cat $HOME/.scripts/weather.txt)"

# Print
echo "$D ~ $WTTR [$PKGS#][$VOL%$MUTE][$STATE$BAT_PERC]" > ~/.config/sdorfehs/bar

sleep 1
done
exit 0

The output of barscript.sh is sent to ~/.config/sdorfehs/bar which is then read by the wm through some sort of unix wizardry which I don't understand.

I would love to know what I'm doing wrong or how this could be done better.

  • rvp replied to this.

    pfr Running mute.sh gives this error:
    exec: mixerctl -w outputs.master3.mute=on: not found

    Remove the exec and quotes around $RUN; or, use this somewhat shorter version:

    #!/bin/sh
    #
    # Toggle audio mute state.
    exec mixerctl -w outputs.master3.mute++

    And, for barscript.sh, you don't need to use expr; just calculate it directly using $((...)).

      rvp Remove the exec and quotes around $RUN; or, use this somewhat shorter version:
      #!/bin/sh
      #
      #Toggle audio mute state.
      exec mixerctl -w outputs.master3.mute++

      In that case I don't need a mute script at all, I've just remapped the mute button with:
      definekey top XF86AudioMute exec mixerctl -w outputs.master3.mute++

      This works 🙂
      This also leads me to wonder, is ++ a universal argument for toggle on/off ?

      rvp And, for barscript.sh, you don't need to use expr; just calculate it directly using $((...)).

      I guess your talking about the VOL line:
      VOL="$name$(expr \( $LEV \* 100 \) / 254)"

      This was something that I stole from @pin when I was using his unibar config. I'm not exactly sure how you mean it should be calculated directly.

      • rvp replied to this.

        pfr n that case I don't need a mute script at all, I've just remapped the mute button with:
        definekey top XF86AudioMute exec mixerctl -w outputs.master3.mute++

        Remove exec--it's only useful inside shell scripts.

        pfr This also leads me to wonder, is ++ a universal argument for toggle on/off ?

        You can also use:--, +=1, -=1 (All of this is documented in the man page.)

        pfr I'm not exactly sure how you mean it should be calculated directly.

        LIke this--no expr needed in a POSIX shell:

        #!/bin/sh
        
        n=1
        while [ $n -le 12 ]
        do	for i in $(seq 1 10)
        	do	printf "$n x $i = $((n * i))\t"
        		printf "$((n+1)) x $i = $(((n+1) * i))\t"
        		printf "$((n+2)) x $i = $(((n+2) * i))\t"
        		printf "$((n+3)) x $i = $(((n+3) * i))\n"
        	done
        	printf "\n"
        	n=$((n + 4))
        done
        • pfr replied to this.
        • pfr likes this.

          rvp Remove exec--it's only useful inside shell scripts.

          Inside the sdorfehs config file exec is necessary. I don't know why, but this is how you set executable key bindings.

          rvp LIke this--no expr needed in a POSIX shell:

          This would explain why when I kill the X server I see a whole bunch of this:

          ...
          expr: syntax error
          expr: syntax error
          expr: syntax error
          expr: syntax error
          expr: syntax error
          ...

          EDIT: It took me a few shots to figure it out but I got there in the end with

          VOL="$((( $LEV ) * 100 / 254))"
          😄

          EDIT2: Still getting expr: syntax error after killing X server though

            pfr Still getting expr: syntax error after killing X server though

            This is most probably unrelated.
            I did try to look if I could find anything by taking a pick at your config files but, I gave-up straight away when I saw your .shrc 😨

            Do you use a dictionary to look-up all that stuff? 🤣

            136 lines .shrc 😱

            • pfr replied to this.
            • pfr likes this.

              pfr Inside the sdorfehs config file exec is necessary.

              Ah--I see...

              pfr This would explain why when I kill the X server I see a whole bunch of this:

              Some other error, this, I think.

              pfr EDIT: It took me a few shots to figure it out but I got there in the end with
              VOL="$((( $LEV ) * 100 / 254))"

              I would group the operands this way to make things clearer: VOL=$(( (LEV * 100) / 254 ))

              There are other shortcuts that you can take to reduce the no. of commands run each second.

              1. Use the -n flag to mixerctl to omit the variable name instead of removing it using sed:
                Ie. instead of this:

                $ mixerctl outputs.master3.mute | sed 's/^outputs.master3.mute=//'
                off
                $

                do this:

                $ mixerctl -n outputs.master3.mute
                off
                $
              2. Use the shell's prefix and suffix removal feature instead of sed/awk:
                This:

                $ envstat -d acpibat0 | awk 'FNR == 10 {print $2}'
                FALSE
                $

                can be written without awk:

                $ O=$(envstat -d acpibat0)	# stash output
                $ BAT_CH=${O##*charging:}	# remove BOL to `charging:' (incl.)
                $ BAT_CH=${BAT_CH%charge*}	# remove `charge...' (incl.) to end
                $ echo $BAT_CH			# do _not_ quote this.
                FALSE
                $
              3. In similar vein, you can make multiple passes over the same data and skip running separate commands each time to the extract pieces you want.

              As a demo, I've re-written your barscript.sh like this:

              #!/bin/sh
              
              set -eu
              while :
              do
              	# Packages
              	PKGS=$(pkg_info | wc -l)
              	PKGS=${PKGS##* }		# strip leading spaces
              
              	# Volume
              	VOL=$(mixerctl -n outputs.master)
              	VOL=${VOL%,*}			# strip other channel
              	VOL=$(( (VOL * 100) / 254 ))
              	MUTE=$(mixerctl -n outputs.master3.mute)
              	if [ $MUTE = on ]
              	then	MUTE=" MUTE"
              	else	MUTE=""
              	fi
              
              	# Battery
              	O=$(envstat -s 'acpibat0:charge,acpibat0:charging,acpibat0:discharge rate')
              	BAT_PERC=${O#*(}		# strip from beginning to '('
              	BAT_PERC=${BAT_PERC%%.*}	# strip from first '.' to end
              	BAT_DIS=${O#*discharge rate:}	# strip ^ -> 'discharge rate:' (incl.)
              	BAT_DIS=${BAT_DIS%charg*}	# snag discharge rate, or "N/A"
              	BAT_DIS=${BAT_DIS%W*}		#  " (contd.)
              	BAT_STATE=${O#*charging:}	# snag charge state
              
              	if [ $BAT_DIS != N/A ]		# note: do not quote any of these
              	then	STATE="-"		# discharging
              	elif [ $BAT_STATE = TRUE ]
              	then	STATE="+"		# charging
              	else	STATE="="		# topped-up
              	fi
              
              	# Date
              	D=$(date '+%a %d %b %I:%M')
              
              	# Weather
              	WTTR=$(cat $HOME/.scripts/weather.txt)
              
              	# Print
              	echo "$D ~ $WTTR [$PKGS#][$VOL%$MUTE][$STATE$BAT_PERC%]" > ~/.config/sdorfehs/bar
              
              	sleep 1
              done

              Further "optimizations" (eg. removing the extra mixerctl to get the mute state) are up to you.

                pin Do you use a dictionary to look-up all that stuff? 🤣

                136 lines .shrc 😱

                haha yeah, mostly alias's. Is it better to use a separate file for Alias's? Believe it or not I remember most of them.
                In a way, it's kind of a personal knowledge base for commands I would otherwise forget.

                rvp
                Wow, that's awesome thanks! I'll try it out 😃

                EDIT: Works like a charm 😉

                Now lets say I wanted to add uptime to the bar as well. In previous bar scripts I had used this line:

                UP="$(uptime | awk -F, '{sub(".*up ",x,$1);print $1}' | sed -e 's/^[ \t]*//')"

                Is there a neater/simpler way to get the same output without sed of awk ?

                pin This is most probably unrelated.
                I did try to look if I could find anything by taking a pick at your config files but, I gave-up straight away when I saw your .shrc

                Any way of knowing which file is causing the error?

                  pfr Is there a neater/simpler way to get the same output without sed of awk ?

                  UP=$(uptime)
                  UP=${UP#*up}
                  UP=${UP%%,*}
                  echo $UP
                  • pfr replied to this.

                    pfr Is there a neater/simpler way to get the same output without sed of awk ?

                    $ UP=$(set - $(uptime) ; echo $3 days)
                    $ echo $UP
                    39 days
                    • pfr replied to this.
                    • pfr likes this.

                      rvp 👍️ This does the trick. The only thing which is strange is, I like to encase each output in [square brackets] however when I do this I get a trailing space after the first bracket like so: [ 14 minutes]. This triggers my OCD slightly 😟 I've just added the bracked to the echo line at the end like this:

                      UP=$(uptime)
                      UP=${UP#*up}
                      UP=${UP%%,*}
                      echo [$UP]

                      yeti Nice! However, I shutdown my laptop when I'm done using it so no need for "days" 😆

                      • yeti replied to this.

                        pfr yeti Nice! However, I shutdown my laptop when I'm done using it so no need for "days" 😆

                        Without "days" it even is shorter... ;-)
                        I only kept it because your original awk/sed dance had it.

                        14 days later

                        Ok, new query...

                        I'd like for my laptop to warn me when my battery is low. Visual notifications are useless and I would rather have an audible beep (which I noticed isn't available on NetBSD) or some other sound once the battery gets below 10%.

                        I guess a shell script could do this but would a system service need to be started at boot in order to monitor the battery status?

                          pfr Why not run the script from cron every minute or so? And battery level can be checked with envstat I think.

                            pfr I would rather have an audible beep (which I noticed isn't available on NetBSD) or some other sound once the battery gets below 10%.

                            Call your script which plays a wav file or somesuch in /etc/powerd/scripts/sensor_battery. That script is called with a warning-under event when the SOC reaches 10% and a critical-under event when it reaches 1%. There is also a low-power event there to shutdown the system.

                              pfr Is there any way to use a built in hardware beep/bell rather than a .wav file?

                              Set device permissions (as root--once only):

                              chmod go+rw /dev/speaker

                              Then, in your script:

                              echo -n CDEF >/dev/speaker

                              See spkr(4) for more info.

                                rvp
                                I assumed I could just add this to the /etc/powerd/scripts/sensor_battery script like so but it didn't work.
                                Does it need to be called from a separate script?

                                #!/bin/sh -
                                #
                                #       $NetBSD: sensor_battery,v 1.8 2014/03/13 00:50:55 christos Exp $
                                #
                                # Generic script for battery sensors.
                                #
                                # Arguments passed by powerd(8):
                                #
                                #       script_path device event sensor state_description
                                #
                                case "${2}" in
                                normal)
                                        logger -p warning \
                                            "${0}: (${3}) capacity reached normal state [${1}]" >&1
                                        exit 0
                                        ;;
                                state-changed)
                                        logger -p warning "${0}: (${3}) state changed to ${4} [${1}]" >&1
                                        exit 0
                                        ;;
                                warning-capacity|warning-under)
                                        logger -p warning \
                                            "${0}: (${3}) capacity below warning limit [${1}]" >&1
                                        exit 0
                                        ;;
                                critical-capacity|critical-under)
                                        logger -p warning \
                                            "${0}: (${3}) capacity below critical limit [${1}]" >&1
                                        echo -n C >/dev/speaker
                                        exit 0
                                        ;;
                                warning-over)
                                        logger -p warning \
                                            "${0}: (${3}) capacity above warning limit [${1}]" >&1
                                        echo -n CD >/dev/speaker
                                        exit 0
                                        ;;
                                        critical-over)
                                        logger -p warning \
                                            "${0}: (${3}) capacity above critical limit [${1}]" >&1
                                        exit 0
                                        ;;
                                high-capacity)
                                        logger -p warning \
                                            "${0}: (${3}) capacity above high limit [${1}]" >&1
                                        exit 0
                                        ;;
                                maximum-capacity)
                                        logger -p warning \
                                            "${0}: (${3}) capacity above maximum limit [${1}]" >&1
                                        exit 0
                                        ;;
                                #
                                # This event is _ONLY_ received when all AC Adapters are OFF and all
                                # batteries on the system are in CRITICAL or LOW state.
                                #
                                # It is not recommended to remove the shutdown call.
                                #
                                low-power)
                                        logger -p warning "${0}: LOW POWER! SHUTTING DOWN." >&1
                                        /sbin/shutdown -p now "${0}: LOW POWER! SHUTTING DOWN."
                                        echo -n CDEF >/dev/speaker
                                        exit 0
                                        ;;
                                *)
                                        logger -p warning "${0}: unsupported event ${2} on device ${1}" >&1
                                        exit 1
                                        ;;
                                esac
                                • rvp replied to this.

                                  pfr I assumed I could just add this to the /etc/powerd/scripts/sensor_battery script like so but it didn't work.

                                  1. powerd running? powerd=YES in /etc/rc.conf? (it's enabled by default in /etc/defaults/rc.conf)
                                  2. Does the beep work on the command line?

                                  The script looks OK, except:

                                  1. There is no beep for warning-under (10%)--only critical-under (1%) & warning-over (why this?) beep.
                                  2. For the low-power event, the beep should come before shutdown is initiated.

                                  pfr Does it need to be called from a separate script?

                                  A separate script like /usr/local/bin/alarm.sh (with the event passed to it) would be cleaner.
                                  And, I don't understand why you want a pathetic bleep when one can go Dive! Dive! Dive!

                                  • pfr replied to this.

                                    rvp powerd running? powerd=YES in /etc/rc.conf?

                                    Yes.

                                    rvp Does the beep work on the command line?

                                    Yes

                                    rvp The script looks OK, except:
                                    There is no beep for warning-under (10%)--only critical-under (1%) & warning-over (why this?) beep.
                                    For the low-power event, the beep should come before shutdown is initiated.

                                    This is the default script, I only added the calls to /dev/speaker

                                    I've changed it to look like this:

                                    warning-capacity|warning-under)
                                        logger -p warning \
                                            "${0}: (${3}) capacity below warning limit [${1}]" >&1
                                        echo -n C >/dev/speaker
                                        exit 0
                                        ;;
                                    critical-capacity|critical-under)
                                        logger -p warning \
                                            "${0}: (${3}) capacity below critical limit [${1}]" >&1
                                        echo -n CD >/dev/speaker
                                        exit 0
                                        ;;

                                    &

                                    low-power)
                                        logger -p warning "${0}: LOW POWER! SHUTTING DOWN." >&1
                                        echo -n CDEF >/dev/speaker  
                                        /sbin/shutdown -p now "${0}: LOW POWER! SHUTTING DOWN."
                                        exit 0
                                        ;;

                                    I got a beep once rendomly at 4% will keep testing.

                                    rvp A separate script like /usr/local/bin/alarm.sh (with the event passed to it) would be cleaner.
                                    And, I don't understand why you want a pathetic bleep when one can go Dive! Dive! Dive!

                                    How does calling a separate script keep it cleaner? Without it, it's one less script right?

                                    And I don't need a fancy alarm, I like to use what tools are built in.

                                    • rvp replied to this.