Skip to content

Commit 33fbee0

Browse files
committed
Improved hist stats and du charts
1 parent b68d302 commit 33fbee0

File tree

1 file changed

+142
-84
lines changed

1 file changed

+142
-84
lines changed

bin/hhs-functions/bash/hhs-shell-utils.bash

Lines changed: 142 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -29,63 +29,81 @@ function __hhs_history() {
2929
return $?
3030
}
3131

32-
# inspiRED by https://superuser.com/questions/250227/how-do-i-see-what-my-most-used-linux-command-are
33-
# @function: Display statistics about commands in history.
32+
# @function: Display statistics about commands in history (robust date/time filtering)
3433
# @param $1 [Opt] : Limit to the top N commands.
3534
function __hhs_hist_stats() {
36-
37-
local top_n=${1:-10} width=${2:-30} i=1 cmd_name cmd_qty hist_output bar_len bar columns pad_len pad
35+
local top_n=${1:-10} width=${2:-30} i=1
36+
local cmd_name cmd_qty hist_output bar_len bar columns pad_len pad max_size
3837

3938
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
4039
echo "usage: ${FUNCNAME[0]} [top_N]"
41-
return 1
40+
return 0
4241
fi
4342

44-
hist_output="$(history | tr -s ' ' | cut -d ' ' -f6 | sort | uniq -c | sort -nr)"
43+
# Generic parser: remove metadata, extract first non-date token as command
44+
hist_output="$(
45+
history |
46+
sed -E 's/^\[[^]]*\][[:space:]]*//' | # remove [user,date,...]
47+
sed -E 's/^[[:space:]]*[0-9]+\**[[:space:]]*//' | # remove numeric ids
48+
awk '{
49+
for (i=1; i<=NF; i++) {
50+
token=$i
51+
# Skip timestamps and dates (e.g., 2025-11-05, 23:47:12, etc.)
52+
if (token ~ /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/) continue
53+
if (token ~ /^[0-9]{2}:[0-9]{2}:[0-9]{2}$/) continue
54+
if (token ~ /^([0-9]{4}-[0-9]{2}-[0-9]{2})[T ]([0-9]{2}:[0-9]{2}:[0-9]{2})$/) continue
55+
if (token ~ /^[0-9]{1,4}$/) continue
56+
if (token ~ /^[[:punct:]]+$/) continue
57+
if (token ~ /^[[:alnum:]_.\/:+-]+$/) { CMD[token]++; break }
58+
}
59+
}
60+
END { for (c in CMD) print CMD[c], c }' |
61+
sort -nr
62+
)"
63+
64+
[[ -z "${hist_output}" ]] && {
65+
__hhs_errcho "${FUNCNAME[0]}" "No valid command tokens found in history."
66+
return 1
67+
}
68+
69+
columns=$(tput cols 2>/dev/null || echo 80)
70+
pad_len=$((columns / 2))
71+
pad=$(printf '%0.1s' "."{1..120})
4572

46-
columns=$(tput cols)
47-
pad=$(printf '%0.1s' "."{1..41})
48-
pad_len=41
4973
max_size=$(echo "${hist_output}" | head -n 1 | awk '{print $1}')
74+
[[ -z "${max_size}" || "${max_size}" -le 0 ]] && max_size=1
5075

51-
echo ' '
52-
echo "${YELLOW}Top ${top_n} used commands in history:"
53-
echo ' '
76+
echo ''
77+
echo "${YELLOW}Top ${top_n} used commands in history:${NC}"
78+
echo ''
5479

55-
hist_output="$(echo "${hist_output}" | head -n "${top_n}")"
80+
while read -r cmd_qty cmd_name; do
81+
[[ -z "${cmd_qty}" || -z "${cmd_name}" ]] && continue
5682

57-
echo "${hist_output}" | while read -r line; do
58-
if [[ "${line}" =~ ^[[:space:]]*([0-9]+)[[:space:]]*([a-zA-Z0-9]+)$ ]]; then
83+
bar_len=$(((cmd_qty * width) / max_size))
84+
((bar_len < 1)) && bar_len=1
5985

60-
cmd_qty="${BASH_REMATCH[1]}"
61-
cmd_name="${BASH_REMATCH[2]}"
62-
cmd_name="${cmd_name:0:$columns}"
86+
if __hhs_has seq; then
87+
bar=$(printf '▄%.0s' $(seq 1 "${bar_len}"))
88+
elif __hhs_has jot; then
89+
bar=$(printf '▄%.0s' "$(jot - 1 "${bar_len}")")
90+
else
91+
__hhs_errcho "${FUNCNAME[0]}" "Neither seq nor jot is available."
92+
return 1
93+
fi
6394

