4

I'd like to use the watch command to the following chain of commands:

journalctl | grep 'UFW BLOCK' | grep 'DST=192.168.0.2' | awk '{printf "%-4s%-3s%-10s%-1s%+16s\n", $1, $2, $3, $7, $11, $18, $19}'

Normally when I want to apply watch to a command chain I'd enclose the chain in-between the quotation mark

watch "COMMAND_1 | COMMAND_2 | ... | COMMAND_n"

but when there is another quotation mark in one of the commands like the one above, it gives error. What would be a workaround to this issue?

1 Answers1

4

To avoid having to worry with quotes, you can use a here document:

shell_code=$(cat << 'EOF'
  journalctl |
    awk '
      /UFW BLOCK/ && index($0, "DST=192.168.0.2 ") {
         printf "%-4s%-3s%-10s%-1s%+16s\n", $1, $2, $3, $7, $11, $18, $19
      }'
EOF
)

watch "$shell_code"

(or watch sh -c "$shell_code" if your watch implementation does not already start a shell to interpret a command line but runs the command itself, though those watch implementations are getting rare these days¹).

Some other notes:

  • awk can do most of what grep can do, so you rarely need to pipe them together.

  • . is a regular expression operator that matches any single character. grep 192.168.0.2 would match in 192.168.012. You can use [.] or \. to escape it or -F in grep or index() in awk to do substring searches instead of regex matching.

  • If you don't add that extra space in the search string, you'd also find DST=192.168.0.2 in DST=192.168.0.234 for instance. Using grep -Fw DST=192.168.0.2 would address both issues.

  • I would avoid double quotes to enclose shell code, as it would be all too easy to forget to escape $s or `s and introduce command injection vulnerabilities (with the contents of variables being potentially interpreted as shell code). Single quotes are safer as you're guaranteed that no character is special within them. As that includes 's themselves (in Bourne-like shells at least²), that meant you can't have 's within '...', but you can always enter them as 'foo'\''bar', which is 'foo' concatenated with \' (' quoted with a backslash) concatenated with 'bar'. So here:

    watch 'journalctl |
       awk '\''
         /UFW BLOCK/ && index($0, "DST=192.168.0.2 ") {
            printf "%-4s%-3s%-10s%-1s%+16s\n", $1, $2, $3, $7, $11, $18, $19
         }'\'
    
  • journalctl has a -f/--follow mode where new log entries are displayed as they arrive. You may want to use that rather than searching the full logs over and over again.


¹ the watch implementation from procps-ng runs sh to interpret the code in the concatenation of its arguments with spaces, though can run commands directly with the -x / --exec option allowing you to more easily have code interpreted by another language interpreter like watch -x zsh -c 'zsh code' or watch -x perl -e 'perl code'.

² Though for completeness zsh (Bourne-like; though with features from rc and csh as well) can do 'foo''bar' to have a single quote inside single quoted strings à la rc if you enable the rcquotes option.