File: hawk.sh
   1 #!/bin/sh
   2 
   3 # The MIT License (MIT)
   4 #
   5 # Copyright (c) 2026 pacman64
   6 #
   7 # Permission is hereby granted, free of charge, to any person obtaining a copy
   8 # of this software and associated documentation files (the "Software"), to deal
   9 # in the Software without restriction, including without limitation the rights
  10 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11 # copies of the Software, and to permit persons to whom the Software is
  12 # furnished to do so, subject to the following conditions:
  13 #
  14 # The above copyright notice and this permission notice shall be included in
  15 # all copies or substantial portions of the Software.
  16 #
  17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  23 # SOFTWARE.
  24 
  25 
  26 # hawk [options...] [awk condition] [filenames...]
  27 #
  28 #
  29 # Highlight (lines) satisfying an AWK condition/expression, keeping other
  30 # lines the same. When not given a condition, all lines are highlighted.
  31 #
  32 # The handy case-insensitive shortcut options may cause this tool to fail,
  33 # if the main AWK tool installed doesn't support the special IGNORECASE
  34 # variable.
  35 #
  36 # The AWK options available only in single-dash versions are
  37 #
  38 #   -F fs               use `fs` for the field separator (the `FS` variable)
  39 #   -V                  show the AWK version installed
  40 #   -v var=val          set a variable to a given value
  41 #
  42 # The other options are, available both in single and double-dash versions
  43 #
  44 #   -h, -help             show this help message
  45 #   -ins, -insensitive    match regexes case-insensitively; fail if unsupported
  46 #   -tsv                  split fields using tabs, same as using -F "\t"
  47 #
  48 #   -blue                 use a blue style
  49 #   -gray                 use a gray style
  50 #   -green                use a green style
  51 #   -orange               use an orange style
  52 #   -purple               use a purple style
  53 #   -red                  use a red style
  54 #
  55 #   -bb, -blueback        use a blue-background style
  56 #        -grayback        use a gray-background style
  57 #   -gb, -greenback       use a green-background style
  58 #   -ob, -orangeback      use an orange-background style
  59 #   -pb, -purpleback      use a purple-background style
  60 #   -rb, -redback         use a red-background style
  61 
  62 
  63 case "$1" in
  64     -h|--h|-help|--help)
  65         awk '/^# +hawk /, /^$/ { gsub(/^# ?/, ""); print }' "$0"
  66         exit 0
  67     ;;
  68 esac
  69 
  70 ansi='7m'
  71 command='awk'
  72 if { [ -p /dev/stdout ] || [ -t 1 ]; } && [ -e /usr/bin/stdbuf ]; then
  73     command='stdbuf -oL awk'
  74 fi
  75 
  76 case_insensitive=0
  77 
  78 while [ $# -gt 0 ]; do
  79     arg="$1"
  80 
  81     if [ "${arg}" = "--" ]; then
  82         shift
  83         break
  84     fi
  85 
  86     case "${arg}" in
  87         -F)
  88             shift
  89             if [ $# -eq 0 ]; then
  90                 printf "expected value after -F option\n" >&2
  91                 exit 1
  92             fi
  93             command="${command} -F $1"
  94             shift
  95             continue
  96         ;;
  97 
  98         -F*)
  99             command="${command} ${arg}"
 100             shift
 101             continue
 102         ;;
 103 
 104         -v)
 105             shift
 106             if [ $# -eq 0 ]; then
 107                 printf "expected variable assignment after -v option\n" >&2
 108                 exit 1
 109             fi
 110             command="${command} -v $1"
 111             shift
 112             continue
 113         ;;
 114 
 115         -ins|--ins|-insensitive|--insensitive)
 116             case_insensitive=1
 117             shift
 118             continue
 119         ;;
 120 
 121         -tsv|--tsv)
 122             command="${command} -F \"\\t\""
 123             shift
 124             continue
 125         ;;
 126 
 127         -blue|--blue)
 128             ansi='38;2;0;95;215m'
 129             shift
 130             continue
 131         ;;
 132 
 133         -bb|--bb|-blueback|--blueback)
 134             ansi='48;2;0;95;215m\x1b[38;2;238;238;238m'
 135             shift
 136             continue
 137         ;;
 138 
 139         -bold|--bold|-bolded|--bolded)
 140             ansi='1m'
 141             shift
 142             continue
 143         ;;
 144 
 145         -dim|--dim|-faint|--faint|-gray|--gray)
 146             ansi='38;2;168;168;168m'
 147             shift
 148             continue
 149         ;;
 150 
 151         -grayback|--grayback)
 152             ansi='48;2;168;168;168m\x1b[38;2;238;238;238m'
 153             shift
 154             continue
 155         ;;
 156 
 157         -green|--green)
 158             ansi='38;2;0;135;95m'
 159             shift
 160             continue
 161         ;;
 162 
 163         -gb|--gb|-greenback|--greenback)
 164             ansi='48;2;0;135;95m\x1b[38;2;238;238;238m'
 165             shift
 166             continue
 167         ;;
 168 
 169         -inv|--inv|-inverse|--inverse|-invert|--invert|-inverted|--inverted)
 170             ansi='7m'
 171             shift
 172             continue
 173         ;;
 174 
 175         -orange|--orange)
 176             ansi='38;2;215;95;0m'
 177             shift
 178             continue
 179         ;;
 180 
 181         -ob|--ob|-orangeback|--orangeback)
 182             ansi='48;2;215;95;0m\x1b[38;2;238;238;238m'
 183             shift
 184             continue
 185         ;;
 186 
 187         -purple|--purple)
 188             ansi='38;2;135;95;255m'
 189             shift
 190             continue
 191         ;;
 192 
 193         -pb|--pb|-purpleback|--purpleback)
 194             ansi='48;2;135;95;255m\x1b[38;2;238;238;238m'
 195             shift
 196             continue
 197         ;;
 198 
 199         -red|--red)
 200             ansi='38;2;204;0;0m'
 201             shift
 202             continue
 203         ;;
 204 
 205         -rb|--rb|-redback|--redback)
 206             ansi='48;2;204;0;0m\x1b[38;2;238;238;238m'
 207             shift
 208             continue
 209         ;;
 210 
 211         -strike|--strike|-striked|--striked|-stricken|--stricken)
 212             ansi='9m'
 213             shift
 214             continue
 215         ;;
 216 
 217         -underline|--underline|-underlined|--underlined)
 218             ansi='4m'
 219             shift
 220             continue
 221         ;;
 222 
 223         -*)
 224             command="${command} ${arg}"
 225             shift
 226             continue
 227         ;;
 228     esac
 229 
 230     break
 231 done
 232 
 233 cond="${1:-1}"
 234 [ $# -gt 0 ] && shift
 235 
 236 # show all non-existing files given
 237 failed=0
 238 for arg in "$@"; do
 239     if [ "${arg}" = "-" ]; then
 240         continue
 241     fi
 242     if [ ! -e "${arg}" ]; then
 243         printf "no file named \"%s\"\n" "${arg}" > /dev/stderr
 244         failed=1
 245     fi
 246 done
 247 
 248 if [ "${failed}" -gt 0 ]; then
 249     exit 2
 250 fi
 251 
 252 ci='
 253     BEGIN {
 254         if (IGNORECASE == "") {
 255             m = "your `awk` command lacks case-insensitive regex-matching"
 256             printf("\x1b[38;2;204;0;0m%s\x1b[0m\n", m) > "/dev/stderr"
 257             exit 125
 258         }
 259         IGNORECASE = 1
 260     }
 261 '
 262 if [ "${case_insensitive}" -eq 0 ]; then
 263     ci=''
 264 fi
 265 
 266 ${command} "${ci}"'
 267     '"${cond}"' {
 268         gsub(/\x1b\[0m/, "\x1b[0m\x1b['"${ansi}"'")
 269         printf "\x1b['"${ansi}"'%s\x1b[0m%s", $0, ORS
 270         next
 271     }
 272     1
 273 ' "$@"