# main function designed for quickly copying to another programprogressBar(){Bar=""# Progress Bar / Volume levelLen=25# Length of Progress Bar / Volume levelDiv=4# Divisor into Volume for # of blocksFill="▒"# Fill up to $LenArr=("▉""▎""▌""▊")# UTF-8 left blocks: 7/8, 1/4, 1/2, 3/4FullBlock=$((${1} / Div))# Number of full blocksPartBlock=$((${1} % Div))# Size of partial block (array index)while[[$FullBlock-gt0]];doBar="$Bar${Arr[0]}"# Add 1 full block into Progress Bar(( FullBlock--))# Decrement full blocks counterdone# if remainder zero no partial block, else append character from arrayif[[$PartBlock-gt0]];thenBar="$Bar${Arr[$PartBlock]}";fi# Pad Progress Bar with fill characterwhile[["${#Bar}"-lt"$Len"]];doBar="$Bar$Fill";doneecho progress :"$1$Bar"exit0# Remove this line when copying into program}# progressBarMain(){
tput civis # Turn off cursorfor((i=0; i<=100; i++));doCurrLevel=$(progressBar "$i")# Generate progress bar 0 to 100echo-ne"$CurrLevel"\\r # Reprint overtop same linesleep .04
doneecho-e\\n # Advance line to keep last progressecho"$0 Done"
tput cnorm # Turn cursor back on}# main
Main "$@"
#!/usr/bin/env bash# credit: https://github.com/ppo/bash-colors# shellcheck disable=SC2015,SC2059c(){[$#==0]&&printf"\e[0m"||printf"$1"|sed's/\(.\)/\1;/g;s/\([SDIUFNHT]\)/2\1/g;s/\([KRGYBMCW]\)/3\1/g;s/\([krgybmcw]\)/4\1/g;y/SDIUFNHTsdiufnhtKRGYBMCWkrgybmcw/12345789123457890123456701234567/;s/^\(.*\);$/\\e[\1m/g';}# capture ctrl-c to exit the sub-process# return the sub-process stdout ( to external variable )functionwithSpinner(){localmsg="$1";shiftlocal__resultvar="$1";shiftlocalspinner=("$(c Rs)⣾$(c)""$(c Ys)⣽$(c)""$(c Gs)⣻$(c)""$(c Cs)⢿$(c)""$(c Rs)⡿$(c)""$(c Ys)⣟$(c)""$(c Gs)⣯$(c)""$(c Cs)⣷$(c)")localframe=0local output
local cmdPid
localpgid=''localinterrupted=0# define the cursor recovery functionrestoreCursor(){printf"\033[?25h">&2;}# make sure that any exit restores the cursortrap'restoreCursor' EXIT
# hide cursorprintf"\033[?25l">&2printf"%s ""$msg">&2set-mtrap'interrupted=1; [ -n "$pgid" ] && kill -TERM -- -$pgid 2>/dev/null' INT
# use file descriptor to capture outputlocal tmpout
tmpout=$(mktemp)exec3<>"${tmpout}"# shellcheck disable=SC2031,SC2030output="$(
{
# execute command and redirect output to file descriptor 3
"$@" >&3 2>/dev/null &
cmdPid=$!
pgid=$(ps-opgid="$cmdPid"|tr-d' ')
# update the spinner while the command is running
while kill -0 "$cmdPid" 2>/dev/null && (( interrupted ==0)); do
printf "\r\033[K%s %b" "${msg}" "${spinner[frame]}" >&2
((frame =(frame +1)% ${#spinner[@]}))
sleep 0.08
done
wait "$cmdPid" 2>/dev/null
# show the captured content
cat "${tmpout}"
}
)"# clean the temporary fileexec3>&-
rm-f"${tmpout}"# \r : beginning of line# \033[K : clear current position to end of line# shellcheck disable=SC2031if(( interrupted ));thenprintf"\r\033[K\033[31m✗\033[0m Interrupted!\033[K\n">&2[-n"${pgid}"]&&kill-TERM -- -"${pgid}"2>/dev/null
else# or using `printf "\r" >&2` directly without sub-progress status outputprintf"\r\033[K\033[32m✓\033[0m Done!\033[K\n">&2fi# assign the result to an external variableprintf-v"$__resultvar""%s""$output"}functionmain(){# shellcheck disable=SC2155localtmpfile=$(mktemp)trap'rm -f "${tmpfile}"' EXIT
local response
withSpinner "Loading..." response \curl-s https://<API>...
# check curl outputecho"${response}"}
main "$@"# vim:tabstop=2:softtabstop=2:shiftwidth=2:expandtab:filetype=sh:
since fdOpt is a single string (containing multiple arguments), Bash treats it as one single argument when passed to fd. This leads to the following issues:
--exclude '*.png' is treated as one single argument, rather than two separate ones: --exclude and '*.png';
As a result, fd cannot correctly interpret the glob pattern and treats it as a literal string;
Therefore, --exclude '*.png' does not actually exclude anything.
recommend using arrays to store multiple arguments and then pass them to the command.
# arraylocal-afdArgs=(--type f --hidden--follow--unrestricted --ignore-file "${HOME}/.fdignore")localignores=('*.pem''*.p12''*.png''*.jpg''*.jpeg''*.gif''*.svg''*.zip''*.tar''*.gz''*.bz2''*.xz''*.7z''*.rar''Music''.target_book''_book''OneDrive*')forpatternin"${ignores[@]}";dofdArgs+=(--exclude "${pattern}");donefdArgs+=(--exec-batch ls -t)# array call# +------------+
fd ."${fdArgs[@]}"| fzf ${foption}--bind="enter:become(${VIM} {+})"
details:
FORM
WORKS?
REASON
fd . ${fdOpt}
❌ No
${fdOpt} is a single string; arguments are not properly split
eval "fd . ${fdOpt}"
✅ Yes
Bash re-splits the command string before execution, but it’s risky
fd . "${fdArgs[@]}"
✅✅ Yes (Recommended)
Uses an argument array — most recommended, safe, and clean
METHOD
ARGUMENT PARSING
SAFETY
WILDCARD EXPANSION
RECOMMENDED USE CASE
$cmd
❌ Incorrect, treated as a single command
❌ Low
❌ No
Avoid using
eval "$cmd"
✅ Correctly splits arguments
⚠️ Low
✅ Yes
Quick testing or executing ad-hoc command strings
"${cmd[@]}"
✅ Correct and safe argument passing
✅ High
❌ No (no expansion)
Recommended for building command argument lists programmatically
$ ls
bar.bak bar.txt demo.sh foo.log foo.txt
$ bash demo.sh
→ Running: echo Listing *.txt with excludes: --exclude'*.log'--exclude'*.bak'
Listing bar.txt foo.txt with excludes: --exclude'*.log'--exclude'*.bak'# +-------------+# *.txt got expanded
→ Running with eval: echo Listing *.txt with excludes: --exclude'*.log'--exclude'*.bak'
Listing bar.txt foo.txt with excludes: --exclude *.log --exclude *.bak
# +-------------+# *.txt got expanded
→ Running with array: echo Listing *.txt with excludes: --exclude *.log --exclude *.bak
Listing *.txt with excludes: --exclude *.log --exclude *.bak
$ cat-c demo.sh
#!/usr/bin/env bashset-euo pipefail
functionplainString(){localcmd="echo Listing *.txt with excludes: --exclude '*.log' --exclude '*.bak'"echo"→ Running: $cmd"$cmd}functionevalString(){localcmd="echo Listing *.txt with excludes: --exclude '*.log' --exclude '*.bak'"echo"→ Running with eval: $cmd"eval"$cmd"}functionarrayCall(){local-acmd=("echo""Listing""*.txt""with""excludes:""--exclude""*.log""--exclude""*.bak")echo"→ Running with array: ${cmd[*]}""${cmd[@]}"}
plainString
echo''
evalString
echo''
arrayCall
# vim:tabstop=2:softtabstop=2:shiftwidth=2:expandtab:filetype=sh:
wildcard expansion
METHOD
WILDCARD EXPANDED?
EXPLANATION
eval "echo *.txt"
✅ Yes
Shell expands the wildcard during evaluation
eval "echo '*.txt'"
❌ No
'*.txt' is a quoted string literal, not subject to expansion
"${arr[@]}"
❌ No
Arguments are passed as literal strings, no globbing applied