240ba3c79146db78cadfa5ffe05abb9bac49c9c5
[BK-2020-03.git] / user / list_large_dirs.sh
1 #!/usr/bin/env bash
2 # Desc: Lists directories immediately containing large amounts of data
3 # Usage: script.sh [int megabytes] [dir]
4 # Example: A directory named `vacation_photos` located somewhere in $HOME/
5 # containing more than 100MB of files (without checking subdirectories of
6 # `vacation_photos`) could be found by running:
7 # $ script.sh 100 "$HOME"
8 # /home/johndoe/Pictures/unsorted/notjunk/vacation_photos
9 # Version: 0.0.1
10
11 declare -Ag appRollCall # Associative array for storing app status
12 declare -Ag fileRollCall # Associative array for storing file status
13 declare -Ag dirRollCall # Associative array for storing dir status
14
15 yell() { echo "$0: $*" >&2; } # print script path and all args to stderr
16 die() { yell "$*"; exit 111; } # same as yell() but non-zero exit status
17 must() { "$@" || die "cannot $*"; } # runs args as command, reports args if command fails
18 checkInt() {
19 # Desc: Checks if arg is integer
20 # Usage: checkInt arg
21 # Input: arg: integer
22 # Output: - return code 0 (if arg is integer)
23 # - return code 1 (if arg is not integer)
24 # Example: if ! checkInt $arg; then echo "not int"; fi;
25 # Version: 0.0.1
26 local returnState
27
28 #===Process Arg===
29 if [[ $# -ne 1 ]]; then
30 die "ERROR:Invalid number of arguments:$#";
31 fi;
32
33 RETEST1='^[0-9]+$'; # Regular Expression to test
34 if [[ ! $1 =~ $RETEST1 ]] ; then
35 returnState="false";
36 else
37 returnState="true";
38 fi;
39
40 #===Determine function return code===
41 if [ "$returnState" = "true" ]; then
42 return 0;
43 else
44 return 1;
45 fi;
46 } # Checks if arg is integer
47 checkapp() {
48 # Desc: If arg is a command, save result in assoc array 'appRollCall'
49 # Usage: checkapp arg1 arg2 arg3 ...
50 # Version: 0.1.1
51 # Input: global assoc. array 'appRollCall'
52 # Output: adds/updates key(value) to global assoc array 'appRollCall'
53 # Depends: bash 5.0.3
54 local returnState
55
56 #===Process Args===
57 for arg in "$@"; do
58 if command -v "$arg" 1>/dev/null 2>&1; then # Check if arg is a valid command
59 appRollCall[$arg]="true";
60 if ! [ "$returnState" = "false" ]; then returnState="true"; fi;
61 else
62 appRollCall[$arg]="false"; returnState="false";
63 fi;
64 done;
65
66 #===Determine function return code===
67 if [ "$returnState" = "true" ]; then
68 return 0;
69 else
70 return 1;
71 fi;
72 } # Check that app exists
73 checkfile() {
74 # Desc: If arg is a file path, save result in assoc array 'fileRollCall'
75 # Usage: checkfile arg1 arg2 arg3 ...
76 # Version: 0.1.2
77 # Input: global assoc. array 'fileRollCall'
78 # Output: adds/updates key(value) to global assoc array 'fileRollCall';
79 # Output: returns 0 if app found, 1 otherwise
80 # Depends: bash 5.0.3
81 local returnState
82
83 #===Process Args===
84 for arg in "$@"; do
85 if [ -f "$arg" ]; then
86 fileRollCall["$arg"]="true";
87 if ! [ "$returnState" = "false" ]; then returnState="true"; fi;
88 elif [ -z "$arg" ]; then
89 fileRollCall["(no name)"]="false"; returnState="false";
90 else
91 fileRollCall["$arg"]="false"; returnState="false";
92 fi;
93 done;
94
95 #===Determine function return code===
96 if [ "$returnState" = "true" ]; then
97 return 0;
98 else
99 return 1;
100 fi;
101 } # Check that file exists
102 checkdir() {
103 # Desc: If arg is a dir path, save result in assoc array 'dirRollCall'
104 # Usage: checkdir arg1 arg2 arg3 ...
105 # Version 0.1.2
106 # Input: global assoc. array 'dirRollCall'
107 # Output: adds/updates key(value) to global assoc array 'dirRollCall';
108 # Output: returns 0 if all args are dirs; 1 otherwise
109 # Depends: Bash 5.0.3
110 local returnState
111
112 #===Process Args===
113 for arg in "$@"; do
114 if [ -z "$arg" ]; then
115 dirRollCall["(Unspecified Dirname(s))"]="false"; returnState="false";
116 elif [ -d "$arg" ]; then
117 dirRollCall["$arg"]="true";
118 if ! [ "$returnState" = "false" ]; then returnState="true"; fi
119 else
120 dirRollCall["$arg"]="false"; returnState="false";
121 fi
122 done
123
124 #===Determine function return code===
125 if [ "$returnState" = "true" ]; then
126 return 0;
127 else
128 return 1;
129 fi
130 } # Check that dir exists
131 displayMissing() {
132 # Desc: Displays missing apps, files, and dirs
133 # Usage: displayMissing
134 # Version 1.0.0
135 # Input: associative arrays: appRollCall, fileRollCall, dirRollCall
136 # Output: stderr: messages indicating missing apps, file, or dirs
137 # Output: returns exit code 0 if nothing missing; 1 otherwise
138 # Depends: bash 5, checkAppFileDir()
139 local missingApps value appMissing missingFiles fileMissing
140 local missingDirs dirMissing
141
142 #==BEGIN Display errors==
143 #===BEGIN Display Missing Apps===
144 missingApps="Missing apps :";
145 #for key in "${!appRollCall[@]}"; do echo "DEBUG:$key => ${appRollCall[$key]}"; done
146 for key in "${!appRollCall[@]}"; do
147 value="${appRollCall[$key]}";
148 if [ "$value" = "false" ]; then
149 #echo "DEBUG:Missing apps: $key => $value";
150 missingApps="$missingApps""$key ";
151 appMissing="true";
152 fi;
153 done;
154 if [ "$appMissing" = "true" ]; then # Only indicate if an app is missing.
155 echo "$missingApps" 1>&2;
156 fi;
157 unset value;
158 #===END Display Missing Apps===
159
160 #===BEGIN Display Missing Files===
161 missingFiles="Missing files:";
162 #for key in "${!fileRollCall[@]}"; do echo "DEBUG:$key => ${fileRollCall[$key]}"; done
163 for key in "${!fileRollCall[@]}"; do
164 value="${fileRollCall[$key]}";
165 if [ "$value" = "false" ]; then
166 #echo "DEBUG:Missing files: $key => $value";
167 missingFiles="$missingFiles""$key ";
168 fileMissing="true";
169 fi;
170 done;
171 if [ "$fileMissing" = "true" ]; then # Only indicate if an app is missing.
172 echo "$missingFiles" 1>&2;
173 fi;
174 unset value;
175 #===END Display Missing Files===
176
177 #===BEGIN Display Missing Directories===
178 missingDirs="Missing dirs:";
179 #for key in "${!dirRollCall[@]}"; do echo "DEBUG:$key => ${dirRollCall[$key]}"; done
180 for key in "${!dirRollCall[@]}"; do
181 value="${dirRollCall[$key]}";
182 if [ "$value" = "false" ]; then
183 #echo "DEBUG:Missing dirs: $key => $value";
184 missingDirs="$missingDirs""$key ";
185 dirMissing="true";
186 fi;
187 done;
188 if [ "$dirMissing" = "true" ]; then # Only indicate if an dir is missing.
189 echo "$missingDirs" 1>&2;
190 fi;
191 unset value;
192 #===END Display Missing Directories===
193
194 #==END Display errors==
195 #==BEGIN Determine function return code===
196 if [ "$appMissing" == "true" ] || [ "$fileMissing" == "true" ] || [ "$dirMissing" == "true" ]; then
197 return 1;
198 else
199 return 0;
200 fi
201 #==END Determine function return code===
202 } # Display missing apps, files, dirs
203 check_depends() {
204 local flag_return=0;
205
206 if ! checkapp du; then flag_return="1"; fi;
207 return "$flag_return";
208 }; # returns 1 if missing dependencies
209 size_dir_contents() {
210 # usage: size_dir_contents [path]
211 # depends: find, du
212 local bytes re;
213
214 bytes=0;
215 re="^[0-9]+$";
216
217 #yell "DEBUG:Checking dir:$1";
218 while read -r addend; do
219 #yell "DEBUG:addend:$addend";
220 if [[ $addend =~ $re ]]; then
221 bytes="$((bytes + addend))";
222 fi;
223 done < <( find "$1" -maxdepth 1 -type f -exec du -b --summarize '{}' \; | cut -f1 );
224
225 echo "$bytes" && return 0;
226 }; # return size of dir immediate contents
227 main() {
228 #size_large="100000000"; # 100 MB
229
230 #yell "DEBUG:arg1:$1"; # debug
231 #yell "DEBUG:arg2:$2"; # debug
232
233 # check dependencies
234 if ! check_depends; then die "FATAL:Missing dependencies."; fi;
235
236 # check args
237 if ! checkInt "$1"; then
238 die "FATAL:Not an int:$1";
239 else
240 size_l_mb="$1"
241 size_l_b="$(( size_l_mb * 1000000 ))";
242 fi;
243 if [[ ! -d "$2" ]]; then
244 die "FATAL:Not a dir:$2";
245 else
246 dir_in="$2";
247 fi;
248
249 # check each dir size contents
250 while IFS= read -r -d $'\0' pathdir; do
251 #yell "DEBUG:Checking:pathdir:$pathdir"; # debug
252 if [[ ! -d "$pathdir" ]]; then continue; fi;
253 dir_size="$(size_dir_contents "$pathdir";)"
254 #yell "DEBUG:dir_size:$dir_size"; # debug
255 if [[ $dir_size -gt "$size_l_b" ]]; then
256 printf "%s\n" "$pathdir"; # output
257 fi;
258 #sleep 1; # debug
259 done < <(find "$dir_in" -type d -print0);
260
261 return 0;
262 };
263
264 main "$@";