📌
ibook
  • README
  • cheatsheet
    • bash
      • builtin
      • syntactic sugar
      • cmd
      • havefun
    • text-processing
      • awk
      • sed
      • html
      • json
      • regex
      • unicode
    • osx
    • curl
    • tricky
    • widget
    • proxy
    • colors
    • math
    • media
    • ssl
      • keystore
      • verification
      • server
      • client
      • tricky
    • windows
      • powershell
      • choco
      • wsl
      • wt
      • shortcut
      • clsid
      • env
      • shell:folder
  • vim
    • nvim
    • install
    • color
    • plugins
      • usage
      • other plugins
      • deprecated
    • tricky
    • viml
    • windows
    • troubleshooting
  • devops
    • admin tools
    • ssh
    • git
      • config
      • alias
      • submodule
      • eol
      • example
      • gerrit
        • gerrit API
      • github
      • troubleshooting
      • tricky
      • statistics
    • pre-commit
    • release-tools
    • tmux
      • cheatsheet
    • ansible
    • vault
    • artifactory
      • api
      • cli
      • aql
      • nginx cert
    • klocwork
      • kwadmin
      • kwserver
      • api
      • q&a
    • elk
    • mongodb
    • android
    • mobile
  • jenkins
    • config
      • windows
    • appearance
    • troubleshooting
    • jenkinsfile
      • utility
      • parallel
      • build
      • envvar
      • properties
      • trigger
      • node
    • script
      • job
      • build
      • stage
      • agent
      • security & authorization
      • exception
      • monitor
      • tricky
    • api
      • blueocean
    • cli
    • plugins
      • kubernetes
      • docker
      • shared-libs
      • lockable-resource
      • ansicolor
      • badge
      • groovy-postbuild
      • simple-theme
      • customizable-header
      • artifactory
      • jira-steps
      • job-dsl
      • build-timeline
      • crumbIssuer
      • coverage
      • uno-choice
      • tricky
  • virtualization
    • kubernetes
      • init
        • kubespray
        • kubeadm
          • environment
          • crio v1.30.4
          • docker v1.15.3
          • HA
        • addons
        • etcd
      • kubectl
        • pod
        • deploy
        • replicasets
        • namespace
        • secrets
      • node
      • certificates
      • events
      • kubeconfig
      • kubelet
      • troubleshooting
      • cheatsheet
      • auth
      • api
      • tools
        • monitor
        • helm
        • network
        • minikube
    • docker
      • run & exec
      • voume
      • remove
      • show info
      • dockerfile
      • dockerd
      • tricky
      • troubleshooting
      • windows
    • crio
    • podman
  • ai
    • prompt
  • osx
    • apps
      • init
      • brew
    • defaults
    • system
    • network
    • script
    • tricky
  • linux
    • devenv
    • util
      • time & date
      • output formatting
      • params
      • tricky
    • nutshell
    • disk
    • network
    • troubleshooting
    • system
      • apt/yum/snap
      • authorization
      • apps
      • x11
    • ubuntu
      • systemctl
      • x
    • rpi
  • programming
    • groovy
    • python
      • config
      • basic
      • list
      • pip
      • q&a
    • others
    • archive
      • angular
      • maven
      • mysql
        • installation
        • logs
      • ruby
        • rubyInstallationQ&A
  • tools
    • fonts
    • html & css
    • Jira & Confluence
    • node & npm
      • gitbook
      • hexo
      • github.page
      • code themes
    • app
      • microsoft office
      • vscode
      • virtualbox
      • iterm2
      • browser
      • skype
      • teamviewer
      • others
  • quotes
  • english
Powered by GitBook
On this page
  • process bar
  • spinner
  • save & restore screen
  • terminfo escape sequences
  • tput
  • Operation not permitted
  • array

Was this helpful?

  1. linux
  2. util

tricky

PreviousparamsNextnutshell

Last updated 7 days ago

Was this helpful?

process bar

reference:

with dot .

reference:

while true; do
  (( i++ == 0 )) && printf $(c sY)%-6s$(c) 'waiting ...' || printf $(c sY)%s$(c) '.'
  sleep 1
done

another:

