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] [files...]
  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, -F fs, -Ffs, -F=fs    make `fs` the field separator
  39 #
  40 # The other options are, available both in single and double-dash versions
  41 #
  42 #   -h, -help    show this help message
  43 #   -i, -ins     match regexes case-insensitively; may fail the default `awk`
  44 #   -tsv         split fields using tabs, same as using -F "\t"
  45 #
  46 #   -blue                 use a blue style
  47 #   -gray                 use a gray style
  48 #   -green                use a green style
  49 #   -orange               use an orange style
  50 #   -purple               use a purple style
  51 #   -red                  use a red style
  52 #
  53 #   -bb, -blueback        use a blue-background style
  54 #        -grayback        use a gray-background style
  55 #   -gb, -greenback       use a green-background style
  56 #   -ob, -orangeback      use an orange-background style
  57 #   -pb, -purpleback      use a purple-background style
  58 #   -rb, -redback         use a red-background style
  59 
  60 
  61 case "$1" in
  62     -h|--h|-help|--help)
  63         awk '/^# +hawk /, /^$/ { gsub(/^# ?/, ""); print }' "$0"
  64         exit 0
  65     ;;
  66 esac
  67 
  68 tsv=0
  69 ansi='7m'
  70 buffered=0
  71 case_insensitive=0
  72 command='awk'
  73 
  74 while [ $# -gt 0 ]; do
  75     if [ "$1" = "--" ]; then
  76         shift
  77         break
  78     fi
  79 
  80     case "$1" in
  81         -b|--b|-buffered|--buffered)
  82             buffered=1
  83             shift
  84             continue
  85         ;;
  86 
  87         -f|-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} $1"
 100             shift
 101             continue
 102         ;;
 103 
 104         -i|--i|-ins|--ins|-insensitive|--insensitive)
 105             case_insensitive=1
 106             shift
 107             continue
 108         ;;
 109 
 110         -tsv|--tsv)
 111             tsv=1
 112             shift
 113             continue
 114         ;;
 115 
 116         -blue|--blue)
 117             ansi='38;2;0;95;215m'
 118             shift
 119             continue
 120         ;;
 121 
 122         -bb|--bb|-blueback|--blueback)
 123             ansi='48;2;0;95;215m\x1b[38;2;238;238;238m'
 124             shift
 125             continue
 126         ;;
 127 
 128         -bold|--bold|-bolded|--bolded)
 129             ansi='1m'
 130             shift
 131             continue
 132         ;;
 133 
 134         -dim|--dim|-faint|--faint|-gray|--gray)
 135             ansi='38;2;168;168;168m'
 136             shift
 137             continue
 138         ;;
 139 
 140         -grayback|--grayback)
 141             ansi='48;2;168;168;168m\x1b[38;2;238;238;238m'
 142             shift
 143             continue
 144         ;;
 145 
 146         -green|--green)
 147             ansi='38;2;0;135;95m'
 148             shift
 149             continue
 150         ;;
 151 
 152         -gb|--gb|-greenback|--greenback)
 153             ansi='48;2;0;135;95m\x1b[38;2;238;238;238m'
 154             shift
 155             continue
 156         ;;
 157 
 158         -inv|--inv|-inverse|--inverse|-invert|--invert|-inverted|--inverted)
 159             ansi='7m'
 160             shift
 161             continue
 162         ;;
 163 
 164         -orange|--orange)
 165             ansi='38;2;215;95;0m'
 166             shift
 167             continue
 168         ;;
 169 
 170         -ob|--ob|-orangeback|--orangeback)
 171             ansi='48;2;215;95;0m\x1b[38;2;238;238;238m'
 172             shift
 173             continue
 174         ;;
 175 
 176         -purple|--purple)
 177             ansi='38;2;135;95;255m'
 178             shift
 179             continue
 180         ;;
 181 
 182         -pb|--pb|-purpleback|--purpleback)
 183             ansi='48;2;135;95;255m\x1b[38;2;238;238;238m'
 184             shift
 185             continue
 186         ;;
 187 
 188         -red|--red)
 189             ansi='38;2;204;0;0m'
 190             shift
 191             continue
 192         ;;
 193 
 194         -rb|--rb|-redback|--redback)
 195             ansi='48;2;204;0;0m\x1b[38;2;238;238;238m'
 196             shift
 197             continue
 198         ;;
 199 
 200         -strike|--strike|-striked|--striked|-stricken|--stricken)
 201             ansi='9m'
 202             shift
 203             continue
 204         ;;
 205 
 206         -underline|--underline|-underlined|--underlined)
 207             ansi='4m'
 208             shift
 209             continue
 210         ;;
 211     esac
 212 
 213     break
 214 done
 215 
 216 cond="${1:-1}"
 217 [ $# -gt 0 ] && shift
 218 
 219 # show all non-existing files given
 220 failed=0
 221 for arg in "$@"; do
 222     if [ "${arg}" = "-" ]; then
 223         continue
 224     fi
 225     if [ ! -e "${arg}" ]; then
 226         printf "no file named \"%s\"\n" "${arg}" >&2
 227         failed=1
 228     fi
 229 done
 230 
 231 if [ "${failed}" -gt 0 ]; then
 232     exit 2
 233 fi
 234 
 235 flush=''
 236 if [ "${buffered}" -eq 0 ] && { [ -p /dev/stdout ] || [ -t 1 ]; }; then
 237     flush='fflush()'
 238 fi
 239 
 240 ci='
 241     BEGIN {
 242         if (IGNORECASE == "") {
 243             m = "your `awk` command lacks case-insensitive regex-matching"
 244             print(m) > "/dev/stderr"
 245             exit 125
 246         }
 247         IGNORECASE = 1
 248     }
 249 '
 250 if [ "${case_insensitive}" -eq 0 ]; then
 251     ci=''
 252 fi
 253 
 254 src="${ci}"'
 255     '"${cond}"' {
 256         gsub(/\x1b\[0m/, "\x1b[0m\x1b['"${ansi}"'")
 257         printf "\x1b['"${ansi}"'%s\x1b[0m%s", $0, ORS
 258         '"${flush}"'
 259         next
 260     }
 261     1
 262 '
 263 
 264 if [ "${tsv}" -eq 1 ]; then
 265     ${command} -F "\t" "${src}" "$@"
 266 else
 267     ${command} "${src}" "$@"
 268 fi