File: sfs.sh 1 #!/bin/sh 2 3 # The MIT License (MIT) 4 # 5 # Copyright © 2020-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 # sfs [options...] [filenames...] 27 # 28 # Show File Sizes, making numbers easier to read by using ANSI styles. 29 30 31 case "$1" in 32 -h|--h|-help|--help) 33 awk '/^# +sfs /, /^$/ { gsub(/^# ?/, ""); print }' "$0" 34 exit 0 35 ;; 36 esac 37 38 [ "$1" = "--" ] && shift 39 40 # turn arg-list into single-item lines 41 # printf "%s\n" "$@" | 42 43 # turn arg-list into single-item lines 44 for arg in "${@:-.}"; do 45 if [ -f "${arg}" ]; then 46 # printf "%s\n" "$(realpath "${arg}")" 47 printf "%s\n" "${arg}" 48 continue 49 fi 50 if [ -d "${arg}" ]; then 51 stdbuf -oL find "${arg}" -type f 52 fi 53 done | awk '!c[$0]++ { print; fflush() }' | 54 # calculate file-sizes, and reverse-sort results 55 xargs -d '\n' wc -c | sort -rn | 56 # add/realign fields to improve legibility 57 awk ' 58 # start output with a header-like line, and add a MiB field 59 BEGIN { printf "%6s %10s %8s name\n", "n", "bytes", "MiB" } 60 # make table breathe with empty lines, so tall outputs are readable 61 (NR - 1) % 5 == 1 { print "" } 62 # emit regular output lines 63 { 64 printf "%6d %10d %8.2f ", NR - 1, $1, $1 / 1048576 65 # first field is likely space-padded 66 gsub(/^ */, "") 67 # slice line after the first field, as filepaths can have spaces 68 $0 = substr($0, length($1) + 1) 69 # first field is likely space-padded 70 gsub(/^ /, "") 71 printf "%s\n", $0 72 } 73 ' | 74 # make zeros in the MiB field stand out with a special color 75 awk ' 76 { 77 gsub(/ 00*\.00* /, "\x1b[38;2;135;135;175m&\x1b[0m") 78 print 79 } 80 ' | 81 # make numbers nice, alternating styles along 3-digit groups 82 # restyle numbers with at least 4 digits to make them easier to read; ignore 83 # numbers in the command fields at the end of lines 84 awk ' 85 { 86 s = $0 87 88 # restyle digit-runs which are longer than 3 89 while (match(s, /[0-9]{4,}/)) { 90 # give up on digit-runs which are unusually long, to mitigate the 91 # quadratic time-complexity of slicing strings in a loop 92 if (RLENGTH > 1000) { 93 printf "%s", substr(s, 1, RSTART + RLENGTH) 94 s = substr(s, RSTART + RLENGTH) 95 continue 96 } 97 98 len = RLENGTH 99 lead = len % 3 100 alt = lead > 0 101 102 printf "%s", substr(s, 1, RSTART + lead - 1) 103 s = substr(s, RSTART + lead) 104 len -= lead 105 106 while (len > 0) { 107 if (alt == 1) { 108 printf "\x1b[38;2;168;168;168m%s\x1b[0m", substr(s, 1, 3) 109 } else { 110 printf "%s", substr(s, 1, 3) 111 } 112 113 alt = 1 - alt 114 s = substr(s, 4) 115 len -= 3 116 } 117 } 118 119 printf "%s\n", s 120 } 121 ' | 122 # make result interactively browsable 123 less -MKiCRS