# main function designed for quickly copying to another program
progressBar() {
  Bar=""                                  # Progress Bar / Volume level
  Len=25                                  # Length of Progress Bar / Volume level
  Div=4                                   # Divisor into Volume for # of blocks
  Fill="▒"                                # Fill up to $Len
  Arr=( "▉" "▎" "▌" "▊" )                 # UTF-8 left blocks: 7/8, 1/4, 1/2, 3/4

  FullBlock=$((${1} / Div))               # Number of full blocks
  PartBlock=$((${1} % Div))               # Size of partial block (array index)

  while [[ $FullBlock -gt 0 ]]; do
      Bar="$Bar${Arr[0]}"                 # Add 1 full block into Progress Bar
      (( FullBlock-- ))                   # Decrement full blocks counter
  done

  # if remainder zero no partial block, else append character from array
  if [[ $PartBlock -gt 0 ]]; then Bar="$Bar${Arr[$PartBlock]}"; fi

  # Pad Progress Bar with fill character
  while [[ "${#Bar}" -lt "$Len" ]]; do Bar="$Bar$Fill"; done

  echo progress : "$1 $Bar"
  exit 0                                  # Remove this line when copying into program
} # progressBar

Main () {
  tput civis                              # Turn off cursor
  for ((i=0; i<=100; i++)); do
    CurrLevel=$(progressBar "$i")         # Generate progress bar 0 to 100
    echo -ne "$CurrLevel"\\r              # Reprint overtop same line
    sleep .04
  done
  echo -e \\n                             # Advance line to keep last progress
  echo "$0 Done"
  tput cnorm                              # Turn cursor back on
} # main

Main "$@"

another solution:

BAR='##############################'
FILL='------------------------------'
totalLines=100
barLen=30
count=0

while [ ${count} -lt ${totalLines} ]; do
  # update progress bar
  count=$(( ${count}+ 1 ))
  percent=$(( (${count} * 100 / ${totalLines} * 100)/ 100 ))
  i=$(( ${percent} * ${barLen} / 100 ))
  echo -ne "\r[${BAR:0:$i}${FILL:$i:barLen}] ${count}/${totalLines} (${percent}%)"
  sleep .1
done
while :; do
  for s in / - \\ \|
    do printf "\r$s"
    sleep .1
  done
done

spinner

[!TIP|label:check more:]

Braille Patterns

[!NOTE|label:references:]

+---+---+
| 1 | 4 |
+---+---+
| 2 | 5 |
+---+---+
| 3 | 6 |
+---+---+
| 7 | 8 |
+---+---+

4-dots

UNICODE
ICON
HTML ENCODING
COMMENTS

28C4

⣄

&#x28C4;

378

28C6

⣆

&#x28C6;

2378

2847

⡇

&#x2847;

1237

280F

⠏

&#x280F;

1234

280B

⠋

&#x280B;

124

2839

⠹

&#x2839;

1456

28B8

⢸

&#x28B8;

4568

28F0

⣰

&#x28F0;

5678

28E0

⣠

&#x28E0;

678

local spinner=( '⣄' '⣆' '⡇' '⠏' '⠋' '⠹' '⢸' '⣰' '⣠' )
local spinner=(
  "$(c Rs)⣄$(c)"    # red
  "$(c Ys)⣆$(c)"    # yellow
  "$(c Gs)⡇$(c)"    # green
  "$(c Bs)⠏$(c)"    # blue
  "$(c Ms)⠋$(c)"    # magenta
  "$(c Ys)⠹$(c)"    # yellow
  "$(c Gs)⢸$(c)"    # green
  "$(c Bs)⣰$(c)"    # blue
  "$(c Ms)⣠$(c)"    # magenta
)

7-dots

UNICODE
ICON
HTML ENCODING
COMMENTS

28FE

⣾

&#x28FE;

2345678

28FD

⣽

&#x28FD;

1345678

28FB

⣻

&#x28FB;

1245678

28BF

⢿

&#x28BF;

1234568

287F

⡿

&#x287F;

1234567

28DF

⣟

&#x28DF;

1234578

28EF

⣯

&#x28EF;

1234678

28F7

⣷

