forked from oldratlee/useful-scripts
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfind-in-jars
executable file
·409 lines (356 loc) · 13.2 KB
/
find-in-jars
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
#!/bin/bash
# @Function
# Find files in the jar files under specified directory, search jar files recursively(include subdirectory).
#
# @Usage
# $ find-in-jars 'log4j\.properties'
# # search file log4j.properties/log4j.xml at zip root
# $ find-in-jars '^log4j\.(properties|xml)$'
# $ find-in-jars 'log4j\.properties$' -d /path/to/find/directory
# $ find-in-jars '\.properties$' -d /path/to/find/dir1 -d path/to/find/dir2
# $ find-in-jars 'Service\.class$' -e jar -e zip
# $ find-in-jars 'Mon[^$/]*Service\.class$' -s ' <-> '
#
# @online-doc https://github.com/oldratlee/useful-scripts/blob/dev-2.x/docs/java.md#-find-in-jars
# @author Jerry Lee (oldratlee at gmail dot com)
#
# NOTE about Bash Traps and Pitfalls:
#
# 1. DO NOT combine var declaration and assignment which value supplied by subshell in ONE line!
# for example: readonly var1=$(echo value1)
# local var2=$(echo value1)
#
# Because the combination make exit code of assignment to be always 0,
# aka. the exit code of command in subshell is discarded.
# tested on bash 3.2.57/4.2.46
#
# solution is separation of var declaration and assignment:
# var1=$(echo value1)
# readonly var1
# local var2
# var2=$(echo value1)
set -eEuo pipefail
# NOTE: DO NOT declare var PROG as readonly in ONE line!
PROG="$(basename "$0")"
readonly PROG
readonly PROG_VERSION='2.5.0-dev'
################################################################################
# util functions
################################################################################
# NOTE: $'foo' is the escape sequence syntax of bash
readonly ec=$'\033' # escape char
readonly eend=$'\033[0m' # escape end
readonly nl=$'\n' # new line
# How to delete line with echo?
# https://unix.stackexchange.com/questions/26576
#
# terminal escapes: http://ascii-table.com/ansi-escape-sequences.php
# In particular, to clear from the cursor position to the beginning of the line:
# echo -e "\033[1K"
# Or everything on the line, regardless of cursor position:
# echo -e "\033[2K"
readonly clear_line=$'\033[2K\r'
redEcho() {
# -t check: is a terminal device?
[ -t 1 ] && echo "${ec}[1;31m$*$eend" || echo "$*"
}
# Getting console width using a bash script
# https://unix.stackexchange.com/questions/299067
#
# NOTE: DO NOT declare columns var as readonly in ONE line!
[ -t 2 ] && columns=$(stty size | awk '{print $2}')
printResponsiveMessage() {
if ! $show_responsive || [ ! -t 2 ]; then
return
fi
local message="$*"
# http://www.linuxforums.org/forum/red-hat-fedora-linux/142825-how-truncate-string-bash-script.html
echo -n "$clear_line${message:0:columns}" >&2
}
clearResponsiveMessage() {
if ! $show_responsive || [ ! -t 2 ]; then
return
fi
echo -n "$clear_line" >&2
}
die() {
clearResponsiveMessage
redEcho "Error: $*" >&2
exit 1
}
usage() {
local -r exit_code="${1:-0}"
(($# > 0)) && shift
# shellcheck disable=SC2015
[ "$exit_code" != 0 ] && local -r out=/dev/stderr || local -r out=/dev/stdout
(($# > 0)) && redEcho "$*$nl" >$out
cat >$out <<EOF
Usage: ${PROG} [OPTION]... PATTERN
Find files in the jar files under specified directory,
search jar files recursively(include subdirectory).
The pattern default is *extended* regex.
Example:
${PROG} 'log4j\.properties'
# search file log4j.properties/log4j.xml at zip root
${PROG} '^log4j\.(properties|xml)$'
${PROG} 'log4j\.properties$' -d /path/to/find/directory
${PROG} '\.properties$' -d /path/to/find/dir1 -d path/to/find/dir2
${PROG} 'Service\.class$' -e jar -e zip
${PROG} 'Mon[^$/]*Service\.class$' -s ' <-> '
Find control:
-d, --dir the directory that find jar files.
default is current directory. this option can specify
multiply times to find in multiply directories.
-e, --extension set find file extension, default is jar. this option
can specify multiply times to find multiply extension.
-E, --extended-regexp PATTERN is an extended regular expression (*default*)
-F, --fixed-strings PATTERN is a set of newline-separated strings
-G, --basic-regexp PATTERN is a basic regular expression
-P, --perl-regexp PATTERN is a Perl regular expression
-i, --ignore-case ignore case distinctions
Output control:
-a, --absolute-path always print absolute path of jar file
-s, --separator specify the separator between jar file and zip entry.
default is \`!'.
-L, --files-not-contained-found
print only names of JAR FILEs NOT contained found
-l, --files-contained-found
print only names of JAR FILEs contained found
-R, --no-find-progress do not display responsive find progress
Miscellaneous:
-h, --help display this help and exit
-V, --version display version information and exit
EOF
exit "$exit_code"
}
progVersion() {
echo "$PROG $PROG_VERSION"
exit
}
################################################################################
# parse options
################################################################################
declare -a dirs=()
declare -a extensions=()
declare -a args=()
separator='!'
regex_mode=-E
use_absolute_path=false
show_responsive=true
only_print_file_name=false
while (($# > 0)); do
case "$1" in
-d | --dir)
dirs=(${dirs[@]:+"${dirs[@]}"} "$2")
shift 2
;;
-e | --extension)
extensions=(${extensions[@]:+"${extensions[@]}"} "$2")
shift 2
;;
-E | --extended-regexp)
regex_mode=-E
shift
;;
-F | --fixed-strings)
regex_mode=-F
shift
;;
-G | --basic-regexp)
regex_mode=-G
shift
;;
-P | --perl-regexp)
regex_mode=-P
shift
;;
-i | --ignore-case)
ignore_case_option=-i
shift
;;
-a | --absolute-path)
use_absolute_path=true
shift
;;
# support the legacy typo option name --seperator for compatibility
-s | --separator | --seperator)
separator="$2"
shift 2
;;
-L | --files-not-contained-found)
only_print_file_name=true
print_matched_files=false
shift
;;
-l | --files-contained-found)
only_print_file_name=true
print_matched_files=true
shift
;;
-R | --no-find-progress)
show_responsive=false
shift
;;
-h | --help)
usage
;;
-V | --version)
progVersion
;;
--)
shift
args=(${args[@]:+"${args[@]}"} "$@")
break
;;
-*)
usage 2 "${PROG}: unrecognized option '$1'"
;;
*)
args=(${args[@]:+"${args[@]}"} "$1")
shift
;;
esac
done
readonly separator regex_mode ignore_case_option use_absolute_path only_print_file_name print_matched_files show_responsive args
# shellcheck disable=SC2178
dirs=${dirs:-.}
# shellcheck disable=SC2178
readonly extensions=${extensions:-jar}
(("${#args[@]}" == 0)) && usage 1 "Missing file pattern!"
(("${#args[@]}" > 1)) && usage 1 "More than 1 file pattern: ${args[*]}"
readonly pattern="${args[0]}"
declare -a tmp_dirs=()
for d in "${dirs[@]}"; do
[ -e "$d" ] || die "file $d(specified by option -d) does not exist!"
[ -d "$d" ] || die "file $d(specified by option -d) exists but is not a directory!"
[ -r "$d" ] || die "directory $d(specified by option -d) exists but is not readable!"
# convert dirs to Absolute Path if has option -a, --absolute-path
$use_absolute_path && tmp_dirs=(${tmp_dirs[@]:+"${tmp_dirs[@]}"} "$(cd "$d" && pwd)")
done
# set dirs to Absolute Path
$use_absolute_path && dirs=("${tmp_dirs[@]}")
readonly dirs
# convert extensions to find -iname options
find_iname_options=()
for e in "${extensions[@]}"; do
(("${#find_iname_options[@]}" == 0)) &&
find_iname_options=(-iname "*.$e") ||
find_iname_options=("${find_iname_options[@]}" -o -iname "*.$e")
done
readonly find_iname_options
################################################################################
# Check the existence of command for listing zip entry!
################################################################################
__prepareCommandToListZipEntries() {
# `zipinfo -1`/`unzip -Z1` is ~25 times faster than `jar tf`, find zipinfo/unzip command first.
#
# How to list files in a zip without extra information in command line
# https://unix.stackexchange.com/a/128304/136953
if command -v zipinfo &>/dev/null; then
command_to_list_zip_entries=(zipinfo -1)
is_use_zip_cmd_to_list_zip_entries=true
elif command -v unzip &>/dev/null; then
command_to_list_zip_entries=(unzip -Z1)
is_use_zip_cmd_to_list_zip_entries=true
elif [ -n "$JAVA_HOME" ]; then
# search jar command under JAVA_HOME
if [ -f "$JAVA_HOME/bin/jar" ]; then
[ -x "$JAVA_HOME/bin/jar" ] || die "found \$JAVA_HOME/bin/jar($JAVA_HOME/bin/jar) is NOT executable!"
command_to_list_zip_entries=("$JAVA_HOME/bin/jar" tf)
elif [ -f "$JAVA_HOME/../bin/jar" ]; then
[ -x "$JAVA_HOME/../bin/jar" ] || die "found \$JAVA_HOME/../bin/jar($JAVA_HOME/../bin/jar) is NOT executable!"
command_to_list_zip_entries=("$JAVA_HOME/../bin/jar" tf)
fi
is_use_zip_cmd_to_list_zip_entries=false
elif command -v jar &>/dev/null; then
# search jar command under PATH
command_to_list_zip_entries=(jar tf)
is_use_zip_cmd_to_list_zip_entries=false
else
die "NOT found command to list zip entries: zipinfo, unzip or jar!"
fi
readonly command_to_list_zip_entries is_use_zip_cmd_to_list_zip_entries
}
__prepareCommandToListZipEntries
listZipEntries() {
local zip_file="$1" msg
if $is_use_zip_cmd_to_list_zip_entries; then
# How to check if zip file is empty in bash
# https://superuser.com/questions/438878
msg="$("${command_to_list_zip_entries[@]}" -t "$zip_file" 2>&1)" || {
# NOTE:
# if list emtpy zip file by zipinfo/unzip command,
# exit code is 1, and print 'Empty zipfile.'
if [ "$msg" != 'Empty zipfile.' ]; then
clearResponsiveMessage
redEcho "fail to list zip entries of $zip_file, ignored: $msg" >&2
fi
return 0
}
fi
"${command_to_list_zip_entries[@]}" "$zip_file" || {
clearResponsiveMessage
redEcho "fail to list zip entries of $zip_file, ignored!" >&2
return 0
}
}
################################################################################
# find logic
################################################################################
searchJarFiles() {
printResponsiveMessage "searching jars under dir ${dirs[*]} , ..."
local jar_files total_jar_count
jar_files="$(find "${dirs[@]}" "${find_iname_options[@]}" -type f)"
[ -n "$jar_files" ] || die "No ${extensions[*]} file found!"
total_jar_count="$(echo "$jar_files" | wc -l)"
# delete white space
# because the output of mac system command `wc -l` contains white space!
total_jar_count="${total_jar_count//[[:space:]]/}"
echo "$total_jar_count"
echo "$jar_files"
}
__outputResultOfJarFile() {
local jar_file="$1" file
if $only_print_file_name; then
local matched=false
# NOTE: Do NOT use -q flag with grep:
# With the -q flag the grep program will stop immediately when the first line of data matches.
# Normally you shouldn't use -q in a pipeline like this
# unless you are sure the program at the other end can handle SIGPIPE.
# more info see:
# - https://stackoverflow.com/questions/19120263/why-exit-code-141-with-grep-q
# - https://unix.stackexchange.com/questions/305547/broken-pipe-when-grepping-output-but-only-with-i-flag
# - http://www.pixelbeat.org/programming/sigpipe_handling.html
if grep $regex_mode ${ignore_case_option:-} -c -- "$pattern" &>/dev/null; then
matched=true
fi
if [ $print_matched_files != $matched ]; then
return
fi
clearResponsiveMessage
[ -t 1 ] && echo "${ec}[1;35m${jar_file}${eend}" || echo "${jar_file}"
else
{
# Prevent grep from exiting in case of no match
# https://unix.stackexchange.com/questions/330660
# shellcheck disable=SC2086
grep $regex_mode ${ignore_case_option:-} ${grep_color_option:-} -- "$pattern" || true
} | while read -r file; do
clearResponsiveMessage
[ -t 1 ] &&
echo "${ec}[1;35m${jar_file}${eend}${ec}[1;32m${separator}${eend}${file}" ||
echo "${jar_file}${separator}${file}"
done
fi
}
findInJarFiles() {
[ -t 1 ] && local -r grep_color_option='--color=always'
local counter=1 total_jar_count jar_file
read -r total_jar_count
while read -r jar_file; do
printResponsiveMessage "finding in jar($((counter++))/$total_jar_count): $jar_file"
listZipEntries "${jar_file}" | __outputResultOfJarFile "${jar_file}"
done
clearResponsiveMessage
}
searchJarFiles | findInJarFiles