From 59044e2d2ad4b3e4653ad95810eec5a9ff998e29 Mon Sep 17 00:00:00 2001 From: Jerry Lee Date: Mon, 28 Aug 2023 12:35:45 +0800 Subject: [PATCH 01/10] =?UTF-8?q?refactor/robust(`c`):=20use=20`printf`=20?= =?UTF-8?q?=F0=9F=92=AA=20instead=20of=20`echo`;=20use=20`if-else`=20inste?= =?UTF-8?q?ad=20of=20`&&-||`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NOTE: - the `echo` option(e.g. -e -n) may effect correctness, `printf` is more robust 💪 - about `&&-||` see shell check: https://www.shellcheck.net/wiki/SC2015 --- bin/c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/bin/c b/bin/c index 7c023d63..9f65eee4 100755 --- a/bin/c +++ b/bin/c @@ -35,12 +35,14 @@ readonly PROG_VERSION='2.5.0-dev' # util functions ################################################################################ -readonly ec=$'\033' # escape char -readonly eend=$'\033[0m' # escape end -readonly nl=$'\n' # new line - -redEcho() { - [ -t 1 ] && echo "${ec}[1;31m$*$eend" || echo "$*" +printErrorMsg() { + # check isatty in bash https://stackoverflow.com/questions/10022323 + # if stdout is console, print with red color. + if [ -t 1 ]; then + printf "\033[1;31m%s\033[0m\n\n" "Error: $*" + else + printf '%s\n\n' "Error: $*" + fi } usage() { @@ -49,7 +51,7 @@ usage() { # shellcheck disable=SC2015 [ "$exit_code" != 0 ] && local -r out=/dev/stderr || local -r out=/dev/stdout - (($# > 0)) && redEcho "$*$nl" >$out + (($# > 0)) && printErrorMsg "$*" >$out cat >$out < Date: Mon, 28 Aug 2023 12:39:53 +0800 Subject: [PATCH 02/10] =?UTF-8?q?refactor/robust(`coat/a2l`):=20use=20`pri?= =?UTF-8?q?ntf`=20=F0=9F=92=AA=20instead=20of=20`echo`;=20the=20`echo`=20o?= =?UTF-8?q?ption(e.g.=20-e=20-n)=20may=20effect=20correctness=20?= =?UTF-8?q?=F0=9F=90=9E=20,=20`printf`=20is=20more=20robust=20=F0=9F=92=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bin/a2l | 34 +++++++++++++++------------------- bin/coat | 16 ++++++---------- 2 files changed, 21 insertions(+), 29 deletions(-) diff --git a/bin/a2l b/bin/a2l index f940fa51..6ab30820 100755 --- a/bin/a2l +++ b/bin/a2l @@ -1,6 +1,6 @@ #!/bin/bash # @Function -# echo each arguments on one line colorfully. +# print each arguments on one line colorfully. # # @Usage # $ ./a2l arg1 arg2 @@ -19,26 +19,22 @@ 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 - -colorEcho() { +colorPrint() { local color="$1" shift # check isatty in bash https://stackoverflow.com/questions/10022323 # if stdout is console, turn on color output. - [ -t 1 ] && echo "${ec}[1;${color}m$*${eend}" || echo "$*" -} - -redEcho() { - colorEcho 31 "$@" + if [ -t 1 ]; then + printf "\033[1;${color}m%s\033[0m\n" "$*" + else + printf '%s\n' "$*" + fi } usage() { cat < Date: Mon, 28 Aug 2023 12:43:33 +0800 Subject: [PATCH 03/10] =?UTF-8?q?refactor/robust(`echo-args`):=20use=20`pr?= =?UTF-8?q?intf`=20=F0=9F=92=AA=20instead=20of=20`echo`;=20align=20the=20i?= =?UTF-8?q?ndex=20number?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bin/echo-args | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/bin/echo-args b/bin/echo-args index 82b76859..a2dd472a 100755 --- a/bin/echo-args +++ b/bin/echo-args @@ -6,20 +6,37 @@ # @author Jerry Lee (oldratlee at gmail dot com) set -eEuo pipefail -readonly ec=$'\033' # escape char -readonly eend=$'\033[0m' # escape end +digitCount() { + # argument 1(num) is always a non-negative integer in this script usage, + # so NO argument validation logic. + local num="$1" count=0 + while ((num != 0)); do + ((++count)) + ((num = num / 10)) + done + echo "$count" +} + +digit_count=$(digitCount $#) +readonly arg_count=$# digit_count + +readonly red='\033[1;31m' +readonly blue='\033[1;36m' +readonly normal='\033[0m' -echoArg() { - local index="$1" count="$2" value="$3" +printArg() { + local idx="$1" value="$2" # if stdout is console, turn on color output. - [ -t 1 ] && - echo "${index}/${count}: ${ec}[1;31m[$eend${ec}[1;36;40m$value$eend${ec}[1;31m]$eend" || - echo "${index}/${count}: [${value}]" + if [ -t 1 ]; then + printf "%${digit_count}s/%s: ${red}[${blue}%s${red}]${normal}\n" "$idx" "$arg_count" "$value" + else + printf "%${digit_count}s/%s: [%s]\n" "$idx" "$arg_count" "$value" + fi } -echoArg 0 $# "$0" +printArg 0 "$0" idx=1 for a; do - echoArg $((idx++)) $# "$a" + printArg $((idx++)) "$a" done From e9f69f7f0a008ec66139fc1c41e0b56afa1aa48c Mon Sep 17 00:00:00 2001 From: Jerry Lee Date: Mon, 28 Aug 2023 13:25:05 +0800 Subject: [PATCH 04/10] =?UTF-8?q?refactor/robust(`find-in-jars`):=20use=20?= =?UTF-8?q?`printf`=20=F0=9F=92=AA=20instead=20of=20`echo`;=20use=20`if-el?= =?UTF-8?q?se`=20instead=20of=20`&&-||`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NOTE: - the `echo` option(e.g. -e -n) may effect correctness, `printf` is more robust 💪 - about `&&-||` see shell check: https://www.shellcheck.net/wiki/SC2015 --- bin/find-in-jars | 63 ++++++++++++++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/bin/find-in-jars b/bin/find-in-jars index efcd9e24..5f393589 100755 --- a/bin/find-in-jars +++ b/bin/find-in-jars @@ -40,10 +40,9 @@ 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 +readonly red='\033[1;31m' normal='\033[0m' +readonly jar_color='\033[1;35m' sep_color='\033[1;32m' + # How to delete line with echo? # https://unix.stackexchange.com/questions/26576 # @@ -52,11 +51,15 @@ readonly nl=$'\n' # new line # echo -e "\033[1K" # Or everything on the line, regardless of cursor position: # echo -e "\033[2K" -readonly clear_line=$'\033[2K\r' +readonly clear_line='\033[2K\r' -redEcho() { +redPrint() { # -t check: is a terminal device? - [ -t 1 ] && echo "${ec}[1;31m$*$eend" || echo "$*" + if [ -t 1 ]; then + printf "${red}%s${normal}\n" "$*" + else + printf '%s\n' "$*" + fi } # Getting console width using a bash script @@ -72,7 +75,7 @@ printResponsiveMessage() { 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 + printf "${clear_line}%s" "${message:0:columns}" >&2 } clearResponsiveMessage() { @@ -80,12 +83,12 @@ clearResponsiveMessage() { return fi - echo -n "$clear_line" >&2 + printf "%b" "$clear_line" >&2 } die() { clearResponsiveMessage - redEcho "Error: $*" >&2 + redPrint "Error: $*" >&2 exit 1 } @@ -95,7 +98,9 @@ usage() { # shellcheck disable=SC2015 [ "$exit_code" != 0 ] && local -r out=/dev/stderr || local -r out=/dev/stdout - (($# > 0)) && redEcho "$*$nl" >$out + # NOTE: $'foo' is the escape sequence syntax of bash + local -r nl=$'\n' # new line + (($# > 0)) && redPrint "$*$nl" >$out cat >$out < 0)); do break ;; -*) - usage 2 "${PROG}: unrecognized option '$1'" + usage 2 "Error: unrecognized option '$1'" ;; *) args=(${args[@]:+"${args[@]}"} "$1") @@ -319,7 +324,7 @@ listZipEntries() { # 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 + redPrint "fail to list zip entries of $zip_file, ignored: $msg" >&2 fi return 0 } @@ -327,7 +332,7 @@ listZipEntries() { "${command_to_list_zip_entries[@]}" "$zip_file" || { clearResponsiveMessage - redEcho "fail to list zip entries of $zip_file, ignored!" >&2 + redPrint "fail to list zip entries of $zip_file, ignored!" >&2 return 0 } } @@ -344,17 +349,18 @@ searchJarFiles() { 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="$(printf '%s\n' "$jar_files" | wc -l)" + # remove white space, because the `wc -l` output on mac contains white space! total_jar_count="${total_jar_count//[[:space:]]/}" echo "$total_jar_count" - echo "$jar_files" + printf '%s\n' "$jar_files" } __outputResultOfJarFile() { local jar_file="$1" file + # shellcheck disable=SC2206 + local grep_opt_args=($regex_mode ${ignore_case_option:-} ${grep_color_option:-} -- "$pattern") if $only_print_file_name; then local matched=false @@ -366,7 +372,7 @@ __outputResultOfJarFile() { # - 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 + if grep -c "${grep_opt_args[@]}" &>/dev/null; then matched=true fi @@ -375,18 +381,23 @@ __outputResultOfJarFile() { fi clearResponsiveMessage - [ -t 1 ] && echo "${ec}[1;35m${jar_file}${eend}" || echo "${jar_file}" + if [ -t 1 ]; then + printf "${jar_color}%s${normal}\n" "${jar_file}" + else + printf '%s\n' "${jar_file}" + fi 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 + grep "${grep_opt_args[@]}" || 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}" + if [ -t 1 ]; then + printf "${jar_color}%s${sep_color}%s${normal}%s\n" "$jar_file" "$separator" "$file" + else + printf '%s\n' "${jar_file}${separator}${file}" + fi done fi } From 15047b039e151ced56be35a1fa52e1448300d942 Mon Sep 17 00:00:00 2001 From: Jerry Lee Date: Mon, 28 Aug 2023 20:09:48 +0800 Subject: [PATCH 05/10] =?UTF-8?q?refactor/robust(`ap/rp`):=20improve=20rob?= =?UTF-8?q?ustness=20=F0=9F=92=AA=20;=20check=20failure=20and=20reflect=20?= =?UTF-8?q?as=20exit=20code;=20improve=20portability(`portableRelPath/port?= =?UTF-8?q?ableReadLink`)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NOTE: - use `printf` 💪 instead of `echo`; use `if-else` instead of `&&-||` - the `echo` option(e.g. -e -n) may effect correctness, `printf` is more robust 💪 - about `&&-||` see shell check: https://www.shellcheck.net/wiki/SC2015 --- bin/ap | 55 +++++++++++++++++++++++------------- bin/rp | 89 ++++++++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 100 insertions(+), 44 deletions(-) diff --git a/bin/ap b/bin/ap index 901d8055..ea6c984c 100755 --- a/bin/ap +++ b/bin/ap @@ -21,25 +21,24 @@ 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 - -colorEcho() { +colorPrint() { local color="$1" shift # check isatty in bash https://stackoverflow.com/questions/10022323 # if stdout is console, turn on color output. - [ -t 1 ] && echo "${ec}[1;${color}m$*${eend}" || echo "$*" + if [ -t 1 ]; then + printf "\033[1;${color}m%s\033[0m\n" "$*" + else + printf '%s\n' "$*" + fi } -redEcho() { - colorEcho 31 "$@" +redPrint() { + colorPrint 31 "$*" } die() { - redEcho "Error: $*" 1>&2 + redPrint "Error: $*" >&2 exit 1 } @@ -54,10 +53,15 @@ portableReadLink() { readlink -f "$file" ;; Darwin*) + local py_args=(-c 'import os, sys; print(os.path.realpath(sys.argv[1]))' "$file") if command -v greadlink >/dev/null; then greadlink -f "$file" + elif command -v python3 >/dev/null; then + python3 "${py_args[@]}" + elif command -v python >/dev/null; then + python "${py_args[@]}" else - python -c 'import os, sys; print(os.path.realpath(sys.argv[1]))' "$file" + die "fail to find command(greadlink/python3/python) to get absolute path!" fi ;; *) @@ -72,10 +76,12 @@ usage() { # shellcheck disable=SC2015 [ "$exit_code" != 0 ] && local -r out=/dev/stderr || local -r out=/dev/stdout - (($# > 0)) && redEcho "$*$nl" >$out + # NOTE: $'foo' is the escape sequence syntax of bash + local nl=$'\n' # new line + (($# > 0)) && redPrint "$*$nl" >$out cat >$out <&2 + has_error=true + fi done + +# set exit status +! $has_error diff --git a/bin/rp b/bin/rp index 01ad6c47..b11c5bd8 100755 --- a/bin/rp +++ b/bin/rp @@ -21,21 +21,51 @@ 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 - -colorEcho() { +colorPrint() { local color="$1" shift # check isatty in bash https://stackoverflow.com/questions/10022323 # if stdout is console, turn on color output. - [ -t 1 ] && echo "${ec}[1;${color}m$*${eend}" || echo "$*" + if [ -t 1 ]; then + printf "\033[1;${color}m%s\033[0m\n" "$*" + else + printf '%s\n' "$*" + fi +} + +redPrint() { + colorPrint 31 "$@" } -redEcho() { - colorEcho 31 "$@" +die() { + redPrint "Error: $*" >&2 + exit 1 +} + +portableRelPath() { + local file="$1" relTo="$2" uname + + uname="$(uname)" + case "$uname" in + Linux* | CYGWIN* | MINGW*) + realpath "$f" --relative-to="$relTo" + ;; + 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 + grealpath "$f" --relative-to="$relTo" + elif command -v python3 >/dev/null; then + python3 "${py_args[@]}" + elif command -v 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)!" + ;; + esac } usage() { @@ -44,15 +74,18 @@ usage() { # shellcheck disable=SC2015 [ "$exit_code" != 0 ] && local -r out=/dev/stderr || local -r out=/dev/stdout - (($# > 0)) && redEcho "$*$nl" >$out + # NOTE: $'foo' is the escape sequence syntax of bash + local nl=$'\n' # new line + (($# > 0)) && redPrint "$*$nl" >$out cat >$out <&2 - exit 1 -} +[ "${#files[@]}" -eq 0 ] && die "NO argument!" if [ "${#files[@]}" -eq 1 ]; then relativeTo=. @@ -113,12 +142,24 @@ else fi [ -f "$relativeTo" ] && relativeTo="$(dirname "$relativeTo")" +[ -e "$relativeTo" ] || die "relativeTo dir($relativeTo) does NOT exists!" + readonly files relativeTo +################################################################################ +# biz logic +################################################################################ + +has_error=false + for f in "${files[@]}"; do - ! [ -e "$f" ] && { - echo "$f does not exists!" - continue - } - realpath "$f" --relative-to="$relativeTo" + if [ -e "$f" ]; then + portableRelPath "$f" "$relativeTo" + else + redPrint "error: $f does not exists!" >&2 + has_error=true + fi done + +# set exit status +! $has_error From 7d166fe34e5f5cd3e010ef0961b11ca79d84a017 Mon Sep 17 00:00:00 2001 From: Jerry Lee Date: Tue, 29 Aug 2023 12:40:58 +0800 Subject: [PATCH 06/10] =?UTF-8?q?refactor/robust(`xpl/xpf`):=20improve=20r?= =?UTF-8?q?obustness=20=F0=9F=92=AA=20;=20check=20failure=20and=20reflect?= =?UTF-8?q?=20as=20exit=20code;=20improve=20portability(`portableRelPath/p?= =?UTF-8?q?ortableReadLink`)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NOTE: - use `printf` 💪 instead of `echo`; use `if-else` instead of `&&-||` - the `echo` option(e.g. -e -n) may effect correctness, `printf` is more robust 💪 - about `&&-||` see shell check: https://www.shellcheck.net/wiki/SC2015 --- bin/xpf | 26 ++++++++++++++++++++++---- bin/xpl | 46 ++++++++++++++++++++++++++++++++-------------- 2 files changed, 54 insertions(+), 18 deletions(-) diff --git a/bin/xpf b/bin/xpf index 3abad3b2..4e6c9dae 100755 --- a/bin/xpf +++ b/bin/xpf @@ -1,7 +1,7 @@ #!/bin/bash # @Function # Open file in file explorer, file is selected. -# same as xpl --selected [file [file ...] ] +# same as xpl --selected [file]... # # @Usage # $ ./xpf file @@ -10,6 +10,10 @@ # @author Jerry Lee (oldratlee at gmail dot com) set -eEuo pipefail +################################################################################ +# util functions +################################################################################ + # How can I get the behavior of GNU's readlink -f on a Mac? # https://stackoverflow.com/questions/1055671 portableReadLink() { @@ -21,10 +25,16 @@ portableReadLink() { readlink -f "$file" ;; Darwin*) + local py_args=(-c 'import os, sys; print(os.path.realpath(sys.argv[1]))' "$file") if command -v greadlink >/dev/null; then greadlink -f "$file" + elif command -v python3 >/dev/null; then + python3 "${py_args[@]}" + elif command -v python >/dev/null; then + python "${py_args[@]}" else - python -c 'import os, sys; print(os.path.realpath(sys.argv[1]))' "$file" + echo "fail to find command(greadlink/python3/python) for readlink!" >&2 + exit 1 fi ;; *) @@ -34,5 +44,13 @@ portableReadLink() { esac } -BASE="$(dirname "$(portableReadLink "${BASH_SOURCE[0]}")")" -source "$BASE/xpl" "$@" +################################################################################ +# biz logic +################################################################################ + +# DO NOT inline THIS_SCRIPT into BASE_DIR, because sub-shell: +# BASE_DIR="$(dirname "$(portableReadLink "${BASH_SOURCE[0]}")")" +THIS_SCRIPT="$(portableReadLink "${BASH_SOURCE[0]}")" +BASE_DIR="$(dirname "$THIS_SCRIPT")" + +source "$BASE_DIR/xpl" "$@" diff --git a/bin/xpl b/bin/xpl index b197836e..fbad3a37 100755 --- a/bin/xpl +++ b/bin/xpl @@ -30,7 +30,9 @@ PROG="$(basename "$0")" readonly PROG readonly PROG_VERSION='2.5.0-dev' -readonly nl=$'\n' # new line +################################################################################ +# util functions +################################################################################ usage() { local -r exit_code="${1:-0}" @@ -38,7 +40,7 @@ usage() { # shellcheck disable=SC2015 [ "$exit_code" != 0 ] && local -r out=/dev/stderr || local -r out=/dev/stdout - (($# > 0)) && echo "$*$nl" >$out + (($# > 0)) && printf '%s\n\n' "$*" >$out cat < Date: Wed, 30 Aug 2023 13:08:59 +0800 Subject: [PATCH 07/10] =?UTF-8?q?refactor/robust(`cp-into-docker-run`):=20?= =?UTF-8?q?use=20`printf`=20=F0=9F=92=AA=20instead=20of=20`echo`;=20use=20?= =?UTF-8?q?`if-else`=20instead=20of=20`&&-||`;=20improve=20portability(`po?= =?UTF-8?q?rtableReadLink`)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NOTE: - the `echo` option(e.g. -e -n) may effect correctness, `printf` is more robust 💪 - about `&&-||` see shell check: https://www.shellcheck.net/wiki/SC2015 --- bin/cp-into-docker-run | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/bin/cp-into-docker-run b/bin/cp-into-docker-run index 66648c0b..ffac978b 100755 --- a/bin/cp-into-docker-run +++ b/bin/cp-into-docker-run @@ -17,18 +17,17 @@ 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 - -redEcho() { +redPrint() { # -t check: is a terminal device? - [ -t 1 ] && echo "${ec}[1;31m$*$eend" || echo "$*" + if [ -t 1 ]; then + printf "\033[1;31m%s\033[0m\n" "$*" + else + printf '%s\n' "$*" + fi } die() { - redEcho "Error: $*" 1>&2 + redPrint "Error: $*" 1>&2 exit 1 } @@ -47,10 +46,15 @@ portableReadLink() { readlink -f "$file" ;; Darwin*) + local py_args=(-c 'import os, sys; print(os.path.realpath(sys.argv[1]))' "$file") if command -v greadlink >/dev/null; then greadlink -f "$file" + elif command -v python3 >/dev/null; then + python3 "${py_args[@]}" + elif command -v python >/dev/null; then + python "${py_args[@]}" else - python -c 'import os, sys; print(os.path.realpath(sys.argv[1]))' "$file" + die "fail to find command(greadlink/python3/python) for readlink!" fi ;; *) @@ -65,7 +69,9 @@ usage() { # shellcheck disable=SC2015 [ "$exit_code" != 0 ] && local -r out=/dev/stderr || local -r out=/dev/stdout - (($# > 0)) && redEcho "$*$nl" >$out + # NOTE: $'foo' is the escape sequence syntax of bash + local nl=$'\n' # new line + (($# > 0)) && redPrint "$*$nl" >$out cat >$out <&2 + $verbose && printf '%s\n' "[$PROG] $*" 1>&2 "$@" } From e5f5fac46cf69663bde478095fc70b5bef20f71f Mon Sep 17 00:00:00 2001 From: Jerry Lee Date: Wed, 30 Aug 2023 13:21:00 +0800 Subject: [PATCH 08/10] =?UTF-8?q?refactor/robust(`tcp-connection-state-cou?= =?UTF-8?q?nter/console-text-color-themes.sh`):=20use=20`printf`=20?= =?UTF-8?q?=F0=9F=92=AA=20instead=20of=20`echo`;=20use=20`if-else`=20inste?= =?UTF-8?q?ad=20of=20`&&-||`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NOTE: - the `echo` option(e.g. -e -n) may effect correctness, `printf` is more robust 💪 - about `&&-||` see shell check: https://www.shellcheck.net/wiki/SC2015 --- bin/tcp-connection-state-counter | 3 +- lib/console-text-color-themes.sh | 47 +++++++++++++++++++------------- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/bin/tcp-connection-state-counter b/bin/tcp-connection-state-counter index 6629e8fb..cbebc013 100755 --- a/bin/tcp-connection-state-counter +++ b/bin/tcp-connection-state-counter @@ -35,7 +35,7 @@ EOF } progVersion() { - echo "$PROG $PROG_VERSION" + printf '%s\n' "$PROG $PROG_VERSION" exit } @@ -58,6 +58,7 @@ done # On MacOS, netstat need to using -p tcp to get only tcp output. uname | grep Darwin -q && option_for_mac="-ptcp" +# shellcheck disable=SC2086 netstat -tna ${option_for_mac:-} | awk 'NR > 2 { ++s[$NF] } diff --git a/lib/console-text-color-themes.sh b/lib/console-text-color-themes.sh index dcff9380..63de93ff 100755 --- a/lib/console-text-color-themes.sh +++ b/lib/console-text-color-themes.sh @@ -37,55 +37,64 @@ colorEcho() { local combination="$1" shift 1 - [ -t 1 ] && echo "${_ctct_ec}[${combination}m$*$_ctct_eend" || echo "$*" + if [ -t 1 ]; then + echo "${_ctct_ec}[${combination}m$*$_ctct_eend" + else + echo "$*" + fi } colorEchoWithoutNewLine() { local combination="$1" shift 1 - [ -t 1 ] && echo -n "${_ctct_ec}[${combination}m$*$_ctct_eend" || echo -n "$*" + if [ -t 1 ]; then + echo -n "${_ctct_ec}[${combination}m$*$_ctct_eend" + else + echo -n "$*" + fi } # if not directly run this script(use as lib), just export 2 helper functions, # and do NOT print anything. -[ "$_ctct_is_direct_run" == "true" ] && { +[ "$_ctct_is_direct_run" == true ] && { for style in 0 1 2 3 4 5 6 7; do for fg in 30 31 32 33 34 35 36 37; do for bg in 40 41 42 43 44 45 46 47; do combination="${style};${fg};${bg}" colorEchoWithoutNewLine "$combination" "$combination" - echo -n " " + printf ' ' done echo done echo done - echo "Code sample to print color text:" + echo 'Code sample to print color text:' echo -n ' echo -e "\033[' - colorEchoWithoutNewLine "3;35;40" "1;36;41" - echo -n "m" - colorEchoWithoutNewLine "0;32;40" "Sample Text" - echo "\033[0m\"" + colorEchoWithoutNewLine '3;35;40' '1;36;41' + echo -n m + colorEchoWithoutNewLine '0;32;40' 'Sample Text' + echo '\033[0m"' echo -n " echo \$'\033[" - colorEchoWithoutNewLine "3;35;40" "1;36;41" + colorEchoWithoutNewLine '3;35;40' '1;36;41' echo -n "m'\"" - colorEchoWithoutNewLine "0;32;40" "Sample Text" + colorEchoWithoutNewLine '0;32;40' 'Sample Text' echo "\"$'\033[0m'" echo " # NOTE: $'foo' is the escape sequence syntax of bash, safer escape" - echo "Output of above code:" - echo " ${_ctct_ec}[1;36;41mSample Text${_ctct_eend}" + echo 'Output of above code:' + echo -n ' ' + colorEcho '1;36;41' 'Sample Text' echo - echo "If you are going crazy to write text in escapes string like me," - echo "you can use colorEcho and colorEchoWithoutNewLine function in this script." + echo 'If you are going crazy to write text in escapes string like me,' + echo 'you can use colorEcho and colorEchoWithoutNewLine function in this script.' echo - echo "Code sample to print color text:" + echo 'Code sample to print color text:' echo ' colorEcho "1;36;41" "Sample Text"' - echo "Output of above code:" - echo -n " " - colorEcho "1;36;41" "Sample Text" + echo 'Output of above code:' + echo -n ' ' + colorEcho '1;36;41' 'Sample Text' } From b2a6bb6e457d39a93a928c15a55f8f6931d7298c Mon Sep 17 00:00:00 2001 From: Jerry Lee Date: Wed, 30 Aug 2023 13:58:55 +0800 Subject: [PATCH 09/10] =?UTF-8?q?refactor/robust(`uq`):=20use=20`printf`?= =?UTF-8?q?=20=F0=9F=92=AA=20instead=20of=20`echo`;=20use=20`if-else`=20in?= =?UTF-8?q?stead=20of=20`&&-||`;=20use=20`${var#}`=20instead=20of=20`awk`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NOTE: - the `echo` option(e.g. -e -n) may effect correctness, `printf` is more robust 💪 - about `&&-||` see shell check: https://www.shellcheck.net/wiki/SC2015 --- bin/uq | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/bin/uq b/bin/uq index f7b2ebee..bdb59914 100755 --- a/bin/uq +++ b/bin/uq @@ -38,20 +38,28 @@ readonly PROG_VERSION='2.5.0-dev' ################################################################################ # 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 - -redEcho() { - [ -t 1 ] && echo "${ec}[1;31m$*$eend" || echo "$*" +readonly nl=$'\n' # new line + +redPrint() { + # -t check: is a terminal device? + if [ -t 1 ]; then + printf "\033[1;31m%s\033[0m\n" "$*" + else + printf '%s\n' "$*" + fi } -yellowEcho() { - [ -t 1 ] && echo "${ec}[1;33m$*$eend" || echo "$*" +yellowPrint() { + # -t check: is a terminal device? + if [ -t 1 ]; then + printf "\033[1;33m%s\033[0m\n" "$*" + else + printf '%s\n' "$*" + fi } die() { - redEcho "Error: $*" 1>&2 + redPrint "Error: $*" 1>&2 exit 1 } @@ -82,7 +90,7 @@ usage() { # shellcheck disable=SC2015 [ "$exit_code" != 0 ] && local -r out=/dev/stderr || local -r out=/dev/stdout - (($# > 0)) && redEcho "$*$nl" >$out + (($# > 0)) && redPrint "$*$nl" >$out cat >$out < 0)); do --all-repeated=*) uq_opt_all_repeated=1 - uq_opt_repeated_method=$(echo "$1" | awk -F= '{print $2}') + 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’" @@ -209,7 +217,7 @@ done usage 2 "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) ]] && - yellowEcho "[$PROG] WARN: -D/--all-repeated=none option without -c/-d option, just cat input simply!" >&2 + yellowPrint "[$PROG] WARN: -D/--all-repeated=none option without -c/-d option, just cat input simply!" >&2 # NOTE: DO NOT declare var uq_max_input_size as readonly in ONE line! uq_max_input_size="$(convertHumanReadableSizeToSize "$uq_max_input_human_readable_size")" || From 0942d598eeee3619540834f4191460283dfda507 Mon Sep 17 00:00:00 2001 From: Jerry Lee Date: Fri, 1 Sep 2023 00:41:37 +0800 Subject: [PATCH 10/10] =?UTF-8?q?refactor/robust(`show-busy-java-threads`)?= =?UTF-8?q?:=20use=20`printf`=20=F0=9F=92=AA=20instead=20of=20`echo`;=20us?= =?UTF-8?q?e=20`if-else`=20instead=20of=20`&&-||`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NOTE: - the `echo` option(e.g. -e -n) may effect correctness, `printf` is more robust 💪 - about `&&-||` see shell check: https://www.shellcheck.net/wiki/SC2015 --- bin/show-busy-java-threads | 91 ++++++++++++++++++++------------------ 1 file changed, 49 insertions(+), 42 deletions(-) diff --git a/bin/show-busy-java-threads b/bin/show-busy-java-threads index 4abd1778..2e761a96 100755 --- a/bin/show-busy-java-threads +++ b/bin/show-busy-java-threads @@ -53,59 +53,66 @@ readonly ec=$'\033' # escape char readonly eend=$'\033[0m' # escape end readonly nl=$'\n' # new line -colorEcho() { +colorPrint() { local color=$1 shift # if stdout is console, turn on color output. - [ -t 1 ] && echo "${ec}[1;${color}m$*$eend" || echo "$@" + if [ -t 1 ]; then + printf "\033[1;${color}m%s\033[0m\n" "$*" + else + printf '%s\n' "$*" + fi } -colorPrint() { +__appendToFile() { + [[ -n "$append_file" && -w "$append_file" ]] && printf '%s\n' "$*" >>"$append_file" + [[ -n "$store_dir" && -w "$store_dir" ]] && printf '%s\n' "$*" >>"${store_file_prefix}$PROG" +} + +colorOutput() { local color=$1 shift - colorEcho "$color" "$@" - [[ -n "$append_file" && -w "$append_file" ]] && echo "$@" >>"$append_file" - [[ -n "$store_dir" && -w "$store_dir" ]] && echo "$@" >>"${store_file_prefix}$PROG" + colorPrint "$color" "$*" + __appendToFile "$*" } # shellcheck disable=SC2120 -normalPrint() { - echo "$@" - [[ -n "$append_file" && -w "$append_file" ]] && echo "$@" >>"$append_file" - [[ -n "$store_dir" && -w "$store_dir" ]] && echo "$@" >>"${store_file_prefix}$PROG" +normalOutput() { + printf '%s\n' "$*" + __appendToFile "$*" } -redPrint() { - colorPrint 31 "$@" +redOutput() { + colorOutput 31 "$*" } -greenPrint() { - colorPrint 32 "$@" +greenOutput() { + colorOutput 32 "$*" } -yellowPrint() { - colorPrint 33 "$@" +yellowOutput() { + colorOutput 33 "$*" } -bluePrint() { - colorPrint 36 "$@" +blueOutput() { + colorOutput 36 "$*" } die() { - redPrint "Error: $*" 1>&2 + redOutput "Error: $*" 1>&2 exit 1 } logAndRun() { - echo "$@" + printf '%s\n' "$*" echo "$@" } logAndCat() { - echo "$@" + printf '%s\n' "$*" echo cat } @@ -147,7 +154,7 @@ usage() { # shellcheck disable=SC2015 [ "$exit_code" != 0 ] && local -r out=/dev/stderr || local -r out=/dev/stdout - (($# > 0)) && colorEcho 31 "$*$nl" >$out + (($# > 0)) && colorPrint 31 "$*$nl" >$out cat >$out <"${store_file_prefix}$((update_round_num + 1))_ps" + printf '%s\n' "$ps_out" | logAndCat "${ps_cmd_line[*]} | sort -k3,3nr" >"${store_file_prefix}$((update_round_num + 1))_ps" fi if ((count > 0)); then - echo "$ps_out" | head -n "${count}" + printf '%s\n' "$ps_out" | head -n "${count}" else - echo "$ps_out" + printf '%s\n' "$ps_out" fi } @@ -473,13 +480,13 @@ __top_threadId_cpu() { local top_out top_out=$(HOME="$tmp_store_dir" "${top_cmd_line[@]}") if [ -n "$store_dir" ]; then - echo "$top_out" | logAndCat "${top_cmd_line[@]}" >"${store_file_prefix}$((update_round_num + 1))_top" + printf '%s\n' "$top_out" | logAndCat "${top_cmd_line[@]}" >"${store_file_prefix}$((update_round_num + 1))_top" fi # DO NOT combine var result_threads_top_info declaration and assignment in ONE line! local result_threads_top_info result_threads_top_info=$( - echo "$top_out" | awk '{ + 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 @@ -490,7 +497,7 @@ __top_threadId_cpu() { ) [ -n "$result_threads_top_info" ] || __die_when_no_java_process_found - echo "$result_threads_top_info" | sort -k2,2nr + printf '%s\n' "$result_threads_top_info" | sort -k2,2nr } __complete_pid_user_by_ps() { @@ -501,7 +508,7 @@ __complete_pid_user_by_ps() { local ps_out ps_out="$("${ps_cmd_line[@]}")" if [ -n "$store_dir" ]; then - echo "$ps_out" | logAndCat "${ps_cmd_line[@]}" >"${store_file_prefix}$((update_round_num + 1))_ps" + printf '%s\n' "$ps_out" | logAndCat "${ps_cmd_line[@]}" >"${store_file_prefix}$((update_round_num + 1))_ps" fi local idx=0 threadId pcpu output_fields @@ -509,13 +516,13 @@ __complete_pid_user_by_ps() { ((count <= 0 || idx < count)) || break # output field: pid, threadId, pcpu, user - output_fields="$(echo "$ps_out" | + 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++)) - echo "$output_fields" + printf '%s\n' "$output_fields" fi done } @@ -543,20 +550,20 @@ printStackOfThreads() { logAndRun sudo -u "${user}" "${jstack_cmd_line[@]}" >"${jstackFile}" else # current user is not root user, so can not run with sudo; print error message and rerun suggestion - redPrint "[$idx] Fail to jstack busy(${pcpu}%) thread(${threadId}/${threadId0x}) stack of java process(${pid}) under user(${user})." - redPrint "User of java process($user) is not current user($USER), need sudo to rerun:" - yellowPrint " sudo $(printCallingCommandLine)" - normalPrint + redOutput "[$idx] Fail to jstack busy(${pcpu}%) thread(${threadId}/${threadId0x}) stack of java process(${pid}) under user(${user})." + redOutput "User of java process($user) is not current user($USER), need sudo to rerun:" + yellowOutput " sudo $(printCallingCommandLine)" + normalOutput continue fi || { - redPrint "[$idx] Fail to jstack busy(${pcpu}%) thread(${threadId}/${threadId0x}) stack of java process(${pid}) under user(${user})." - normalPrint + redOutput "[$idx] Fail to jstack busy(${pcpu}%) thread(${threadId}/${threadId0x}) stack of java process(${pid}) under user(${user})." + normalOutput rm "${jstackFile}" &>/dev/null continue } } - bluePrint "[$idx] Busy(${pcpu}%) thread(${threadId}/${threadId0x}) stack of java process(${pid}) under user(${user}):" + blueOutput "[$idx] Busy(${pcpu}%) thread(${threadId}/${threadId0x}) stack of java process(${pid}) under user(${user}):" if [ -n "$mix_native_frames" ]; then local sed_script="/--------------- $threadId ---------------/,/^---------------/ {