&#x28F7;

1235678

local spinner=( '⣾' '⣽' '⣻' '⢿' '⡿' '⣟' '⣯' '⣷' '⣿' )
local spinner=(
  "$(c Rs)⣾$(c)"     # red
  "$(c Ys)⣽$(c)"     # yellow
  "$(c Gs)⣻$(c)"     # green
  "$(c Cs)⢿$(c)"     # cyan
  "$(c Rs)⡿$(c)"     # red
  "$(c Ys)⣟$(c)"     # yellow
  "$(c Gs)⣯$(c)"     # green
  "$(c Cs)⣷$(c)"     # cyan
)

1-dot

local spinner=( '⠁' '⠂' '⠄' '⡀' '⢀' '⠠' '⠐' '⠈' )
local spinner=(
  "$(c Ys)⠁$(c)"     # yellow
  "$(c Gs)⠂$(c)"     # green
  "$(c Cs)⠄$(c)"     # cyan
  "$(c Ms)⡀$(c)"     # magenta
  "$(c Ys)⢀$(c)"     # yellow
  "$(c Gs)⠠$(c)"     # green
  "$(c Cs)⠐$(c)"     # cyan
  "$(c Ms)⠈$(c)"     # magenta
)

others

local spinner=( '∙∙∙∙∙' '●∙∙∙∙' '∙●∙∙∙' '∙∙●∙∙' '∙∙∙●∙' '∙∙∙∙●' )
local spinner=(
  "∙∙∙∙∙"
  "$(c Ys)●$(c)∙∙∙∙"     # yellow
  "∙$(c Gs)●$(c)∙∙∙"     # green
  "∙∙$(c Cs)●$(c)∙∙"     # cyan
  "∙∙∙$(c Bs)●$(c)∙"     # blue
  "∙∙∙∙$(c Ms)●$(c)"     # magenta
)
local spinner=(
  "$(c Rs)∙∙∙∙∙$(c)"     # red
  "$(c Ys)●∙∙∙∙$(c)"     # yellow
  "$(c Gs)∙●∙∙∙$(c)"     # green
  "$(c Cs)∙∙●∙∙$(c)"     # cyan
  "$(c Bs)∙∙∙●∙$(c)"     # blue
  "$(c Ms)∙∙∙∙●$(c)"     # magenta
)

bash script

[!TIP|label:use case:]

#!/usr/bin/env bash

