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