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