# credit: https://github.com/ppo/bash-colors
# shellcheck disable=SC2015,SC2059
c() { [ $# == 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 )
function withSpinner() {
  local msg="$1"; shift
  local __resultvar="$1"; shift
  local spinner=(
    "$(c Rs)⣾$(c)"
    "$(c Ys)⣽$(c)"
    "$(c Gs)⣻$(c)"
    "$(c Cs)⢿$(c)"
    "$(c Rs)⡿$(c)"
    "$(c Ys)⣟$(c)"
    "$(c Gs)⣯$(c)"
    "$(c Cs)⣷$(c)"
  )
  local frame=0
  local output
  local cmdPid
  local pgid=''
  local interrupted=0

  # define the cursor recovery function
  restoreCursor() { printf "\033[?25h" >&2; }

  # make sure that any exit restores the cursor
  trap 'restoreCursor' EXIT

  # hide cursor
  printf "\033[?25l" >&2
  printf "%s " "$msg" >&2

  set -m
  trap 'interrupted=1; [ -n "$pgid" ] && kill -TERM -- -$pgid 2>/dev/null' INT

  # use file descriptor to capture output
  local tmpout
  tmpout=$(mktemp)
  exec 3<> "${tmpout}"

  # shellcheck disable=SC2031,SC2030
  output="$(
    {
      # execute command and redirect output to file descriptor 3
      "$@" >&3 2>/dev/null &
      cmdPid=$!
      pgid=$(ps -o pgid= "$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 file
  exec 3>&-
  rm -f "${tmpout}"

  # \r : beginning of line
  # \033[K : clear current position to end of line
  # shellcheck disable=SC2031
  if (( interrupted )); then
    printf "\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 output
    printf "\r\033[K\033[32m✓\033[0m Done!\033[K\n" >&2
  fi

  # assign the result to an external variable
  printf -v "$__resultvar" "%s" "$output"
}

function main() {
  # shellcheck disable=SC2155
  local tmpfile=$(mktemp)
  trap 'rm -f "${tmpfile}"' EXIT

  local response
  withSpinner "Loading..." response \
    curl -s https://<API> ...

  # check curl output
  echo "${response}"
}

main "$@"

# vim:tabstop=2:softtabstop=2:shiftwidth=2:expandtab:filetype=sh:
#!/usr/bin/env bash

# credit: https://github.com/ppo/bash-colors
# shellcheck disable=SC2015,SC2059
c() { [ $# == 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
function withSpinner() {
  local msg="$1"; shift
  local __resultvar="$1"; shift
  local spinner=(
    "$(c Rs)⣄$(c)"
    "$(c Ys)⣆$(c)"
    "$(c Gs)⡇$(c)"
    "$(c Bs)⠏$(c)"
    "$(c Ms)⠋$(c)"
    "$(c Ys)⠹$(c)"
    "$(c Gs)⢸$(c)"
    "$(c Bs)⣰$(c)"
    "$(c Ms)⣠$(c)"
  )
  local frame=0
  local output
  local cmdPid
  local pgid=""
  local interrupted=0

  # explicit recovery cursor
  function restoreCursor() { printf "\033[?25h" >&2; }

  # ensure that any exit restores the cursor.
  trap 'restoreCursor' EXIT

  # hide cursor
  printf "\033[?25l" >&2
  printf "%s " "${msg}" >&2

  set -m
  trap 'interrupted=1; [ -n "${pgid}" ] && kill -TERM -- -${pgid} 2>/dev/null' INT

  # shellcheck disable=SC2034,SC2030
  output="$(
    {
      "$@" 2>/dev/null &
      cmdPid=$!
      pgid=$(ps -o pgid= ${cmdPid} | tr -d ' ')
      echo "${pgid}" > "${tmpfile}"

      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.1
      done

      wait "${cmdPid}" 2>/dev/null
    }
  )"

  # \r : beginning of line
  # \033[K : clear current position to end of line
  if (( interrupted )); then
    printf "\r\033[K\033[31m✗\033[0m Interrupted!\033[K\n" >&2
    # shellcheck disable=SC2031
    [ -n "${pgid}" ] && kill -TERM -- -"${pgid}" 2>/dev/null
  else
    printf "\r\033[K\033[32m✓\033[0m Done!\033[K\n" >&2
  fi

  # a separate recovery cursor is no longer required because the exit trap is handled
}

# main function
function main() {
  tmpfile=$(mktemp)
  trap 'rm -f "${tmpfile}"' EXIT

  withSpinner "Loading..." result sleep 5
  echo "Exit code: $?"
}

main "$@"

# vim:tabstop=2:softtabstop=2:shiftwidth=2:expandtab:filetype=sh:

save & restore screen

reference

tput

  • clear

    $ tput smcup
  • restore

    $ tput rmcup

echo

  • save

    $ echo -e '\033[?47h'
  • restore

    $ echo -e '\033[?47l'

terminfo escape sequences

$ infocmp
  ...
  colors#256, cols#80, it#8, lines#24, pairs#32767,
  bel=^G, blink=\E[5m, bold=\E[1m, cbt=\E[Z, civis=\E[?25l,
  clear=\E[H\E[2J, cnorm=\E[?12l\E[?25h, cr=^M,
  ...

tput

reset terminal

[!NOTE]

$ reset
# or
$ stty sane

clear screen

$ tput home

# or
$ tput cup %py %px
# or
$ tput cup %py %px >/dev/null

show term

$ tput color

show terminal width

$ tput cols

references:

$ export GREP_COLORS="sl=0;33;49:ms=1;34;49"
$ find /etc/ -type f | head | grep --color=always '^\|[^/]*$'

Operation not permitted

[!NOTE|label:references:]

  • mac equivalent

    • /bin/ls -lO

      $ /bin/ls -lO
      total 288
      -rwxr-xr-x@  1 marslo  staff  compressed,dataless 1116834851 Feb 21 17:00 Ubuntu2204-221101.AppxBundle
$ sudo lsattr /etc/resolv.conf
----i-------------- /etc/resolv.conf
$ sudo rm -rf /etc/resolv.conf
rm: cannot remove '/etc/resolv.conf': Operation not permitted

# solution
$ sudo chattr -i /etc/resolv.conf
$ sudo lsattr /etc/resolv.conf
------------------- /etc/resolv.conf
$ sudo mv /etc/resolv.conf{,.bak}

# revert back
$ sudo chattr +i /etc/resolv.conf
$ sudo lsattr /etc/resolv.conf
----i-------------- /etc/resolv.conf

array

differences in bash parameter calls

local fdOpt="--type f --hidden --follow --unrestricted --ignore-file $HOME/.fdignore"
local ignores=(
  '*.pem' '*.p12'
  '*.png' '*.jpg' '*.jpeg' '*.gif' '*.svg'
  '*.zip' '*.tar' '*.gz' '*.bz2' '*.xz' '*.7z' '*.rar'
  'Music' '.target_book' '_book' 'OneDrive*'
)
while read -r pattern; do fdOpt+=" --exclude '${pattern}'"; done <<< "$(printf '%s\n' "${ignores[@]}")"
fdOpt+=' --exec-batch ls -t'
  • the --exclude options are not passed correctly when using :

fd . ${fdOpt} | fzf ${foption} --bind="enter:become(${VIM} {+})"
  • but it works when using eval :

eval "fd . ${fdOpt}" | fzf ${foption} --bind="enter:become(${VIM} {+})"

[!TIP|label:tips:]

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.

# array
local -a fdArgs=(--type f --hidden --follow --unrestricted --ignore-file "${HOME}/.fdignore")
local ignores=(
  '*.pem' '*.p12'
  '*.png' '*.jpg' '*.jpeg' '*.gif' '*.svg'
  '*.zip' '*.tar' '*.gz' '*.bz2' '*.xz' '*.7z' '*.rar'
  'Music' '.target_book' '_book' 'OneDrive*'
)

for pattern in "${ignores[@]}"; do fdArgs+=(--exclude "${pattern}"); done
fdArgs+=(--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 bash

set -euo pipefail

function plainString() {
  local cmd="echo Listing *.txt with excludes: --exclude '*.log' --exclude '*.bak'"
  echo "→ Running: $cmd"
  $cmd
}

function evalString() {
  local cmd="echo Listing *.txt with excludes: --exclude '*.log' --exclude '*.bak'"
  echo "→ Running with eval: $cmd"
  eval "$cmd"
}

function arrayCall() {
  local -a cmd=("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

[!NOTEthinking:] I got a issue with/without eval commands like:

🏷️
How to add a progress bar to a shell script?
colorful output : c()
c() can be also found in .marslorc
with ▉ ▎ ▌ ▊
A progress bar for the shell
with [###----]
fearside/ProgressBar
with |\|/
Integralist/python progress bar.py
* iMarslo: gist/_spinner.md
unicode - Braille Patterns
wikipedia - Braille Patterns
SamEureka/spinner.sh
Braille Patterns - U2800.pdf
with stdout
* iMarslo: ccm.sh - copilot commit message
with exitcode
Terminal codes (ANSI/VT100) introduction
Shell does not show typed-in commands, what do I do to fix it?
customized colorful output
imarslo: highlight output
How to Fix 'rm: cannot remove '/etc/resolv.conf': Operation not permitted'
Can not edit resolv.conf
Un-removable /etc/resolv.conf
chflags
process bar
with dot .
with ▉ ▎ ▌ ▊
with [###----]
with |\|/
Integralist/python progress bar.py
spinner
Braille Patterns
others
bash script
save & restore screen
tput
echo
terminfo escape sequences
tput
reset terminal
clear screen
show term
show terminal width
customized colorful output
Operation not permitted
array
differences in bash parameter calls
wildcard expansion
waiting bar with dot
progress bar with ▎▌ ▊ ▉
progress bar with [###----]
progress bar with |\|/
customized color output
Braille 8 dot Cell Numbering