Bash: Run arbitrary command(s) when a log entry appears
During my work on Android apps, I regularly end up debugging things that only happen after a long wait, particularly when it’s concerning streaming music, and playing adverts. If I end up having to wait for 10+ minutes, I won’t be staring at the device the entire time.
Unfortunately, this means I can easily miss events I’m interested in.
Android fortunately has adb’s logcat, a way to view incoming logging messages. I can view this in the IDE I’m working on, but it’s available directly from the command line as well, as adb logcat
.
With that prerequisite out of the way, I wanted something that could reliably draw my attention when a specific log message appeared, so I could look at the android device and observe behaviour then.
This can be achieved in bash by plumbing a few things together.
- Live source of log messages (
adb logcat
) - Something to filter for the specific messages you’re after (
grep
) - Something to run a command that will draw attention every time there’s a line of output from step 2:
while read -r i; do <mycommand>; done
Putting it together, that looks like:
Open a terminal window, and enter:
adb logcat -T 1 | grep --line-buffered "search text" | while read i; do <command>; done
Be sure to replace search text
and <command>
with relevant things for you.
Per example, here’s how I use it. I’ve broken the command up into lines, using \
as the very last character in the line to tell bash not to interpret the “newline” character. This makes it a bit easier to read.
adb logcat -T 1 \
| grep --line-buffered "Checking to launch ads" \
| while read i; do notify-send "Checking for ads" "$i"; done
Let’s take that apart.
adb logcat
adb logcat -T 1
This streams messages from the device’s logs to STDOUT. The -T 1
argument ensures it only outputs messages from the time you invoked it onwards, skipping buffered messages. Essentially, don’t process things that happened in the past.
This invocation assumes you’ve only got one Android device or emulator available to adb right now. If you have multiple devices available to it, run adb devices
to get the serial number of the device you’re interested in, and then use adb logcat -d <serial number> -T 1
instead.
The output of this is piped into grep, which will only let lines through that we care about.
grep
grep --line-buffered "search text"
Given an input line, this will check whether “search text” was found in that line. If it was, the input line will be piped on to the while read
block.
Grep has lots of useful options, so it’s worth skimming the grep manual to be aware of them. In this use case, the simplest form works well, though.
The --line-buffered
argument ensures that as soon as a line of text is available to output, it will be done immediately. Without this, the shell or grep may buffer a large amount of text before passing it on to the pipe. Since we’re looking to do realtime processing, this is not desirable, hence the argument.
A side note: adb logcat
provides its own filtering options, but I want this post to be more generically useful. You can use any command that streams lines of logs as the first command, and still have use of the rest of this setup.
while read
while read i; do <command>; done
read
Takes input from STDIN (which is what is being piped into it, here) and assigns the input to a given argument. It normally splits it by word to be able to populate multiple arguments, but since we’re only providing one argument, the entirety of an input line gets assigned to i
here.
read
will return a success exit code as long as it doesn’t reach an EOF (end of file) character. Combined with while
, this keeps the loop going until input ends. (such as when you disconnect the device, and adb logcat
exits).
On every iteration of this while loop, <command>
gets run. You can use the $i
variable to do something further with the log line that came through.
In my case, I used notify-send "Ads are starting" "$i"
in place of <command>
, which generates a desktop notification on my linux setup.
Enjoy not staring at logs for ages waiting for something interesting to happen!