Shloader - A Modern Shell Loader

Shloader

  1. Why
  2. Features
  3. Preview
  4. Templating
  5. Usage
  6. Parsing Arguments
  7. Shell Configurations
  8. Trap Error and Exit
  9. Display Loader
  10. Call Loader
  11. Scrip Library Integration
  12. Conclusion

Why

I’ve been recently doing some old shell scripts to quickly automatize and distribute actions that were made manually, I’ll write a post on it later. For now I’d like to share with you how I’ve been coding a 100% shell loader library to use it in my scripts.

I’ve been looking on the net for existing library, I’ve found some interesting Github projects but it doesn’t appear to be “library” ready. Furthermore, it was mostly old basic ASCII templates and Iย wanted to give a try to modern loaders and spinners on shell.

So I ended create shloader.

Features

shloader has nice features such as :

๐Ÿ˜ emoji support

๐Ÿ’ช loader support

๐Ÿ˜Ž dynamic message on load step

โ„น๏ธ message on step ending

๐ŸŽจ multiple loading templates

๐Ÿ‘Œ light and easy to use on existing scripts

Preview

Shloader

Templating

First of all, we need to create templates, one for each loaders that will be displayed. I’ve decided to use shell array so we can work on iteration later on customs functions.

So I ended with something like :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# EMOJIS
emoji_hour=( 0.08 '๐Ÿ•›' '๐Ÿ•' '๐Ÿ•‘' '๐Ÿ•’' '๐Ÿ•“' '๐Ÿ•”' '๐Ÿ••' '๐Ÿ•–' '๐Ÿ•—' '๐Ÿ•˜' '๐Ÿ•™' '๐Ÿ•š')
emoji_face=( 0.08 '๐Ÿ˜' '๐Ÿ˜€' '๐Ÿ˜' '๐Ÿ™„' '๐Ÿ˜’' '๐Ÿ˜จ' '๐Ÿ˜ก')
emoji_earth=( 0.1 ๐ŸŒ ๐ŸŒŽ ๐ŸŒ )
emoji_moon=( 0.08 ๐ŸŒ‘ ๐ŸŒ’ ๐ŸŒ“ ๐ŸŒ” ๐ŸŒ• ๐ŸŒ– ๐ŸŒ— ๐ŸŒ˜ )
emoji_orange_pulse=( 0.1 ๐Ÿ”ธ ๐Ÿ”ถ ๐ŸŸ  ๐ŸŸ  ๐Ÿ”ถ )
emoji_blue_pulse=( 0.1 ๐Ÿ”น ๐Ÿ”ท ๐Ÿ”ต ๐Ÿ”ต ๐Ÿ”ท )
emoji_blink=( 0.06 ๐Ÿ˜ ๐Ÿ˜ ๐Ÿ˜ ๐Ÿ˜ ๐Ÿ˜ ๐Ÿ˜ ๐Ÿ˜ ๐Ÿ˜ ๐Ÿ˜ ๐Ÿ˜‘ )
emoji_camera=( 0.05 ๐Ÿ“ท ๐Ÿ“ท ๐Ÿ“ท ๐Ÿ“ท ๐Ÿ“ท ๐Ÿ“ท ๐Ÿ“ท ๐Ÿ“ท ๐Ÿ“ท ๐Ÿ“ท ๐Ÿ“ท ๐Ÿ“ท ๐Ÿ“ท ๐Ÿ“ท ๐Ÿ“ท ๐Ÿ“ท ๐Ÿ“ท ๐Ÿ“ท ๐Ÿ“ท ๐Ÿ“ท ๐Ÿ“ธ ๐Ÿ“ท ๐Ÿ“ธ )
emoji_sick=( 0.2 ๐Ÿคข ๐Ÿคข ๐Ÿคฎ )
emoji_monkey=( 0.2 ๐Ÿ™‰ ๐Ÿ™ˆ ๐Ÿ™Š ๐Ÿ™ˆ )
emoji_bomb=( 0.2 '๐Ÿ’ฃ   ' ' ๐Ÿ’ฃ  ' '  ๐Ÿ’ฃ ' '   ๐Ÿ’ฃ' '   ๐Ÿ’ฃ' '   ๐Ÿ’ฃ' '   ๐Ÿ’ฃ' '   ๐Ÿ’ฃ' '   ๐Ÿ’ฅ' '    ' '    ' )
# ASCII
ball=( 0.2 '(โ—)' '(โšฌ)')
arrow=( 0.06 'โ†‘' 'โ†—' 'โ†’' 'โ†˜' 'โ†“' 'โ†™' 'โ†' 'โ†–')
cym=( 0.1 'โŠ' 'โŠ“' 'โŠ' 'โŠ”')
x_plus=( 0.08 'ร—' '+')
line=( 0.08 'โ˜ฐ' 'โ˜ฑ' 'โ˜ณ' 'โ˜ท' 'โ˜ถ' 'โ˜ด')
ball_wave=( 0.1 '๐“ƒ‰๐“ƒ‰๐“ƒ‰' '๐“ƒ‰๐“ƒ‰โˆ˜' '๐“ƒ‰โˆ˜ยฐ' 'โˆ˜ยฐโˆ˜' 'ยฐโˆ˜๐“ƒ‰' 'โˆ˜๐“ƒ‰๐“ƒ‰')
old=( 0.07 'โ€”' "\\" '|' '/' )
dots=( 0.04 'โฃพ' 'โฃฝ' 'โฃป' 'โขฟ' 'โกฟ' 'โฃŸ' 'โฃฏ' 'โฃท' )
dots2=( 0.04 'โ ‹' 'โ ™' 'โ น' 'โ ธ' 'โ ผ' 'โ ด' 'โ ฆ' 'โ ง' 'โ ‡' 'โ ' )
dots3=( 0.04 'โ ‹' 'โ ™' 'โ š' 'โ ž' 'โ –' 'โ ฆ' 'โ ด' 'โ ฒ' 'โ ณ' 'โ “' )
dots4=( 0.04 'โ „' 'โ †' 'โ ‡' 'โ ‹' 'โ ™' 'โ ธ' 'โ ฐ' 'โ  ' 'โ ฐ' 'โ ธ' 'โ ™' 'โ ‹' 'โ ‡' 'โ †' )
dots5=( 0.04 'โ ‹' 'โ ™' 'โ š' 'โ ’' 'โ ‚' 'โ ‚' 'โ ’' 'โ ฒ' 'โ ด' 'โ ฆ' 'โ –' 'โ ’' 'โ ' 'โ ' 'โ ’' 'โ “' 'โ ‹' )
dots6=( 0.04 'โ ' 'โ ‰' 'โ ™' 'โ š' 'โ ’' 'โ ‚' 'โ ‚' 'โ ’' 'โ ฒ' 'โ ด' 'โ ค' 'โ „' 'โ „' 'โ ค' 'โ ด' 'โ ฒ' 'โ ’' 'โ ‚' 'โ ‚' 'โ ’' 'โ š' 'โ ™' 'โ ‰' 'โ ' )
dots7=( 0.04 'โ ˆ' 'โ ‰' 'โ ‹' 'โ “' 'โ ’' 'โ ' 'โ ' 'โ ’' 'โ –' 'โ ฆ' 'โ ค' 'โ  ' 'โ  ' 'โ ค' 'โ ฆ' 'โ –' 'โ ’' 'โ ' 'โ ' 'โ ’' 'โ “' 'โ ‹' 'โ ‰' 'โ ˆ' )
dots8=( 0.04 'โ ' 'โ ' 'โ ‰' 'โ ™' 'โ š' 'โ ’' 'โ ‚' 'โ ‚' 'โ ’' 'โ ฒ' 'โ ด' 'โ ค' 'โ „' 'โ „' 'โ ค' 'โ  ' 'โ  ' 'โ ค' 'โ ฆ' 'โ –' 'โ ’' 'โ ' 'โ ' 'โ ’' 'โ “' 'โ ‹' 'โ ‰' 'โ ˆ' 'โ ˆ' )
dots9=( 0.04  'โขน' 'โขบ' 'โขผ' 'โฃธ' 'โฃ‡' 'โกง' 'โก—' 'โก' )
dots10=( 0.04  'โข„' 'โข‚' 'โข' 'โก' 'โกˆ' 'โก' 'โก ' )
dots11=( 0.04 'โ ' 'โ ‚' 'โ „' 'โก€' 'โข€' 'โ  ' 'โ ' 'โ ˆ' )

