From f5def403e6acd50691419523b27ccbc0006b8ae2 Mon Sep 17 00:00:00 2001 From: Jerry Lee Date: 2024年2月16日 20:17:15 +0800 Subject: [PATCH 1/3] =?UTF-8?q?refactor:=20unify=20error=20message=20forma?= =?UTF-8?q?t=20and=20refactor=20related=20functions=20=E2=84=B9=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - keep `usage` function simple - use `-s`/`-h` option for optional argument of `die` function - use bash builtin `type -P` instead of `which` command 🐚 --- bin/a2l | 4 +- bin/ap | 44 ++++++++----- bin/c | 42 +++++++++---- bin/coat | 18 +++--- bin/cp-into-docker-run | 44 ++++++++----- bin/find-in-jars | 57 ++++++++++------- bin/rp | 52 +++++++++------ bin/show-busy-java-threads | 101 ++++++++++++++++-------------- bin/taoc | 18 +++--- bin/tcp-connection-state-counter | 1 + bin/uq | 51 +++++++++------ bin/xpf | 2 +- bin/xpl | 40 ++++++++---- legacy-bin/cp-svn-url | 4 +- legacy-bin/svn-merge-stop-on-copy | 4 +- test-cases/self-installer.sh | 2 +- 16 files changed, 292 insertions(+), 192 deletions(-) diff --git a/bin/a2l b/bin/a2l index 1eda0b17..203513c9 100755 --- a/bin/a2l +++ b/bin/a2l @@ -72,7 +72,7 @@ readonly args ################################################################################ readonly -a ROTATE_COLORS=(33 35 36 31 32 37 34) -COUNT=0 +COLOR_INDEX=0 rotateColorPrint() { local content=$* # - if stdout is a terminal, turn on color output. @@ -82,7 +82,7 @@ rotateColorPrint() { if [[ ! -t 1 || $content =~ ^[[:space:]]*$ ]]; then printf '%s\n' "$content" else - local color=${ROTATE_COLORS[COUNT++ % ${#ROTATE_COLORS[@]}]} + local color=${ROTATE_COLORS[COLOR_INDEX++ % ${#ROTATE_COLORS[@]}]} printf '\e[1;%sm%s\e[0m\n' "$color" "$content" fi } diff --git a/bin/ap b/bin/ap index 921f8afd..6bc1c6ca 100755 --- a/bin/ap +++ b/bin/ap @@ -31,11 +31,30 @@ redPrint() { } die() { - redPrint "Error: $*">&2 - exit 1 -} - -# `realpath` command existed on Linux and macOS, return resolved physical path + local prompt_help=false exit_status=2 + while (($#> 0)); do + case "1ドル" in + -h) + prompt_help=true + shift + ;; + -s) + exit_status=2ドル + shift 2 + ;; + *) + break + ;; + esac + done + + (($#> 0)) && redPrint "$PROG: $*" + $prompt_help && echo "Try '$PROG --help' for more information." + + exit "$exit_status" +}>&2 + +# `realpath` command exists on Linux and macOS, return resolved physical path # - realpath command on macOS do NOT support option `-e`; # combined `[ -e $file ]` to check file existence first. # - How can I get the behavior of GNU's readlink -f on a Mac? @@ -45,14 +64,7 @@ realpath() { } usage() { - local -r exit_code=${1:-0} - (($#> 0)) && shift - local -r out=$(((exit_code != 0) + 1)) - - # NOTE: $'foo' is the escape sequence syntax of bash - (($#> 0)) && redPrint "$*"$'\n'>&"$out" - - cat>&"$out" < 0)); do break ;; -*) - usage 2 "$PROG: unrecognized option '1ドル'" + die -h "unrecognized option '1ドル'" ;; *) # if not option, treat all follow files as args @@ -113,8 +125,8 @@ has_error=false for f in "${files[@]}"; do realpath "$f" || { - redPrint "error: $f does not exists!">&2 has_error=true + redPrint "$PROG: $f: No such file or directory!">&2 } done diff --git a/bin/c b/bin/c index b36fd495..c0bbbd5a 100755 --- a/bin/c +++ b/bin/c @@ -18,25 +18,43 @@ readonly PROG_VERSION='2.x-dev' # util functions ################################################################################ -printErrorMsg() { +redPrint() { # if stdout is a terminal, turn on color output. # '-t' check: is a terminal? # check isatty in bash https://stackoverflow.com/questions/10022323 if [ -t 1 ]; then - printf '\e[1;31m%s\e[0m\n\n' "Error: $*" + printf '\e[1;31m%s\e[0m\n' "$*" else - printf '%s\n\n' "Error: $*" + printf '%s\n' "$*" fi } -usage() { - local -r exit_code=${1:-0} - (($#> 0)) && shift - local -r out=$(((exit_code != 0) + 1)) - - (($#> 0)) && printErrorMsg "$*">&"$out" +die() { + local prompt_help=false exit_status=2 + while (($#> 0)); do + case "1ドル" in + -h) + prompt_help=true + shift + ;; + -s) + exit_status=2ドル + shift 2 + ;; + *) + break + ;; + esac + done + + (($#> 0)) && redPrint "$PROG: $*" + $prompt_help && echo "Try '$PROG --help' for more information." + + exit "$exit_status" +}>&2 - cat>&"$out" < 0)); do break ;; -*) - usage 2 "unrecognized option '1ドル'" + die -h "unrecognized option '1ドル'" ;; *) # if not option, treat all follow arguments as command diff --git a/bin/coat b/bin/coat index 37d20757..b1280722 100755 --- a/bin/coat +++ b/bin/coat @@ -18,12 +18,6 @@ readonly PROG_VERSION='2.x-dev' # parse options ################################################################################ -progVersion() { - printf '%s version: %s\n' "$PROG" "$PROG_VERSION" - printf 'cat executable: %s\n' "$(command -v cat)" - exit -} - usage() { cat <= 0; --idx)); do @@ -58,7 +58,7 @@ unset args idx [ -t 1 ] || exec cat "$@" readonly -a ROTATE_COLORS=(33 35 36 31 32 37 34) -COUNT=0 +COLOR_INDEX=0 # CAUTION: print content WITHOUT new line rotateColorPrint() { local content=$* @@ -66,7 +66,7 @@ rotateColorPrint() { if [[ $content =~ ^[[:space:]]*$ ]]; then printf %s "$content" else - local color=${ROTATE_COLORS[COUNT++ % ${#ROTATE_COLORS[@]}]} + local color=${ROTATE_COLORS[COLOR_INDEX++ % ${#ROTATE_COLORS[@]}]} printf '\e[1;%sm%s\e[0m' "$color" "$content" fi } diff --git a/bin/cp-into-docker-run b/bin/cp-into-docker-run index cddc245b..7ad6b164 100755 --- a/bin/cp-into-docker-run +++ b/bin/cp-into-docker-run @@ -28,15 +28,34 @@ redPrint() { } die() { - redPrint "Error: $*">&2 - exit 1 -} + local prompt_help=false exit_staus=2 + while (($#> 0)); do + case "1ドル" in + -h) + prompt_help=true + shift + ;; + -s) + exit_staus=2ドル + shift 2 + ;; + *) + break + ;; + esac + done + + (($#> 0)) && redPrint "$PROG: $*" + $prompt_help && echo "Try '$PROG --help' for more information." + + exit "$exit_staus" +}>&2 isAbsolutePath() { [[ "1ドル" =~ ^/ ]] } -# `realpath` command existed on Linux and macOS, return resolved physical path +# `realpath` command exists on Linux and macOS, return resolved physical path # - realpath command on macOS do NOT support option `-e`; # combined `[ -e $file ]` to check file existence first. # - How can I get the behavior of GNU's readlink -f on a Mac? @@ -46,14 +65,7 @@ realpath() { } usage() { - local -r exit_code=${1:-0} - (($#> 0)) && shift - local -r out=$(((exit_code != 0) + 1)) - - # NOTE: $'foo' is the escape sequence syntax of bash - (($#> 0)) && redPrint "$*"$'\n'>&"$out" - - cat>&"$out" < 0)); do break ;; -*) - usage 2 "$PROG: unrecognized option '1ドル'" + die -h "unrecognized option '1ドル'" ;; *) # if not option, treat all follow arguments as command @@ -153,7 +165,7 @@ done readonly container_name docker_user docker_workdir docker_tmpdir docker_command_cp_path verbose args [ -n "$container_name" ] || - usage 1 "No destination docker container name, specified by option -c/--container!" + die -h "requires destination docker container name, specified by option -c/--container!" if [ -n "$docker_workdir" ]; then isAbsolutePath "$docker_workdir" || @@ -171,7 +183,7 @@ fi # check docker command existence ######################################## -command -v docker &>/dev/null || die 'docker command not found!' +type -P docker &>/dev/null || die 'docker command not found!' ######################################## # prepare vars for docker operation diff --git a/bin/find-in-jars b/bin/find-in-jars index 9a2ef20c..fd00d3fd 100755 --- a/bin/find-in-jars +++ b/bin/find-in-jars @@ -68,20 +68,32 @@ clearResponsiveMessage() { } die() { - clearResponsiveMessage - redPrint "Error: $*">&2 - exit 1 -} + local prompt_help=false exit_status=2 + while (($#> 0)); do + case "1ドル" in + -h) + prompt_help=true + shift + ;; + -s) + exit_status=2ドル + shift 2 + ;; + *) + break + ;; + esac + done -usage() { - local -r exit_code=${1:-0} - (($#> 0)) && shift - local -r out=$(((exit_code != 0) + 1)) + clearResponsiveMessage + (($#> 0)) && redPrint "$PROG: $*" + $prompt_help && echo "Try '$PROG --help' for more information." - # NOTE: $'foo' is the escape sequence syntax of bash - (($#> 0)) && redPrint "$*"$'\n'>&"$out" + exit "$exit_status" +}>&2 - cat>&"$out" < 0)); do break ;; -*) - usage 2 "Error: unrecognized option '1ドル'" + die -h "unrecognized option '1ドル'" ;; *) args=(${args[@]:+"${args[@]}"} "1ドル") @@ -227,13 +239,13 @@ 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[*]}" +((${#args[@]} == 0)) && die -h "requires file pattern!" +((${#args[@]}> 1)) && die -h "more than 1 file pattern: ${args[*]}" readonly pattern=${args[0]} tmp_dirs=() for d in "${dirs[@]}"; do - [ -e "$d" ] || die "file $d(specified by option -d) does not exist!" + [ -e "$d" ] || die "file $d(specified by option -d): No such file or directory!" [ -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!" @@ -263,10 +275,10 @@ __prepareCommandToListZipEntries() { # 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 + if type -P 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 + elif type -P 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 @@ -279,12 +291,12 @@ __prepareCommandToListZipEntries() { 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 + elif type -P 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!" + die "command to list zip entries NOT found : zipinfo, unzip or jar!" fi readonly command_to_list_zip_entries is_use_zip_cmd_to_list_zip_entries @@ -312,7 +324,6 @@ listZipEntries() { "${command_to_list_zip_entries[@]}" "$zip_file" || { clearResponsiveMessage redPrint "fail to list zip entries of $zip_file, ignored!">&2 - return } } @@ -326,7 +337,7 @@ searchJarFiles() { local jar_files total_jar_count jar_files=$(find "${dirs[@]}" "${find_iname_options[@]}" -type f) - [ -n "$jar_files" ] || die "No ${extensions[*]} file found!" + [ -n "$jar_files" ] || die "${extensions[*]} file NOT found!" total_jar_count=$(printf '%s\n' "$jar_files" | wc -l) # remove white space, because the `wc -l` output on mac contains white space! @@ -380,11 +391,9 @@ __outputResultOfJarFile() { 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" diff --git a/bin/rp b/bin/rp index f44ab089..d4d85210 100755 --- a/bin/rp +++ b/bin/rp @@ -31,9 +31,28 @@ redPrint() { } die() { - redPrint "Error: $*">&2 - exit 1 -} + local prompt_help=false exit_status=2 + while (($#> 0)); do + case "1ドル" in + -h) + prompt_help=true + shift + ;; + -s) + exit_status=2ドル + shift 2 + ;; + *) + break + ;; + esac + done + + (($#> 0)) && redPrint "$PROG: $*" + $prompt_help && echo "Try '$PROG --help' for more information." + + exit "$exit_status" +}>&2 portableRelPath() { local file=1ドル relTo=2ドル uname @@ -45,31 +64,24 @@ portableRelPath() { ;; Darwin*) local py_args=(-c 'import os, sys; print(os.path.relpath(sys.argv[1], sys.argv[2]))' "$file" "$relTo") - if command -v grealpath>/dev/null; then + if type -P grealpath>/dev/null; then grealpath "$f" --relative-to="$relTo" - elif command -v python3>/dev/null; then + elif type -P python3>/dev/null; then python3 "${py_args[@]}" - elif command -v python>/dev/null; then + elif type -P python>/dev/null; then python "${py_args[@]}" else die "fail to find command(grealpath/python3/python) to get relative path!" fi ;; *) - die "NOT support uname($uname)!" + die "uname($uname) NOT support!" ;; esac } usage() { - local -r exit_code=${1:-0} - (($#> 0)) && shift - local -r out=$(((exit_code != 0) + 1)) - - # NOTE: $'foo' is the escape sequence syntax of bash - (($#> 0)) && redPrint "$*"$'\n'>&"$out" - - cat>&"$out" < 0)); do break ;; -*) - usage 2 "$PROG: unrecognized option '1ドル'" + die -h "unrecognized option '1ドル'" ;; *) # if not option, treat all follow files as args @@ -120,7 +132,7 @@ while (($#> 0)); do esac done -((${#files[@]} == 0)) && die "NO argument!" +((${#files[@]} == 0)) && die -h "requires at least one argument!" if ((${#files[@]} == 1)); then relativeTo=. @@ -133,7 +145,7 @@ else fi [ -f "$relativeTo" ] && relativeTo=$(dirname -- "$relativeTo") -[ -e "$relativeTo" ] || die "relativeTo dir($relativeTo) does NOT exists!" +[ -e "$relativeTo" ] || die "relativeTo dir($relativeTo): No such file or directory!" readonly files relativeTo @@ -147,7 +159,7 @@ for f in "${files[@]}"; do if [ -e "$f" ]; then portableRelPath "$f" "$relativeTo" else - redPrint "error: $f does not exists!">&2 + redPrint "$PROG: $f: No such file or directory!">&2 has_error=true fi done diff --git a/bin/show-busy-java-threads b/bin/show-busy-java-threads index 73e78194..c141ad8d 100755 --- a/bin/show-busy-java-threads +++ b/bin/show-busy-java-threads @@ -85,9 +85,28 @@ blueOutput() { } die() { - redOutput "Error: $*">&2 - exit 1 -} + local prompt_help=false exit_status=2 + while (($#> 0)); do + case "1ドル" in + -h) + prompt_help=true + shift + ;; + -s) + exit_status=2ドル + shift 2 + ;; + *) + break + ;; + esac + done + + (($#> 0)) && colorPrint "1;31" "$PROG: $*" + $prompt_help && echo "Try '$PROG --help' for more information." + + exit "$exit_status" +}>&2 logAndRun() { printf '%s\n' "$*" @@ -133,13 +152,7 @@ printCallingCommandLine() { } usage() { - local -r exit_code=${1:-0} - (($#> 0)) && shift - local -r out=$(((exit_code != 0) + 1)) - - (($#> 0)) && colorPrint 31 "$*$NL">&"$out" - - cat>&"$out" </dev/null; then +elif type -P jstack &>/dev/null; then # 3. search jstack under PATH - jstack_path=$(command -v jstack) - [ -x "$jstack_path" ] || die "found $jstack_path from PATH is NOT executable!${NL}Use -s option set jstack path manually." + jstack_path=$(type -P jstack) + [ -x "$jstack_path" ] || die -h "found $jstack_path from PATH is NOT executable!${NL}Use -s option set jstack path manually." else - die "jstack NOT found by JAVA_HOME(${JAVA_HOME:-not set}) setting and PATH!${NL}Use -s option set jstack path manually." + die -h "jstack NOT found by JAVA_HOME(${JAVA_HOME:-not set}) setting and PATH!${NL}Use -s option set jstack path manually." fi readonly jstack_path @@ -470,16 +480,14 @@ __top_threadId_cpu() { # DO NOT combine var result_threads_top_info declaration and assignment in ONE line! local result_threads_top_info - result_threads_top_info=$( - printf '%s\n' "$top_out" | awk '{ - # from text line to empty line, increase block index - if (previousLine && !0ドル) blockIndex++ - # only print 4th text block(blockIndex == 3), aka. process info of second top update - if (blockIndex == 3 && 1ドル ~ /^[0-9]+$/) - print 1,ドル 9ドル # 1ドル is thread id field, 9ドル is %cpu field - previousLine = 0ドル - }' - ) + result_threads_top_info=$(printf '%s\n' "$top_out" | awk '{ + # from text line to empty line, increase block index + if (previousLine && !0ドル) blockIndex++ + # only print 4th text block(blockIndex == 3), aka. process info of second top update + if (blockIndex == 3 && 1ドル ~ /^[0-9]+$/) + print 1,ドル 9ドル # 1ドル is thread id field, 9ドル is %cpu field + previousLine = 0ドル + }') [ -n "$result_threads_top_info" ] || __die_when_no_java_process_found printf '%s\n' "$result_threads_top_info" | sort -k2,2nr @@ -501,10 +509,9 @@ __complete_pid_user_by_ps() { ((count <= 0 || idx < count)) || break # output field: pid, threadId, pcpu, user - output_fields=$(printf '%s\n' "$ps_out" | - awk -v "threadId=$threadId" -v "pcpu=$pcpu" '2ドル==threadId { - print 1,ドル threadId, pcpu, 3ドル; exit - }') + output_fields=$(printf '%s\n' "$ps_out" | awk -v "threadId=$threadId" -v "pcpu=$pcpu" '2ドル==threadId { + print 1,ドル threadId, pcpu, 3ドル; exit + }') if [ -n "$output_fields" ]; then ((idx++)) printf '%s\n' "$output_fields" @@ -550,18 +557,18 @@ printStackOfThreads() { if [ -n "$mix_native_frames" ]; then local sed_script="/--------------- $threadId ---------------/,/^---------------/ { - /--------------- $threadId ---------------/b # skip first separator line - /^---------------/d # delete second separator line - p - }" + /--------------- $threadId ---------------/b # skip first separator line + /^---------------/d # delete second separator line + p + }" elif [ -n "$force" ]; then local sed_script="/^Thread $threadId:/,/^$/ { - /^$/d; p # delete end separator line - }" + /^$/d; p # delete end separator line + }" else local sed_script="/ nid=($threadId0x|$threadId) /,/^$/ { - /^$/d; p # delete end separator line - }" + /^$/d; p # delete end separator line + }" fi sed "$sed_script" -n -r "$jstackFile" | tee ${append_file:+-a "$append_file"} ${store_dir:+-a "$store_file_prefix$PROG"} done diff --git a/bin/taoc b/bin/taoc index e51de9ce..5e5fcfc0 100755 --- a/bin/taoc +++ b/bin/taoc @@ -18,12 +18,6 @@ readonly PROG_VERSION='2.x-dev' # parse options ################################################################################ -progVersion() { - printf '%s version: %s\n' "$PROG" "$PROG_VERSION" - printf 'tac executable: %s\n' "$(command -v tac)" - exit -} - usage() { cat <= 0; --idx)); do @@ -58,7 +58,7 @@ unset args idx [ -t 1 ] || exec tac "$@" readonly -a ROTATE_COLORS=(33 35 36 31 32 37 34) -COUNT=0 +COLOR_INDEX=0 # CAUTION: print content WITHOUT new line rotateColorPrint() { local content=$* @@ -66,7 +66,7 @@ rotateColorPrint() { if [[ $content =~ ^[[:space:]]*$ ]]; then printf %s "$content" else - local color=${ROTATE_COLORS[COUNT++ % ${#ROTATE_COLORS[@]}]} + local color=${ROTATE_COLORS[COLOR_INDEX++ % ${#ROTATE_COLORS[@]}]} printf '\e[1;%sm%s\e[0m' "$color" "$content" fi } diff --git a/bin/tcp-connection-state-counter b/bin/tcp-connection-state-counter index 104ea6de..dc7dae87 100755 --- a/bin/tcp-connection-state-counter +++ b/bin/tcp-connection-state-counter @@ -29,6 +29,7 @@ Options: -h, --help display this help and exit -V, --version display version information and exit EOF + exit } diff --git a/bin/uq b/bin/uq index 840ea1d3..535f9a85 100755 --- a/bin/uq +++ b/bin/uq @@ -42,9 +42,28 @@ yellowPrint() { } die() { - redPrint "Error: $*">&2 - exit 1 -} + local prompt_help=false exit_status=2 + while (($#> 0)); do + case "1ドル" in + -h) + prompt_help=true + shift + ;; + -s) + exit_status=2ドル + shift 2 + ;; + *) + break + ;; + esac + done + + (($#> 0)) && redPrint "$PROG: $*" + $prompt_help && echo "Try '$PROG --help' for more information." + + exit "$exit_status" +}>&2 convertHumanReadableSizeToSize() { local human_readable_size=1ドル @@ -68,13 +87,7 @@ convertHumanReadableSizeToSize() { } usage() { - local -r exit_code=${1:-0} - (($#> 0)) && shift - local -r out=$(((exit_code != 0) + 1)) - - (($#> 0)) && redPrint "$*$NL">&"$out" - - cat>&"$out" < 0)); do uq_opt_repeated_method=${1#--all-repeated=} [[ $uq_opt_repeated_method = 'none' || $uq_opt_repeated_method = 'prepend' || $uq_opt_repeated_method = 'separate' ]] || - usage 1 "$PROG: invalid argument ‘$uq_opt_repeated_method’ for ‘--all-repeated’${NL}Valid arguments are:$NL - ‘none’$NL - ‘prepend’$NL - ‘separate’" + die -h "invalid argument ‘$uq_opt_repeated_method’ for ‘--all-repeated’${NL}Valid arguments are:$NL - ‘none’$NL - ‘prepend’$NL - ‘separate’" shift ;; @@ -184,7 +197,7 @@ while (($#> 0)); do shift ;; -*) - usage 2 "$PROG: unrecognized option '1ドル'" + die -h "unrecognized option '1ドル'" ;; *) argv=(${argv[@]:+"${argv[@]}"} "1ドル") @@ -194,17 +207,17 @@ while (($#> 0)); do done [[ $uq_opt_only_repeated = 1 && $uq_opt_only_unique = 1 ]] && - usage 2 "printing duplicated lines(-d, --repeated) and unique lines(-u, --unique) is meaningless" + die -h "printing duplicated lines(-d, --repeated) and unique lines(-u, --unique) is meaningless" [[ $uq_opt_all_repeated = 1 && $uq_opt_only_unique = 1 ]] && - usage 2 "printing all duplicate lines(-D, --all-repeated) and unique lines(-u, --unique) is meaningless" + die -h "printing all duplicate lines(-D, --all-repeated) and unique lines(-u, --unique) is meaningless" [[ $uq_opt_all_repeated = 1 && $uq_opt_repeated_method = none && ($uq_opt_count = 0 && $uq_opt_only_repeated = 0) ]] && - yellowPrint "[$PROG] WARN: -D/--all-repeated=none option without -c/-d option, just cat input simply!">&2 + yellowPrint "WARN: -D/--all-repeated=none option without -c/-d option, just cat input simply!">&2 # DO NOT declare and assign var uq_max_input_size(as readonly) in ONE line! # more info see https://github.com/koalaman/shellcheck/wiki/SC2155 uq_max_input_size=$(convertHumanReadableSizeToSize "$uq_max_input_human_readable_size") || - usage 2 "[$PROG] ERROR: illegal value of option -XM/--max-input: $uq_max_input_human_readable_size" + die -h "illegal value of option -XM/--max-input: $uq_max_input_human_readable_size" readonly argc=${#argv[@]} argv uq_max_input_size @@ -228,7 +241,7 @@ for f in ${input_files[@]:+"${input_files[@]}"}; do # - is stdin, ok [ "$f" = - ] && continue - [ -e "$f" ] || die "input file $f does not exist!" + [ -e "$f" ] || die "input file $f: No such file or directory!" [ ! -d "$f" ] || die "input file $f exists, but is a directory!" [ -f "$f" ] || die "input file $f exists, but is not a file!" [ -r "$f" ] || die "input file $f exists, but is not readable!" @@ -285,7 +298,7 @@ BEGIN { { total_input_size += length + 1 if (total_input_size> uq_max_input_size) { - printf "[%s] ERROR: input size exceed max input size %s!\nuse option -XM/--max-input specify a REASONABLE larger value.\n", + printf "%s: input size exceed max input size %s!\nuse option -XM/--max-input specify a REASONABLE larger value.\n", uq_PROG, uq_max_input_human_readable_size> "/dev/stderr" exit(1) } diff --git a/bin/xpf b/bin/xpf index b407f0fb..34af6d91 100755 --- a/bin/xpf +++ b/bin/xpf @@ -14,7 +14,7 @@ set -eEuo pipefail # util functions ################################################################################ -# `realpath` command existed on Linux and macOS, return resolved physical path +# `realpath` command exists on Linux and macOS, return resolved physical path # - realpath command on macOS do NOT support option `-e`; # combined `[ -e $file ]` to check file existence first. # - How can I get the behavior of GNU's readlink -f on a Mac? diff --git a/bin/xpl b/bin/xpl index 2bac215c..392415aa 100755 --- a/bin/xpl +++ b/bin/xpl @@ -27,15 +27,32 @@ redPrint() { fi } -usage() { - local -r exit_code=${1:-0} - (($#> 0)) && shift - local -r out=$(((exit_code != 0) + 1)) - - # NOTE: $'foo' is the escape sequence syntax of bash - (($#> 0)) && redPrint "$*"$'\n'>&"$out" +die() { + local prompt_help=false exit_status=2 + while (($#> 0)); do + case "1ドル" in + -h) + prompt_help=true + shift + ;; + -s) + exit_status=2ドル + shift 2 + ;; + *) + break + ;; + esac + done + + (($#> 0)) && redPrint "$PROG: $*" + $prompt_help && echo "Try '$PROG --help' for more information." + + exit "$exit_status" +}>&2 - cat>&"$out" < 0)); do break ;; -*) - usage 2 "$PROG: unrecognized option '1ドル'" + die -h "unrecognized option '1ドル'" ;; *) files=(${files[@]:+"${files[@]}"} "1ドル") @@ -144,7 +160,7 @@ has_error=false for file in "${files[@]}"; do [ -e "$file" ] || { has_error=true - redPrint "$file not existed!">&2 + redPrint "$PROG: $file: No such file or directory!">&2 continue } diff --git a/legacy-bin/cp-svn-url b/legacy-bin/cp-svn-url index 97437e17..d5d15095 100755 --- a/legacy-bin/cp-svn-url +++ b/legacy-bin/cp-svn-url @@ -30,8 +30,8 @@ Options: -h, --help display this help and exit -V, --version display version information and exit EOF - # shellcheck disable=SC2086 - exit 1ドル + + exit "1ドル" } progVersion() { diff --git a/legacy-bin/svn-merge-stop-on-copy b/legacy-bin/svn-merge-stop-on-copy index 635b2abc..249fa7d2 100755 --- a/legacy-bin/svn-merge-stop-on-copy +++ b/legacy-bin/svn-merge-stop-on-copy @@ -33,8 +33,8 @@ Example: # because http://www.foo.com/project1/branches/feature2 is remote url, # will check out target branch to tmp directory, and prompt confirm for committing to target branch. EOF - # shellcheck disable=SC2086 - exit 1ドル + + exit "1ドル" } (($#> 2)) && { diff --git a/test-cases/self-installer.sh b/test-cases/self-installer.sh index ee0f8f57..801e7a79 100644 --- a/test-cases/self-installer.sh +++ b/test-cases/self-installer.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -if command -v svn &>/dev/null; then +if type -P svn &>/dev/null; then [ ! -d "/tmp/useful-scripts-$USER" ] && svn checkout https://github.com/oldratlee/useful-scripts/branches/release-2.x "/tmp/useful-scripts-$USER" fi From 934e57e12e00328812191d100dd8de5f5c05ef07 Mon Sep 17 00:00:00 2001 From: Jerry Lee Date: 2024年3月16日 13:13:18 +0800 Subject: [PATCH 2/3] refactor(`find-in-jars`): add `IFS=` for `read`, more robust --- bin/find-in-jars | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/find-in-jars b/bin/find-in-jars index fd00d3fd..eda51301 100755 --- a/bin/find-in-jars +++ b/bin/find-in-jars @@ -378,7 +378,7 @@ __outputResultOfJarFile() { # Prevent grep from exiting in case of no match # https://unix.stackexchange.com/questions/330660 grep "${grep_opt_args[@]}" || true - } | while read -r file; do + } | while IFS= read -r file; do clearResponsiveMessage if [ -t 1 ]; then printf "$JAR_COLOR%s$SEP_COLOR%s$COLOR_RESET%s\n" "$jar_file" "$separator" "$file" @@ -394,7 +394,7 @@ findInJarFiles() { local counter=1 total_jar_count jar_file read -r total_jar_count - while read -r jar_file; do + while IFS= read -r jar_file; do printResponsiveMessage "finding in jar($((counter++))/$total_jar_count): $jar_file" listZipEntries "$jar_file" | __outputResultOfJarFile "$jar_file" done From e1cdec5bfad66be32ca0d960e3fa3c87b11dd913 Mon Sep 17 00:00:00 2001 From: Jerry Lee Date: 2024年1月25日 01:02:27 +0800 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20add=20new=20script=20`uxt`=20?= =?UTF-8?q?=E2=8C=9A=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bin/uxt | 244 +++++++++++++++++++++++++++++++++++++++++ docs/shell.md | 57 ++++++++++ test-cases/uxt_test.sh | 59 ++++++++++ 3 files changed, 360 insertions(+) create mode 100755 bin/uxt create mode 100755 test-cases/uxt_test.sh diff --git a/bin/uxt b/bin/uxt new file mode 100755 index 00000000..167472e2 --- /dev/null +++ b/bin/uxt @@ -0,0 +1,244 @@ +#!/usr/bin/env bash +# @Function +# Convert unix time to human readable date string. +# Note: The range of the 10-digit unix time in second include recent date: +# 9999999999: 2286年11月20日 17:46:39 +0000 +# 1000000000: 2001年09月09日 01:46:40 +0000 +# 0: 1970年01月01日 00:00:00 +0000 +# -1000000000: 1938年04月24日 22:13:20 +0000 +# -9999999999: 1653年02月10日 06:13:21 +0000 +# +# @Usage +# # default treat first 10 digits as second(include recent date) +# $ uxt 1234567890 # unix time of second +# 2009年02月14日 07:31:30 +0800 +# $ uxt 1234567890333 # unix time of milliseconds(10 + 3 digits) +# 2009年02月14日 07:31:30.333 +0800 +# $ uxt 12345678903 # unix time of 10 + 1 digits +# 2009年02月14日 07:31:30.3 +0800 +# # support multiply arguments +# $ uxt 0 1234567890 12345678903 +# +# @online-doc https://github.com/oldratlee/useful-scripts/blob/dev-2.x/docs/shell.md#-unix-time +# @author Jerry Lee (oldratlee at gmail dot com) +set -eEuo pipefail + +readonly PROG=${0##*/} +readonly PROG_VERSION='2.x-dev' + +################################################################################ +# util functions +################################################################################ + +red_print() { + # if stdout is a terminal, turn on color output. + # '-t' check: is a terminal? + # check isatty in bash https://stackoverflow.com/questions/10022323 + if [ -t 1 ]; then + printf "\e[1;31m%s\e[0m\n" "$*" + else + printf '%s\n' "$*" + fi +} + +is_integer() { + [[ "1ドル" =~ ^-?[[:digit:]]+$ ]] +} + +die() { + local prompt_help=false exit_status=2 + while (($#> 0)); do + case "1ドル" in + -h) + prompt_help=true + shift + ;; + -s) + exit_status=2ドル + shift 2 + ;; + *) + break + ;; + esac + done + + (($#> 0)) && red_print "$PROG: $*" + $prompt_help && echo "Try '$PROG --help' for more information." + + exit "$exit_status" +}>&2 + +usage() { + cat < 0)); do + case "1ドル" in + -u | --unit) + unit=2ドル + shift 2 + ;; + -Z | --no-time-zone) + no_tz=true + shift + ;; + -D | --no-second-decimal) + no_second_decimal=true + shift + ;; + -t | --trim-decimal-tailing-0) + trim_decimal_tailing_0=true + shift + ;; + -h | --help) + usage + ;; + -V | --version) + progVersion + ;; + -[[:digit:]]*) + # negative number start with '-', is not option + args=(${args[@]:+"${args[@]}"} "1ドル") + shift + ;; + --) + shift + args=(${args[@]:+"${args[@]}"} "$@") + break + ;; + -*) + die -h "unrecognized option '1ドル'" + ;; + *) + args=(${args[@]:+"${args[@]}"} "1ドル") + shift + ;; + esac +done + +[[ -n $unit ]] && if [[ $unit =~ ^(s|second)$ ]]; then + unit=s +elif [[ $unit =~ ^(ms|millisecond)$ ]]; then + unit=ms +else + die -h "illegal time unit '$unit'! support values: 'second'/'s', 'millisecond'/'ms'" +fi + +readonly args unit trim_decimal_tailing_0 no_tz + +((${#args[@]}> 0)) || die -h "requires at least one argument!" +for a in "${args[@]}"; do + is_integer "$a" || die "argument $a is not integer!" + [[ ! $a =~ ^-?0+[1-9] ]] || die "argument $a contains beginning 0!" +done + +################################################################################ +# biz logic +################################################################################ + +print_date() { + local -r input=1ドル + # split input integer to sign and number part + local -r sign_part=${input%%[!-]*} # remove digits from tail + local -r number_part=${input#-} # remove sign from head + local -r np_len=${#number_part} # length of number part + + local second_part=0 decimal_part= + # case 1: is unix time in second? + if [[ $unit = s ]]; then + second_part=$number_part + # case 2: is unix time in millisecond? + elif [[ $unit = ms ]]; then + if ((np_len> 3)); then + second_part=${number_part:0:np_len-3} + decimal_part=${number_part:np_len-3:3} + else + printf -v decimal_part '%03d' "$number_part" + fi + # case 3: auto detect by length + else + # <= 10 digits, treat as second + if ((np_len <= 10)); then + second_part=$number_part + # for long integer(> 10 digits), treat first 10 digits as second, + # and the rest as decimal/nano second(almost 9 digits) + elif ((np_len <= 19)); then + second_part=${number_part:0:10} + decimal_part=${number_part:10:9} + else + die "argument $input contains $np_len digits(>19), too many to treat as a recent date(first 10-digits as seconds, rest at most 9 digits as decimal)" + fi + fi + + # trim tailing zeros of decimal? + $trim_decimal_tailing_0 && while true; do + local old_len=${#decimal_part} + decimal_part=${decimal_part%0} + ((${#decimal_part} < old_len)) || break + done + + local -r seconds_value=$sign_part$second_part second_part decimal_part + # defensive check. 9999999999999999(16 '9') seconds is so big, 300M years later(316,889,355-01-25 17:46:39 +0000) + ((${#second_part} <= 16)) || + die "argument $input(seconds: $seconds_value${decimal_part:+, decimal: .$decimal_part}) is too big, seconds are more than 16 digits." + + local date_input=$seconds_value${decimal_part:+.$decimal_part} + local format_n= + $no_second_decimal || format_n=${decimal_part:+.%${#decimal_part}N} + local format_tz= + $no_tz || format_tz=' %z' + date -d "@$date_input" +"%Y-%m-%d %H:%M:%S$format_n$format_tz" +} + +for a in "${args[@]}"; do + print_date "$a" +done diff --git a/docs/shell.md b/docs/shell.md index 4ff27aac..fed8d55b 100644 --- a/docs/shell.md +++ b/docs/shell.md @@ -363,6 +363,63 @@ $ rp /home /etc/../etc /home/admin ../../etc ``` +🍺 [uxt](../bin/uxt) +---------------------- + +输出`Unix`时间戳对应的时间,自动识别秒/毫秒格式。 + +### 用法/示例 + +```bash +$ uxt 1234567890 # 秒时间戳(10位以内数字) +2009-02-14 07:31:30 +0800 +$ uxt 1234567890333 # 毫秒时间戳(10位秒 + 3位毫秒) +2009-02-14 07:31:30.333 +0800 +$ uxt 12345678903 # 11位(10位秒 + 剩余1位作为毫秒) +2009-02-14 07:31:30.3 +0800 +# 支持多个参数 +$ uxt 0 1234567890 12345678903 + +# 如果需要转换秒超过10位的时间戳,显式指定单位 +$ uxt -u s 12345678900 +2361-03-22 03:15:00 +0800 +$ uxt -u ms 12345678900123 +2361-03-22 03:15:00.123 +0800 + +$ uxt -h +Usage: uxt [OPTION] unix-time [unix-time...] + +Convert unix time to human readable date string. +Note: The range of the 10-digit unix time in second include recent date: + 9999999999: 2286-11-20 17:46:39 +0000 + 1000000000: 2001-09-09 01:46:40 +0000 + 0: 1970-01-01 00:00:00 +0000 + -1000000000: 1938-04-24 22:13:20 +0000 + -9999999999: 1653-02-10 06:13:21 +0000 + +Example: + # default treat first 10 digits as second(include recent date) + $ uxt 1234567890 # unix time of second + 2009-02-14 07:31:30 +0800 + $ uxt 1234567890333 # unix time of milliseconds(10 + 3 digits) + 2009-02-14 07:31:30.333 +0800 + $ uxt 12345678903 # unix time of 10 + 1 digits + 2009-02-14 07:31:30.3 +0800 + # support multiply arguments + $ uxt 0 1234567890 12345678903 + +Options: + -u, --time-unit set the time unit of given epochs + -Z, --no-time-zone do not print time zone + -D, --no-second-decimal + do not print second decimal + -t, --trim-decimal-tailing-0 + trim the tailing zeros of second decimal + -h, --help display this help and exit + -V, --version display version information and exit +``` + + 🍺 [cp-into-docker-run](../bin/cp-into-docker-run) ---------------------- diff --git a/test-cases/uxt_test.sh b/test-cases/uxt_test.sh new file mode 100755 index 00000000..6b0c496a --- /dev/null +++ b/test-cases/uxt_test.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +set -eEuo pipefail + +READLINK_CMD=readlink +if command -v greadlink &>/dev/null; then + READLINK_CMD=greadlink +fi + +BASE=$(dirname -- "$($READLINK_CMD -f -- "${BASH_SOURCE[0]}")") +cd "$BASE" + +################################################# +# commons and test data +################################################# + +readonly uxt="../bin/uxt" + +################################################# +# test cases +################################################# + +test_uxt_auto_detect() { + assertEquals "2024-01-30 00:00:00 +0000" "$(TZ=0 "$uxt" 1706572800)" + assertEquals "1900-01-30 00:00:00 +0000" "$(TZ=0 "$uxt" -- -2206483200)" + + assertEquals "1970-01-01 00:00:00 +0000" "$(TZ=0 "$uxt" 0)" + assertEquals "1970-01-01 00:01:40 +0000" "$(TZ=0 "$uxt" -- 100)" + assertEquals "1969-12-31 23:58:20 +0000" "$(TZ=0 "$uxt" -- -100)" + + # shellcheck disable=SC2016 + assertFalse 'should fail, 20 more than 19 digits' '"$uxt" 12345678901234567890' +} + +test_uxt_unit_second() { + assertEquals "2024-01-30 00:00:00 +0000" "$(TZ=0 "$uxt" -u s 1706572800)" + assertEquals "1900-01-30 00:00:00 +0000" "$(TZ=0 "$uxt" -u s -- -2206483200)" + + assertEquals "1970-01-01 00:00:00 +0000" "$(TZ=0 "$uxt" -u s 0)" + assertEquals "1970-01-01 00:01:40 +0000" "$(TZ=0 "$uxt" -u s -- 100)" + assertEquals "1969-12-31 23:58:20 +0000" "$(TZ=0 "$uxt" -u s -- -100)" + + # shellcheck disable=SC2016 + assertFalse 'should fail, 20 more than 19 digits' '"$uxt" -u s 12345678901234567890' +} + +test_uxt_unit_ms() { + assertEquals "2024-01-30 00:00:00.000 +0000" "$(TZ=0 "$uxt" -u ms 1706572800000)" + assertEquals "1900-01-30 00:00:00.000 +0000" "$(TZ=0 "$uxt" -u ms -- -2206483200000)" + + assertEquals "1970-01-01 00:00:00.000 +0000" "$(TZ=0 "$uxt" -u ms 0)" + assertEquals "1970-01-01 00:01:40.000 +0000" "$(TZ=0 "$uxt" -u ms -- 100000)" + assertEquals "1969-12-31 23:58:20.000 +0000" "$(TZ=0 "$uxt" -u ms -- -100000)" +} + +################################################# +# Load and run shUnit2. +################################################# + +source "$BASE/shunit2-lib/shunit2"

AltStyle によって変換されたページ (->オリジナル) /