-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsolution.sh
More file actions
150 lines (133 loc) · 3.57 KB
/
solution.sh
File metadata and controls
150 lines (133 loc) · 3.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#!/usr/bin/env bash
# file-ext-summary.sh - count files by extension and print a sorted summary
# Usage: ./file-ext-summary.sh [-r] [directory]
# -r : recursive
# -h|--help : show help
set -o errexit
set -o pipefail
set -o nounset
usage() {
cat <<EOF >&2
Usage: $0 [-r] [directory]
Options:
-r Recurse into subdirectories
-h, --help Show this help and exit
If directory is omitted, the current working directory is used.
Files without an extension are grouped under the label "no_extension".
Output is sorted by descending count: COUNT EXTENSION
EOF
}
# Parse options
recurse=false
target=""
while [ "$#" -gt 0 ]; do
case "$1" in
-r)
recurse=true
shift
;;
-h|--help)
usage
exit 0
;;
--)
shift
break
;;
-*)
echo "Error: Unknown option: $1" >&2
usage
exit 2
;;
*)
if [ -n "$target" ]; then
echo "Error: Multiple directories specified: '$target' and '$1'" >&2
usage
exit 2
fi
target="$1"
shift
;;
esac
done
# Default target
if [ -z "${target:-}" ]; then
target='.'
fi
# Validate target
if [ ! -d "$target" ]; then
echo "Error: Directory not found: $target" >&2
exit 2
fi
# Detect if `find` supports -maxdepth (portable fallback if not)
find_supports_maxdepth() {
# run a harmless find with -maxdepth; suppress output
if find "$target" -maxdepth 0 -print >/dev/null 2>&1; then
return 0
else
return 1
fi
}
# Fallback emitter for non-recursive listing using shell globbing (handles hidden files)
emit_files_nonrecursive() {
# Enable dotglob to include hidden files; nullglob avoids literal patterns when no matches
shopt -s nullglob dotglob
for f in "$target"/* "$target"/.*; do
base=${f##*/}
# skip directory entries '.' and '..'
if [ "$base" = "." ] || [ "$base" = ".." ]; then
continue
fi
if [ -f "$f" ]; then
printf '%s\0' "$f"
fi
done
shopt -u nullglob dotglob
}
# Choose file source (NUL-delimited filenames)
if [ "$recurse" = true ]; then
file_source() { find "$target" -type f -print0; }
else
if find_supports_maxdepth; then
file_source() { find "$target" -maxdepth 1 -type f -print0; }
else
file_source() { emit_files_nonrecursive; }
fi
fi
# Process files: extract extension rules, lowercase, emit one extension per line
# Rules for extension extraction:
# - Use text after the last dot as extension
# - Files that start with a single leading dot and have no other dot are treated as no_extension (e.g. .bashrc)
# - Empty extension (e.g. "file.") is treated as no_extension
# - Group case-insensitively by converting to lowercase
# Produce counts: sort -> uniq -c -> sort by numeric descending -> format output
file_source | \
while IFS= read -r -d '' file; do
base=${file##*/}
# determine extension
if [[ "$base" == .* ]]; then
# starts with dot; check if there's another dot in the rest
rest=${base#.}
if [[ "$rest" != *.* ]]; then
ext="no_extension"
else
ext="${base##*.}"
fi
else
if [[ "$base" == *.* ]]; then
ext="${base##*.}"
else
ext="no_extension"
fi
fi
# treat empty extension as no_extension
if [ -z "${ext:-}" ]; then
ext="no_extension"
fi
# lowercase the extension (POSIX tr)
ext_lc=$(printf '%s' "$ext" | tr '[:upper:]' '[:lower:]')
# print one extension per line (safe: extensions won't contain newlines normally)
printf '%s\n' "$ext_lc"
done | \
sort | uniq -c | sort -rn -k1,1 | awk '{printf "%s %s\n", $1, $2}'
exit 0