Array composition is divided in two parts, the first one is a time interval in seconds and the second one is the loader frame transition.

Usage

I wanted library to be used as commands can be, by passing argument through options.

First, let’s work on default usage function to display to user.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
usage() {
  cat <<EOF
Available options:
-h, --help            <OPTIONAL>    Print this help and exit
-l, --loader          <OPTIONAL>    Chose loader to display
-m, --message         <OPTIONAL>    Text to display while loading
-e, --ending          <OPTIONAL>    Text to display when finishing
EOF
  exit 0
}

I wanted to define argument options as bellow :

Display help

1
2
# e.g
shloader -h
ParameterTypeDescription
-h --helpnoneOptional. Show help usage

Chose loader

1
2
# e.g
shloader -l arrow
ParameterTypeDescription
-l --loaderstringOptional. Chose loader template

Display info on loading

1
2
# e.g
shloader -m "my loading message" 
ParameterTypeDescription
-m --messagestringOptional. Show a text message while displaying loader

Display info on ending

1
2
# e.g
shloader -e "\u2728 all done"
ParameterTypeDescription
-e --endstringOptional. Show an end text message when loader ends

Parsing Arguments

In order to work on user input, it is necessary to read option content so we can modular library execution.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
shloader() {
  loader=''
  message=''
  ending=''

  while :; do
    case "${1-}" in
    -h | --help) usage;;
    -l | --loader)
      loader="${2-}"
      shift
      ;;
    -m | --message)
      message="${2-}"
      shift
      ;;
    -e | --ending)
      ending="${2-}"
      shift
      ;;
    -?*) die "Unknown option: $1" ;;
    *) break ;;
    esac
    shift
  done

