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