64-
bar_len=$(((cmd_qty * width) / max_size))
65-
[[ $bar_len -gt 0 && $bar_len -lt 2 ]] && bar_len=2
95+
[[ ${#cmd_name} -gt $((columns - 20)) ]] && cmd_name="${cmd_name:0:$((columns - 20))}"
6696

67-
if __hhs_has seq; then
68-
bar=$(printf '▄%.0s' $(seq 1 "${bar_len}"))
69-
elif __hhs_has jot; then
70-
bar=$(printf '▄%.0s' "$(jot - 1 "${bar_len}")")
71-
else
72-
__hhs_errcho "${FUNCNAME[0]}" "Neither seq nor jot is available."
73-
return 1
74-
fi
97+
printf "${WHITE}%3d: ${HHS_HIGHLIGHT_COLOR} " "${i}"
98+
printf "%s" "${cmd_name}"
99+
printf '%*.*s' 0 $((pad_len - ${#cmd_name})) "${pad}"
100+
printf "${GREEN}%4d ${ORANGE}|%s${NC}\n" "${cmd_qty}" "${bar}"
75101

76-
printf "${WHITE}%3d: ${HHS_HIGHLIGHT_COLOR} " "${i}"
77-
printf "%s " "${cmd_name}"
78-
[[ "${#cmd_name}" -ge "${columns}" ]] && echo -en "${NC}" || echo -en "${NC}"
79-
printf '%*.*s' 0 $((pad_len - ${#cmd_name})) "${pad}"
80-
printf "${GREEN}%3d ${ORANGE}|%s\n" "${cmd_qty}" "${bar}"
81-
((i += 1))
82-
fi
83-
done
102+
((i += 1))
103+
done < <(echo "${hist_output}" | head -n "${top_n}")
84104

85-
IFS="${OLDIFS}"
105+
echo ''
86106
echo "${NC}"
87-
88-
return 0
89107
}
90108

91109
# @function: Display the current dir (pwd) and remote repo url, if it applies.
@@ -235,75 +253,115 @@ function __hhs_shopt() {
235253
return 1
236254
}
237255

238-
# @function: Display 'du' output formatted as a horizontal bar chart.
256+
# @function: Display 'du' output formatted as a horizontal bar chart (auto unit scaling).
239257
# @param $1 [Opt] : Directory path (default: current directory)
240258
# @param $2 [Opt] : Number of top entries to display (default: 10)
259+
# @param $3 [Opt] : Chart bar width scaling factor (default: 30)
241260
function __hhs_du() {
242-
local dir="${1:-.}" i=1 top_n=${2:-10} width=${3:-30} max_size du_output columns pad_len pad bar_len bar path
261+
local dir="${1:-.}"
262+
local top_n=${2:-10}
263+
local width=${3:-30}
264+
local i=1 du_output term_w pad_len pad max_val bar_len bar path entry_count
265+
local size_human size_kib
243266

244267
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
245-
echo "usage: ${FUNCNAME[0]} [path] [top_N]"
246-
return 1
268+
echo "usage: ${FUNCNAME[0]} [path] [top_N] [width]"
269+
return 0
247270
fi
248271

249272
[[ ! -d "${dir}" ]] && {
250273
__hhs_errcho "${FUNCNAME[0]}" "Directory not found: \"${dir}\""
251274
return 1
252275
}
253276

277+
# Collect du output (human readable)
254278
if [[ "$(uname -s)" == "Darwin" ]]; then
255-
du_output="$(\du -hkd 1 "${dir}" 2>/dev/null | sort -rn | head -n "$((top_n + 1))")"
279+
du_output="$(\du -hd 1 "${dir}" 2>/dev/null | grep -v 'total' | sort -hr)"
256280
else
257-
du_output="$(\du -hk --max-depth=1 "${dir}" 2>/dev/null | sort -rn | head -n "$((top_n + 1))")"
281+
du_output="$(\du -h --max-depth=1 "${dir}" 2>/dev/null | grep -v 'total' | sort -hr)"
258282
fi
259283

260-
columns=$(tput cols)
261-
pad_len=60
262-
pad=$(printf '%0.1s' "."{1..60})
263-
max_size=$(echo "${du_output}" | awk '{print $1}' | sort -n | awk '{
264-
a[NR]=$1
265-
} END {
266-
idx = int(NR * 0.9)
267-
if (idx < 1) idx = 1
268-
print a[idx]
269-
}')
270-
271-
echo ' '
272-
echo "${YELLOW}Top ${top_n} disk usage at: ${BLUE}\"${dir//\./$(pwd)}\""
273-
echo ' '
274-
275-
while read -r size path; do
276-
size=$(echo "${size}" | tr -d '[:space:]')
277-
[[ -z "${size}" || -z "${path}" || "${path}" == '.' || "${path}" == 'total' ]] && continue
278-
bar_len=$(((size * width) / max_size))
279-
[[ $bar_len -gt 0 && $bar_len -lt 2 ]] && bar_len=2
280-
281-
if [[ $bar_len -gt 0 ]]; then
282-
if __hhs_has seq; then
283-
bar=$(printf '▄%.0s' $(seq 1 "${bar_len}"))
284-
elif __hhs_has jot; then
285-
bar=$(printf '▄%.0s' "$(jot - 1 "${bar_len}")")
286-
else
287-
__hhs_errcho "${FUNCNAME[0]}" "Neither seq nor jot is available."
288-
return 1
289-
fi
284+
entry_count=$(echo "${du_output}" | wc -l | tr -d '[:space:]')
285+
[[ "${entry_count}" -eq 0 ]] && {
286+
__hhs_errcho "${FUNCNAME[0]}" "No usable entries found in: \"${dir}\""
287+
return 1
288+
}
289+
290+
du_output=$(echo "${du_output}" | head -n "${top_n}")
291+
292+
term_w=$(tput cols 2>/dev/null || echo 80)
293+
((term_w < 40)) && term_w=80
294+
pad_len=$((term_w / 2))
295+
pad=$(printf '%0.1s' "."{1..120})
296+
297+
# Compute max size in KiB for bar scaling
298+
max_val=$(echo "${du_output}" | awk '
299+
function to_kib(v) {
300+
unit=tolower(substr(v, length(v)))
301+
val=substr(v, 1, length(v)-1)
302+
if (unit=="g") return int(val*1024*1024)
303+
if (unit=="m") return int(val*1024)
304+
if (unit=="k") return int(val)
305+
if (unit=="b") return int(val/1024)
306+
return int(v)
307+
}
308+
{print to_kib($1)}' | sort -n | tail -1)
309+
[[ -z "${max_val}" || "${max_val}" -le 0 ]] && max_val=1
310+
311+
echo ''
312+
echo "${YELLOW}Top ${top_n} disk usage at: ${BLUE}\"${dir//\./$(pwd)}\"${NC}"
313+
echo ''
314+
315+
while read -r size_human path; do
316+
[[ -z "${size_human}" || -z "${path}" ]] && continue
317+
318+
# Convert to KiB for scaling
319+
size_kib=$(awk -v v="${size_human}" '
320+
function to_kib(x) {
321+
unit=tolower(substr(x, length(x)))
322+
val=substr(x, 1, length(x)-1)
323+
if (unit=="g") return int(val*1024*1024)
324+
if (unit=="m") return int(val*1024)
325+
if (unit=="k") return int(val)
326+
if (unit=="b") return int(val/1024)
327+
return int(x)
328+
}
329+
BEGIN { print to_kib(v) }')
330+
331+
# Normalize path
332+
if [[ "${path}" == '.' ]]; then
333+
path="./$(basename "${dir}")"
334+
else
335+
path="${path//\.\//}"
336+
path="${path//\/\//\/}"
337+
fi
338+
339+
bar_len=$(((size_kib * width) / max_val))
340+
((bar_len < 1)) && bar_len=1
341+
342+
if __hhs_has seq; then
343+
bar=$(printf '▄%.0s' $(seq 1 "${bar_len}"))
344+
elif __hhs_has jot; then
345+
bar=$(printf '▄%.0s' "$(jot - 1 "${bar_len}")")
346+
else
347+
__hhs_errcho "${FUNCNAME[0]}" "Neither seq nor jot is available."
348+
return 1
290349
fi
291350

292-
path="${path//\.\//}"
293-
path="${path//\/\//\/}"
294-
path="${path:0:$columns}"
351+
# Safe truncation for narrow terminals
352+
local max_label_width=$((term_w - 30))
353+
((max_label_width < 10)) && max_label_width=10
354+
[[ ${#path} -gt ${max_label_width} ]] && path="${path:0:${max_label_width}}"
295355

296356
printf "${WHITE}%3d: ${HHS_HIGHLIGHT_COLOR} " "${i}"
297357
printf "%s" "${path}"
298358
printf '%*.*s' 0 $((pad_len - ${#path})) "${pad}"
299-
[[ "${#path}" -ge "${columns}" ]] && echo -en "${NC}" || echo -en "${NC}"
300-
printf "${GREEN}%$((${#max_size} + 1))d KB ${ORANGE}|%s \n" " ${size}" "${bar}"
359+
printf "${GREEN}%8s ${ORANGE}|%s${NC}\n" "${size_human}" "${bar}"
360+
301361
((i += 1))
302362
done <<<"${du_output}"
303363

304364
echo ''
305-
echo "${WHITE}Total: ${ORANGE}$(\du -hc "${dir}" | grep total | cut -f1)"
306-
unset TOTAL
307-
308-
echo "${NC}"
365+
echo "${WHITE}Total: ${ORANGE}$(\du -sh "${dir}" 2>/dev/null | awk '{print $1}')${NC}"
366+
echo ''
309367
}

0 commit comments

Comments
 (0)