[โ€ฆ]
}

You might notice I’ve placed it directly in the main function so it will be the first element to be run.

If user don’t specify loader, we should display one as default, let’s say dots one.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# shloader func
[โ€ฆ]

  if [[ -z "${loader}" ]] ; then
    loader=dots[@] 
  else
    loader=$loader[@]
  fi

[โ€ฆ]

One last thing here, I use a little custom die function to exit properly if option is unknown.

1
2
3
4
die() {
  local code=${2-1}
  exit "$code"
}

Shell Configurations

We will make some quick configuration on the top header script under shebang.

1
2
3
4
5
6
#!/bin/bash
# https://github.com/lebaronlebaron.shloader
# me@lebaron.sh
set -Eeuo pipefail
trap end_shloader SIGINT SIGTERM ERR EXIT RETURN
tput civis

First, set -Eeuo pipefail so we can exit execution if one of the commands in the pipe fails.

Then, trap end_shloader SIGINT SIGTERM ERR EXIT RETURN so we can call a custom function to clean our execution on exiting, stopping, errorsโ€ฆ

Finaly, tput civis is used to hide cursor.

Trap Error and Exit

Trap will call a custom function, but how does it work ?

1
2
3
4
5
6
7
end_shloader() {
  kill "${shloader_pid}" &>/dev/null
  tput cnorm
  if [[ "${ending}" ]]; then
    printf "\r${ending}"; echo
  fi
}

Right here I ensure first to kill loader’s PID and restore cursor thanks to tput cnorm.

Display Loader

Now we can interact with shell output to generate loading animations.

Let’s create a new function play_shloader() :

1
2
3
4
5
6
7
8
play_shloader() {
  while true ; do
    for frame in "${loader[@]}" ; do
      printf "\r%s" "${frame} ${message}"
      sleep "${speed}"
    done
  done
}

Here we assume we have loader array we can display during time duration defined in speed variable (we will see it after).

Call Loader

What about now ? We have all bricks to make great loaders, let’s put them in work together !

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# shloader function
[โ€ฆ]

  loader=( ${!loader} )
  speed="${loader[0]}"
  unset "loader[0]"

  tput civis
  play_shloader &
  shloader_pid="${!}"

[โ€ฆ]

What is done here ?

We first print loader array content we save in loader variable. We know first element in array is time duration, so we save it in speed variable.

Now we have split array, we can remove time duration as it is now under speed variable. We then hide cursor, call our play_shloader function and save PID.

You can find the Full Library Code

Script Library Integration

Nothing hard here !

1
2
3
4
5
6
7
8
source ./lib/shloader.sh

shloader -l emoji_hour -m "Testing" -e "โœจ All good !" 

  sleep 2   # remove it in your code
  # โ€ฆ your logic goes here

end_shloader

With more details :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
!/bin/bash

# if you want to try just add this block code in your code 
source ./lib/shloader.sh

# you can chose (see more in lib/loader.sh):
# ball, arrow, cym, x_plus, line, ball_wave, npm and old.
# you can specify message to display during loading
# and message to display after your code finished

# eg with npm style
# notice end message -e use unicode emoji to display
# this is for better terminal support
# \u2728 == โœจ but you can use emoji if your settings support it !
shloader -l emoji_face -m "Testing" -e "\u2728 All good !" 
  sleep 2   # remove it in your code

  # โ€ฆ your logic goes here
  # if you want to hide some output from loader
  # don't forget to redirect your STD*
  #
  # eg :
  # STDOUT
  # my_cmd 1> /dev/null
  # STDERR
  # my_cmd 2> /dev/null
  # BOTH
  # my_cmd &> /dev/null

# stop loader
end_shloader

Conclusion

As all things uselessโ€ฆ it may become mandatory ๐Ÿ˜‚.