Bash – Why is Grep Slow to Exit After Finding Match?

bashgrep

I'm trying to write a bash script that polls btmon for device connections. I've got a working solution, but it's absurdly slow, and it seems like the issue is grep being very slow to exit after finding a match (around 25 seconds). What can I do to either speed grep up or avoid using it altogether?

#!/bin/bash
COUNTER=0
while :
  do
    until btmon | grep -m 1 '@ Device Connected'
      do :
    done
    let COUNTER=COUNTER+1
    echo on 0 | cec-client RPI -s -d 1
    sleep 5
    echo as | cec-client RPI -s -d 1
    until btmon | grep -m 1 '@ Device Disconnected'
      do :
    done
    let COUNTER=COUNTER-1
    if [ $COUNTER -eq 0 ];
      then echo standby 0 | cec-client RPI -s -d 1;
    fi
done

edit: To clarify, btmon and is a bluetooth monitoring tool that's part of the Bluez suite, and cec-client is a utility that's packaged with libCEC for issuing commands across the HDMI-CEC serial bus (amongst other things).

Best Answer

In:

cmd1 | cmd2

Most shells (the Bourne shell, (t)csh, as well as yash and some versions of AT&T ksh under some conditions being the notable exceptions) wait for both cmd1 and cmd2.

In bash, you'll notice that

sleep 1 | uname

returns after one second.

In:

btmon | grep -m 1 '@ Device Disconnected'

grep will exit as soon as it has found one occurrence of the pattern, but bash will still wait for btmon.

btmon will typically die of a SIGPIPE the next time it writes to the pipe after grep has returned, but if it never writes anything again, it would never receive that signal.

You could replace #! /bin/bash with #! /bin/ksh93 as that's a shell compatible with bash and one that only waits for the last component of a pipeline. Then in

btmon | grep -m 1 '@ Device Disconnected'

after grep returns, btmon would be left running in background and the shell would carry on with the rest of the script.

If you wanted to kill btmon as soon as grep returns, POSIXly, you could do something like:

sh -c 'echo "$$"; exec btmon' | (
   read pid
   grep -m1 '@ Device Disconnected' || exit
   kill "$pid" 2> /dev/null
   true)
Related Question