20

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).

  • 2
    How much "stuff" does btmon output? are you sure it's not just a matter of buffering? – steeldriver May 23 '17 at 15:37
  • @steeldriver Seconded. Have you tried disabling buffering in the pipe? – l0b0 May 23 '17 at 16:02
  • btmon outputs about 250 characters per second. – Rob May 23 '17 at 16:06
  • @l0b0 I tried disabling buffering with the unbuffer command, but that seems to prevent grep from exiting at all? I also tried forcing grep to --line-buffer mode, but that didn't seem to help. – Rob May 23 '17 at 16:18
  • It could be that btmon implements buffering itself, in which case you're out of luck. – l0b0 May 23 '17 at 16:21
28

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)
  • 3
    Thanks for explaining why this is behaving the way it does. It didn't occur to me that bash might be waiting for btmon to exit. Swapping to ksh93 works beautifully! – Rob May 23 '17 at 16:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.