File: uawk.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 # uawk [options...] [awk expression...] [files...] 27 # 28 # 29 # Unique via AWK avoids lines duplicating the AWK expression given, by 30 # emitting only the first line which gives the expression each of its 31 # unique results. 32 # 33 # When not given an expression, the default is to use whole lines/records. 34 # This default effectively makes it a line deduplicator which keeps lines 35 # in their original order, unlike `uniq`, which requires sorted lines to 36 # avoid repetitions. 37 # 38 # A `.` (no quotes) for the expression also enables the default behavior. 39 # 40 # The handy case-insensitive shortcut options may cause this tool to fail, 41 # if the main `awk` tool installed doesn't support the special `IGNORECASE` 42 # variable. 43 # 44 # The AWK options available only in single-dash versions are 45 # 46 # -F fs, -Ffs, -F=fs make `fs` the field separator 47 # 48 # The other options are, available both in single and double-dash versions 49 # 50 # -h, -help show this help message 51 # -i, -ins match regexes case-insensitively; may fail `awk` 52 # -tsv split fields using tabs, same as using -F "\t" 53 54 55 tsv=0 56 buf=0 57 ins=0 58 cmd='awk' 59 60 while [ $# -gt 0 ]; do 61 case "$1" in 62 63 -b|--b|-buffered|--buffered) buf=1; shift; continue ;; 64 65 -F) 66 if [ $# -lt 2 ]; then 67 printf "%s: expected value after -F option\n" "$0" >&2 68 exit 1 69 fi 70 cmd="${cmd} -F $2"; shift 2; continue 71 ;; 72 73 -F*) cmd="${cmd} $1"; shift; continue ;; 74 75 -h|--h|-help|--help) 76 awk '/^# +uawk /, /^$/ { gsub(/^# ?/, ""); print }' "$0" 77 exit 0 78 ;; 79 80 -i|--i|-ins|--ins|-insensitive|--insensitive) 81 ins=1; shift; continue 82 ;; 83 84 -tsv|--tsv) tsv=1; shift; continue ;; 85 86 -v) 87 if [ $# -lt 2 ]; then 88 printf "%s: missing variable assignment after -v option\n" "$0" >&2 89 exit 1 90 fi 91 cmd="${cmd} -v $2"; shift 2; continue 92 ;; 93 94 -) break ;; 95 96 --) shift; break ;; 97 98 -*) 99 printf "%s: unsupported option %s\n" "$0" "$1" >&2 100 exit 1 101 ;; 102 103 esac 104 105 break 106 done 107 108 code="${1:-\$0}" 109 [ $# -gt 0 ] && shift 110 if [ "${code}" = '.' ]; then 111 code='$0' 112 fi 113 114 # show all non-existing files given 115 failed=0 116 for arg in "$@"; do 117 [ "${arg}" = '-' ] && continue 118 [ -e "${arg}" ] && continue 119 printf "%s: no file named \"%s\"\n" "$0" "${arg}" >&2 120 failed=1 121 done 122 123 [ "${failed}" -gt 0 ] && exit 2 124 125 flush='' 126 if [ "${buf}" -eq 0 ] && { [ -p /dev/stdout ] || [ -t 1 ]; }; then 127 flush='; fflush()' 128 fi 129 130 ci=' 131 BEGIN { 132 if (IGNORECASE == "") { 133 m = "your `awk` command lacks case-insensitive regex-matching" 134 print(m) > "/dev/stderr" 135 exit 125 136 } 137 IGNORECASE = 1 138 } 139 ' 140 if [ "${ins}" -eq 0 ]; then 141 ci='' 142 fi 143 144 src="${ci}"' 145 BEGIN { for (I = 1; I < ARGC; I++) if (FILES[ARGV[I]]++) delete ARGV[I] } 146 !TALLY['"${code}"']++ { print'"${flush}"' } 147 ' 148 149 if [ "${tsv}" -eq 1 ]; then 150 ${cmd} -F "\t" "${src}" "$@" 151 else 152 ${cmd} "${src}" "$@" 153 fi