File: realign.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 # realign [options...] [files...] 27 # 28 # Realign all detected columns, right-aligning detected numbers, and keeping 29 # ANSI-sequences as given. 30 # 31 # The options are, available both in single and double-dash versions 32 # 33 # -h, -help show this help message 34 # -m, -most use the row with the most items for the item-count 35 # -t, -tsv force TSV-input mode, no matter the first line 36 37 38 sep=' ' 39 most=0 40 41 while [ $# -gt 0 ]; do 42 case "$1" in 43 -h|--h|-help|--help) 44 awk '/^# +realign /, /^$/ { gsub(/^# ?/, ""); print }' "$0" 45 exit 0 46 ;; 47 48 -m|--m|-most|--most) most=1 && shift && continue ;; 49 50 -t|--t|-tsv|--tsv) sep="\t" && shift && continue ;; 51 esac 52 53 break 54 done 55 56 [ "$1" = '--' ] && shift 57 58 # show all non-existing files given 59 failed=0 60 for arg in "$@"; do 61 [ "${arg}" = "-" ] && continue 62 [ -e "${arg}" ] && continue 63 printf "no file named \"%s\"\n" "${arg}" >&2 64 failed=1 65 done 66 67 [ "${failed}" -gt 0 ] && exit 2 68 69 awk -F "${sep}" -v most="${most}" ' 70 BEGIN { if (SUBSEP == "") SUBSEP = "\034" } 71 72 # ignore trailing carriage-returns 73 { gsub(/\r$/, "") } 74 75 # first non-empty line auto-detects SSV vs. TSV, and the column-count 76 nc == 0 { nc = NF; if (/\t/) { FS = "\t"; $0 = $0 } } 77 78 nc > 0 { 79 if (most && nc < NF) nc = NF 80 nitems[++nr] = NF 81 82 for (i = 1; i <= NF; i++) { 83 data[nr SUBSEP i] = $i 84 85 plain = $i 86 gsub(/\x1b\[[0-9;]*[A-Za-z]/, "", plain) 87 w = length(plain) 88 if (widths[i] < w) widths[i] = w 89 90 if (!match(plain, /^[+-]?[0-9]+(\.[0-9]+)?$/)) continue 91 92 # item is a number: check for decimals 93 if (!match(plain, /\./)) continue 94 95 if (int_widths[i] < RSTART) int_widths[i] = RSTART 96 w -= RSTART - 1 97 if (dot_decs[i] < w) dot_decs[i] = w 98 } 99 } 100 101 function show_item() { 102 v = data[i SUBSEP j] 103 104 # put 2-space gaps between columns 105 if (1 < j) due += 2 106 107 if (v ~ /^ *$/) { 108 due += widths[j] 109 return 110 } 111 112 plain = v 113 gsub(/\x1b\[[0-9;]*[A-Za-z]/, "", plain) 114 w = length(plain) 115 116 # handle non-numbers 117 if (!match(plain, /^[+-]?[0-9]+(\.[0-9]+)?$/)) { 118 for (k = 1; k <= due; k++) printf " " 119 printf "%s", v 120 121 due = widths[j] - w 122 return 123 } 124 125 # count `dot-decimals` trail 126 dd = match(plain, /\./) ? w - (RSTART - 1) : 0 127 128 rpad = dot_decs[j] - dd 129 lpad = widths[j] - (w + rpad) + due 130 131 for (k = 1; k <= lpad; k++) printf " " 132 printf "%s", v 133 134 due = rpad 135 } 136 137 END { 138 # decimal numbers can pad columns beyond their max-width 139 for (j = 1; j <= nc; j++) { 140 w = int_widths[j] + dot_decs[j] 141 if (widths[j] < w) widths[j] = w 142 } 143 144 for (i = 1; i <= nr; i++) { 145 due = 0 146 for (j = 1; j <= nc; j++) show_item() 147 148 # treat extra columns as part of the last one 149 last = nitems[i] 150 for (j = nc + 1; j <= last; j++) printf " %s", data[i SUBSEP j] 151 152 print "" 153 } 154 } 155 ' "$@"