8000 GitHub - iusmac/boxlib: Pure Bash library for building portable TUI apps with Whiptail/Dialog
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

iusmac/boxlib

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

76 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

boxlib Latest Tag License

boxlib is a simple, pure Bash library that provides a collection of APIs for creating TUI (terminal user interface) applications, such as installers, rescue disks or interactive scripts. It supports both Whiptail and Dialog as backends for rendering the boxes on the screen.

Demo tape

Features & Advantages

  • 🧠 Simple, Practical & Highly Intuitive
    As a library, boxlib lets the developers to write less boilerplate code and more business logic. Moreover, it also removes needless complexity, and bridges the gap between Dialog and Whiptail.

  • 🌟 Feature Emulation: Bring Dialog's Exclusive Features to Whiptail
    Whiptail is designed to be drop-in compatible with Dialog, but it lacks several features compared to Dialog (e.g., tailbox, timebox, calendarbox, etc.).
    boxlib "backports" all of the missing boxes into Whiptail by emulating them using existing functionality.
    For example, the calendar box is emulated using an input field with validation. The file/directory selector box and form are emulated using the menu box.

  • 🧩 Renderer Compatibility
    boxlib aims to be fully compatibile with both Whiptail & Dialog, and offer a consistent and unified interface with no need to adapt code per backend.

  • 📢 Communication Between Boxes
    In a multi-level hierarchy, the boxes may need to communicate with each other. For that, the library puts at your disposal a built-in communication mechanism, which offers

    • Basic Callback Support

      You can attach callbacks (functions, scripts or executables) to individual boxes and capture their result and the status code in a completely isolated way. There are various options to configure the behavior of the callback system, such as abortOnRendererFailure to fail-fast the whole callback chain and cause the app to exit.

    • Callback Status Code Propagation Support

      Sometimes, you want to decide in the parent box (e.g., main menu) the specific behavior (e.g., remove entry from menu) depending on state of the child box. For that, you'll have the status code propagation mechanism that allows you to pass the exit code from the child box through the entire callback chain, up to the parent box.

    • Callback Environment Propagation Support

      Since Bash's scoping mechanism makes local variables behave mostly like actual global variables, it may cause variable pollution and collisions. To address that, boxlib runs all the callbacks in a sandboxed environment (sub-shell). Unfortunately, variable changes won't be visible in the parent. For that, you'll have the callback environment propagation mechanism, that migrates the exported variables to the parent's environment variable space, ensuring the communication between the boxes is always isolated.

  • 🧭 Breadcrumb Navigation
    The library implements a breadcrumb stack displayed at the top of the screen to help maintain context when exploring multi-level box hierarchies.

  • ⚙️ Global Configuration Per Session
    The library is globally configurable (e.g., select renderer) using config component. It's also possible to set common box options (e.g., maximize all boxes) once at startup or elsewhere during execution.

  • 🎯 Smart Renderer Selection
    The library will automatically decide which utility will be used to render boxes when it's imported (sourced) into your project. Dialog is preferred, if available in the system, otherwise, it falls back to Whiptail. When neither is found, a panic error is thrown.

  • 🖥️ Cross-platform Compatibility
    The library attempts to be POSIX-compliant in order to offer the same experience across Linux, *BSD and other systems. Tested on the following systems upon release:

    • Ubuntu 22.04
    • FreeBSD 14.2
    • macOS Sonoma (version 14)
  • ...and many more!
    Underneath, the library already includes many things what you'll have to write anyway, so you can focus on your app's logic.

    • Want to easily loop a box (e.g. a menu) until user exits? Just set loop=true option!
    • Need the box size to be 50% of the terminal size? Set width=50% and height=50% options to dynamically scale with the terminal!

Demo

Try the demo/main.sh script to explore all available box/widget types. You can also run each example individually from the demo/ directory if you want.

Tip

Since Dialog is the preferred renderer, you can force Whiptail by setting the BOXLIB_USE_WHIPTAIL=1 environment variable:

BOXLIB_USE_WHIPTAIL=1 ./demo/main.sh 1 # or BOXLIB_USE_WHIPTAIL=1 bash demo/menu.sh

Example

Example of a menu box created with Whiptail and with boxlib.

F438
Whiptail-onlythe equivalent with boxlib

Demo main menu with Whiptail-only Demo main menu with boxlib

declare -a options=(
  '1) Option 1' 'Summary 1'
  '2) Option 2' ''
)
# conditionally hide option
if false; then
  options+=('3) Option 3' 'Summary 3')
fi
while true; do
  if ! result="$(whiptail \
    --backtitle 'My Project' \
    --title 'Main menu'  \
    --cancel-button 'Exit' \
    --menu 'Choose an option' \
    0 0 0 \
    "${options[@]}" \
    3>&1 1>&2 2>&3)"; then
    break
  fi
  # Get rid of the enumeration part
  option="${result#* }"
  case "$result" in
    1*)
      myCallBack1
      ;;
    2*)
      source path/to/callback.sh "$option"
      ;;
    3*)
      myCallBack3
      ;;
    *)
      echo "Unhandled option: $option"
      exit 1
      ;;
  esac
done
source boxlib/core.sh

config headerTitle='My Project'

function menu_handler() {
  local option="$1"
  case "$option" in
    'Option 1') myCallBack1;;
    'Option 3') myCallBack3;;
  esac
}

menu \
  title='Main menu' \
  text='Choose an option' \
  cancelLabel='Exit' \
  callback='menu_handler()' \
  loop='true' \
  prefix='num'

menuEntry \
  title='Option 1' \
  summary='Summary 1'

menuEntry \
  title='Option 2' \
  callback='path/to/callback.sh'

# conditionally hide option
if false; then
  menuEntry \
    title='Option 3' \
    summary='Summary 3'
fi

menuDraw

Note

You might wonder why the above example is Whiptail-only, as the same arguments can be used in Dialog to create a menu box. It's true, but overall behavior will be slightly different. For example, when running with Whiptail, the terminal will be cleared automatically after exiting. Dialog, in turn, requires the --keep-tite argument to accomplish this. The boxlib will handle this and bridge many other small gaps between the two for you.

See more demos examples here.

Requirements

Note

Both Whiptail and Dialog utilites are pre-installed on most modern systems. Whiptail is available by default on Debian-based systems as part of the Newt package, while *BSD ones provide Dialog or BSDDialog. boxlib automatically detects and selects the appropriate backend when it's imported (sourced).

  • Bash v4.3 (Bash v4.4 recommended)

Tip

While Bash v4.3 is fully supported, certain distro-specific builds and patch versions (Bash v4.3.48 is the latest) may contain known bugs, that were fixed only in Bash v4.4.

Installation Instructions

  1. Create a Git project

    git init
  2. Clone this repo into your project using one of the following ways:

    • As submodule
      git submodule add --depth=1 https://github.com/iusmac/boxlib.git

      If you configure your project using boxlib as submodule, then you and others should it clone with the --recursive option:

      git clone --recursive https://github.com/.../YourProject.git

      It is necessary to hook the boxlib module from .gitmodules file during cloning.

    • As subtree
      git subtree add --prefix=boxlib https://github.com/iusmac/boxlib.git master --squash

    Submodule/Subtree Cheatsheet

  3. Create an entrypoint script file (will name it main.sh) in the project root directory & make it executable

    touch main.sh && chmod +x $_

    3.1. Import (source) the library core component at the very top of the entrypoint (main.sh) file

    #!/usr/bin/env bash
    
    source boxlib/core.sh
  4. That's it!

    See the Example section on how to create your first main menu box.
    For the components provided by the library, refer to the Components section.

Getting updates

To get the latest library updates, run the appropriate command in the project root directory:

  • If boxlib is added as submodule

    git submodule update --remote --recursive --force boxlib

    This command selectively updates only the submodule directory where the library is located. You may want to commit the changes.

  • If boxlib added as subtree

    git subtree pull --prefix=boxlib https://github.com/iusmac/boxlib.git master --squash

Submodule/Subtree Cheatsheet

Components

Below is the list of core components that will be available to you anywhere after the library has been imported (sourced). You can also get the usage screen by invoking the command with help argument.

Config

Component to globally configure the library and set common box options.

Note

  • Config changes affect only future boxes.
  • Box-specific or Whiptail/Dialog-specific options take precedence over options set via config.

List of commands:

config <options> [<box-options>]

Example usage:

# Set a custom breadcrumb delimiter and maximize all boxes.
# Also pass the --fb option to all boxes for full buttons
config \
    breadcrumbsDelim='' \
    width='max' \
    height='max' \
    \( --fb \)
text text='This text box should be fully maximized'
text text='This text box should be auto-sized to fit the contents' width='auto' height='auto'

Option Default Description
headerTitle=
headerTitle+=
"" # The string to be displayed on the backdrop, at the top of the screen alongside the breadcrumbs.
rendererPath= dialog # The absolute path to Dialog or Whiptail binary that will render the boxes.
rendererName= dialog # The renderer name when renderer binary is specified.
Possible values: dialog, whiptail.
breadcrumbsDelim=  >  # Set the breadcrumb delimiter string. Can by any length.
debug=
debug
/dev/null # The file name where to write debug logs. This option takes precedence over the BOXLIB_DEBUG environment variable.
When no value is provided, will exit with 0 or 1 indicating the enabled debug state.
Possible values
stdoutprints debug to the standard output
stderrprints debug to the standard error
<filename>writes debug to the given filename.
isDialogRenderer # Check whether Dialog is the default renderer. Will print true or false and also exit with 0 or 1.
reset # Reset the configuration to defaults.
help # Print the usage screen and exit.

Box

Below is the list of box components that allow you to easily launch any Dialog/Whiptail box. Each box can be configured using two different types of the arguments:

  • library-specific options
  • Whiptail/Dialog-specific options

Note

The Whiptail/Dialog-specific options use the special round bracket syntax to pass options to the box. For example:

# Create an input box with a custom backtitle in lieu of header title & breadcrumb stack
input \
  text='My input' \
  \( --backtitle 'My custom backtitle' \)
# The equivalent with Dialog
dialog \
  --backtitle 'My custom backtitle' \
  --inputbox 'My input' 0 0

See also the Communication between boxes section on how to interact with boxes and different ways to capture the result.

Common options

Each box may have its own specific options, but all boxes share the following common options:

Option Default Description
title=
title+=
"" # The string that is displayed at the top of the box.
Same as using Whiptail/Dialog's --title option.
text=
text+=
"" # The string that is displayed inside the box.
width= auto
also requires height='auto'
# The width of the box.
Use auto or 0 to auto-size to fit the contents. Use max or -1 to maximize.
Can be denoted using percent sign, (e.g., 50%), to adjust dynamically based on the tput cols command.
height= auto
also requires width='auto'
# The height of the box.
Use auto or 0 to auto-size to fit the contents. Use max or -1 to maximize.
Can be denoted using percent sign, (e.g., 50%), to adjust dynamically based on the tput lines command.
callback= # The callback to receive the result(s) from the box. The callback will be:
  • invoked as a local function if it ends with ()
  • executed if it's a file with the "execute" bit set
  • sourced as a shell script file if it's none of the above
In all cases, the callback should expect the result(s) as input parameters. When executed, the $? variable will contain the exit code from the renderer (Whiptail/Dialog).

NOTE:
  • The callback execution will be sandboxed, i.e., it will run in a sub-shell. This ensures the interaction is isolated.
  • If the callback is a relative path to a file, then it will be searched starting from the working directory.
    Also, the CWD will be changed to where the callback file is located before executing/sourcing it. To disable, set changeToCallbackDir=false.
See All Options
Option Default Description
changeToCallbackDir= true # Whether to change the working directory to where the callback script/executable is located before executing/sourcing it.
Possible values: true (or 1), false (or 0).
abortOnCallbackFailure= false # Whether to abort immediately when the callback exits with a non-zero code.
This will cause the whole callback chain to be interrupted including this box.
For example, if applied on the root box that is the entry point for all boxes (e.g., main menu), then the app will exit. Useful for debugging/development purposes or in combination with loop=true option.
Possible values: true (or 1), false (or 0).
propagateCallbackExitCode= true # Whether to propagate the callback exit code instead of the renderer exit code when the box exits.
Also, if no callback is provided, the renderer exit code will be used.
Possible values: true (or 1), false (or 0).
alwaysInvokeCallback= false # Whether to invoke the callback even if the renderer (Whiptail/Dialog) exited with a non-zero code.
A non-zero code means the user pressed ESC key or Cancel button or answered No.
When set to true, the callback will always be invoked, and the $? variable variable will contain the exit code from the renderer.
Possible values: true (or 1), false (or 0).
printResult= false # Whether to print the result(s) to stdout, each on a new line, after the box exits.
Possible values: true (or 1), false (or 0).
abortOnRendererFailure= false # Whether to abort immediately when the box renderer (Whiptail/Dialog) exits with a non-zero code AND with an error message printed to the standard error. No callbacks will be invoked even if alwaysInvokeCallback option has been used.
Useful to fail-fast on renderer errors, such as invalid options.
You may also want to combine it with the abortOnCallbackFailure=true option, to cause the whole callback chain, if any, to be interrupted.
Possible values: true (or 1), false (or 0).
loop= false # Whether to loop the box until it exits with a non-zero code. Mainly useful for menus or when used in combination with abortOnCallbackFailure=true to control the loop granularly.
Possible values: true (or 1), false (or 0).
hideBreadcrumb= false # Whether to hide the box from the breadcrumbs stack displayed at the top of the screen.
Possible values: true (or 1), false (or 0).
sleep= # Sleep (delay) for the given number of seconds after the box exits with a zero code.
Useful when a pause is needed before displaying the next box.
NOTE: this is not the same as using Dialog's --sleep option. Instead, the sleep command will be used after the box has been cleared.
timeout= # Timeout (exit with error code) if no user response within the given number of seconds. Same as using Dialog's --timeout option. This behavior is replicated internally by the library when using Whiptail.
A timeout of zero seconds is ignored and the decimal part will be dropped.
term= $TERM # The value for the $TERM variable that will be used to render this box.
( --opt val ... ) # The special round bracket syntax to pass Whiptail/Dialog-specific options to the box, as if used on the command line. It can be used multiple times in any order.
NOTE: since parentheses are special to the shell, you will usually need to escape or quote them.
Example usage:
text title='Welcome' text='Hello World!' \( --backtitle 'My backtitle' --fb \)
yesLabel= # Set the text of the Yes button.
Same as using Whiptail/Dialog's --yes-button/--yes-label option.
noLabel= # Set the text of the No button.
Same as using Whiptail/Dialog's --no-button/--no-label option.
okLabel= # Set the text of the Ok button.
Same as using Whiptail/Dialog's --ok-button/--ok-label option.
cancelLabel= # Set the text of the Cancel button.
Same as using Whiptail/Dialog's --cancel-button/--cancel-label option.
scrollbar= false # Whether to force the display of a vertical scrollbar.
Same as using Whiptail/Dialog's --scrolltext/--scrollbar option.
Possible values: true (or 1), false (or 0).
topleft= false # Whether to put window in top-left corner.
Same as using Whiptail/Dialog's --topleft/--begin 0 0 option.
Possible values: true (or 1), false (or 0).
help # Print the usage screen and exit.

Tip

The += operator will concatenate with the previous <option>= value, e.g.,

info \
  title='My very long ' \
  title+='box title'

Calendar

Component to set up a new calendar box and perform drawing to the terminal. Corresponds to the --calendar argument in Dialog.

Note

In Whiptail, this feature is emulated using an input field with validation. The output (as well as the input) defaults to the format dd/mm/yyyy.

Demo | Show example code

Demo calendar box (Dialog) Demo calendar box (Whiptail)

List of commands:

calendar <options>

Sets up a new calendar box and draws it.

Option Default Description
day= (current day) # The day of the calendar.
Example: 25.
month= (current month) # The month of the calendar.
Example: 12.
year= (current year) # The year of the calendar.
Example: 2024.
dateFormat= %d/%m/%Y # The format of the outputted date string.
NOTE: in Dialog, the --date-format option will be used, which relies on strftime, whereas in Whiptail, the date command will be used. So, there may be slight differences in the format specifiers.
forceInputBox= false # Whether to force use of the input box instead of the Dialog's calendar.
Possible values: true (or 1), false (or 0).

See Common Options


Confirm

Component to set up a new confirm box and perform drawing to the terminal. Corresponds to the --yesno argument in Dialog/Whiptail.

Demo | Show example code

Demo confirm box (Dialog) Demo confirm box (Whiptail)

List of commands:

confirm <options>

Sets up a new confirm box and draws it.

See Common Options


Edit

Component to set up a new edit box and perform drawing to the terminal. Corresponds to the --editbox argument in Dialog.

Note

In Whiptail, unless editor option is provided, the default editor will be used instead.

Demo | Show example code

Demo edit box (Dialog) Demo edit box (Whiptail)

List of commands:

edit <options>

Sets up a new edit box and draws it.

Option Default Description
file=
file+=
# The path to a file whose contents to edit.
editor=
editor+=
# The text editor to use. If an empty string is provided, the "good" default text editor will be determined via Debian's sensible-editor helper command. Otherwise, the editor from the $EDITOR or $VISUAL environment variables, or one of nano, nano-tiny, or vi.
This option can be used with Dialog as well to replace the edit box.
inPlace= false # Whether to edit the file in place after the box/editor exits.
Possible values: true (or 1), false (or 0).

See Common Options


Form

Component to set up a form box and perform drawing to the terminal. Corresponds to the --form, --mixedform & --passwordform arguments in Dialog.

Note

In Whiptail, this feature is emulated using a normal menu and an input box to edit the fields.

Tip

Use up/down arrows (or control/N, control/P) to move between fields.

Unless a callback is specified via callback option on the box, on exit, the contents of the form-fields are written to the standard output, each field separated by a newline. The text used to fill non-editable fields (width is zero or negative) is not written out.

Demo | Show example code

Demo form box (Dialog) Demo form box (Whiptail)

List of commands:

form <options>

Sets up a new form box.

Option Default Description
formHeight= auto # The form height, which determines the amount of rows to be displayed at one time, but the form will be scrolled if there are more rows than that.
Use auto or 0 to auto-size to fit the contents.
Can be denoted using percent sign, (e.g., 50%), to adjust dynamically based on the amount of rows.
columns= 1 # The number of columns per row before wrapping fields to a new line. Using this, will effectively lay out, align, and distribute fields within the Dialog's window.
  • If set to 0, all the fields should be manually positioned using coordinates.
  • If set to 1 or higher, fields will be arranged automatically. The individual field coordinates can still be overridden.
fieldWidth= # The default field width.
fieldMaxLength= # The default permissible length of the data that can be entered in the fields.
[ opt=val ... ] # The special square bracket syntax to add form-fields, which accepts formField options as-is. For example:
form title='My form' [ title='First Name' width=10 ] [ ... ]

See Common Options

formField <options>

Adds a form entry. This can only be used after the form box has been set up. Fields will appear in the form in the order they are added.

Option Default Description
type= input # The type(s) of the field. The field can combine multiple types, for example: hidden|readonly.
Possible values
inputA standard input field that allows to enter and edit text
hiddenA field that stores sensitive data not visible to the user,
like passwords
readonlyA non-editable field that displays information to the
user, similar to a label
title=
title+=
"" # The title/label of the field.
value=
value+=
"" # The initial value of the field.
width= 0 # The width of the field that determines the number of characters that the user can see when editing the value.
  • If width value is 0, the field cannot be altered, and the contents of the field determine the displayed-length.
  • If width value is negative, the field cannot be altered, and the negated value is used to determine the displayed-length.
maxlength= 0 # The permissible length of the data that can be entered in the field. When value is 0, the value of width is used instead.
titleX= # The x-coordinate of the field's title within the form's window.
This is automatically computed if columns option is > 0, and a value starting with + or - can be used to adjust (increase or decrease) the automatically computed title's position by that amount.
The unit of measurement is terminal columns.
titleY= # The y-coordinate of the field's title within the form's window.
This is automatically computed if columns option is > 0, and a value starting with + or - can be used to adjust (increase or decrease) the automatically computed title's position by that amount.
The unit of measurement is terminal rows.
valueX= # The x-coordinate of the field's value within the form's window.
Typically, you want it to be ${#title} + 2 (+2 is for padding) in order for it to be positioned next to the title.
This is automatically computed if columns option is > 0, and a value starting with + or - can be used to adjust (increase or decrease) the automatically computed value's position by that amount.
The unit of measurement is terminal columns.
valueY= # The y-coordinate of the field's value within the form's window.
Typically, you want it to be the same as titleY.
This is automatically computed if columns option is > 0, and a value starting with + or - can be used to adjust (increase or decrease) the automatically computed value's position by that amount.
The unit of measurement is terminal rows.
help # Print the usage screen and exit.
formDraw

Performs the actual drawing of the form. This can only be used after the form box has been fully set up.

Option Description
help # Print the usage screen and exit.

Info

Component to set up a new input box and perform drawing to the terminal. Corresponds to the --infobox argument in Dialog/Whiptail.

Tip

Due to an old bug, Whiptail fails to render the info box in an xterm-based terminals (e.g., gnome-terminal). As a workaround, boxlib implicitly uses linux terminfo whenever $TERM matches xterm* or *-256color. The linux terminfo has color support, but if it doesn't work for you, try setting the term option to vt220 or ansi instead.

Demo | Show example code

Demo info box (Dialog) Demo info box (Whiptail)

List of commands:

info <options>

Sets up a new info box and draws it.

Option Default Description
clear= false # Whether to clear the screen after the box exits. Unless specified otherwise, this option is implicitly set to true when combined with the sleep option.
Possible values: true (or 1), false (or 0).

See Common Options


Input

Component to set up a new input box and perform drawing to the terminal. Corresponds to the --inputbox & --passwordbox arguments in Dialog/Whiptail.

Demo
input | Show example code

Demo input box (Dialog) Demo input box (Whiptail)

password | Show example code

Demo input password box (Dialog) Demo input password box (Whiptail)

List of commands:

input <options>

Sets up a new input box and draws it.

Option Default Description
type= text # The type of the input box.
Possible values: text, password.
value=
value+=
"" # The value used to initialize the input string.

See Common Options


List

Component to set up a new list box, add choice entries and perform drawing to the terminal. Corresponds to the --buildlist, --checklist, --radiolist &--treeview arguments in Dialog/Whiptail.

Note

In Whiptail, the buildlist feature is emulated using the checklist box. The treeview feature is emulated using the radiolist box.

Demo
build list | Show example code

Example buildlist box (Dialog) Example buildlist box (Whiptail)

checkbox list | Show example code

Example checklist box (Dialog) Example checklist box (Whiptail)

radiobox list | Show example code

Example radiolist box (Dialog) Example radiolist box (Whiptail)

tree list | Show example code

Example treelist box (Dialog) Example treelist box (Whiptail)

List of commands:

list <options>

Sets up a new list box.

Option Default Description
type= check # The type of the list box.
Possible values
builddisplays two lists, side-by-side. The results are written
in the order displayed in the selected-window.
checksimilar to a menu, but allows to select either many
entries or none at all.
The results are written in the order displayed in the
window.
radiosimilar to a menu, but allows to select either a single
entry or none at all.
treesimilar to a radio list, but displays entries organized
as a tree. The depth is controlled using the depth.
The entry title is not displayed. After selecting an
entry, the entry title is the output.
listHeight= auto # The list height, which determines the amount of choices to be displayed at one time, but the list will be scrolled if there are more entries than that.
Use auto or 0 to auto-size to fit the contents.
Can be denoted using percent sign, (e.g., 50%), to adjust dynamically based on the amount choices.
prefix= # Whether to prefix the entry titles in the list with enumeration, alphabetic letter, or both. This is especially useful for quickly selecting a choice using the keyboard.
Unless the keepPrefix option is used, the prefix will be stripped off before printing/returning the result.
Possible values
numcardinal number: 1, 2, 3 ... 15, 16 ...
alphaalphabet letter: a-z (after z, it goes back to a)
alphanumalphanumeric: 1-9,a-z (after z, it goes back to 1)
alphanum0full alphanumeric: 0-9,a-z (after z, it goes back
to 0)
alphanum1hybrid alphanumeric: 1-9,0,a-z(after z, it goes
back to 1)
keepPrefix= false # Whether to keep the prefix in the selected entry when printing/returning the result.
This can be useful if you want to use the prefix initials to match entries.
Possible values: true (or 1), false (or 0).
[ opt=val ... ] # The special square bracket syntax to add list choices, which accepts listEntry options as-is. For example:
list title='My list' [ title='Option 1' summary='Summary 1' ] [ ... ]

See Common Options

listEntry <options>

Adds a choice entry. This can only be used after the list box has been set up. Choice entries will appear in the list in the order they are added.

Option Default Description
title=
title+=
"" # The title of the choice entry.
summary=
summary+=
"" # The summary of the entry.
selected= false # Whether to preselect the choice.
Possible values: true (or 1), false (or 0).
depth= 0 # The depth of the entry in the tree list.
callback= # The callback to invoke when this entry is selected in the list. The callback will be:
  • invoked as a local function if it ends with ()
  • executed if it's a file with the "execute" bit set
  • sourced as a shell script file if it's none of the above
In all cases, the callback should expect the title of the selected entry as first input parameter. When executed, the $? variable will contain the exit code from the renderer (Whiptail/Dialog).

NOTE:
  • This option takes precedence over the list's callback parameter.
  • The callback execution will be sandboxed, i.e., it will run in a sub-shell. This ensures the interaction is isolated.
  • If the callback is a relative path to a file, then it will be searched starting from the working directory.
    Also, the CWD will be changed to where the callback file is located before executing/sourcing it. To disable, set changeToCallbackDir=false.
help # Print the usage screen and exit.
listDraw

Performs the actual drawing of the list. This can only be used after the list box has been fully set up.

Option Description
help # Print the usage screen and exit.

Menu

Component to set up a new menu box, add menu entries and perform drawing to the terminal. Corresponds to the --menu & --inputmenu arguments in Dialog/Whiptail.

Note

In Whiptail, the input menu is simulated using a normal menu and an input box allowing you to edit the menu entry summary when it is selected, similar to how it works in Dialog.

Demo
menu | Show example code

Demo menu box (Dialog) Demo menu box (Whiptail)

input menu | Show example code

Demo menu (input) box (Dialog) Demo menu (input) box (Whiptail)

List of commands:

menu <options>

Sets up a new menu box.

Option Default Description
menuHeight= auto # The menu height, which determines the amount of entries to be displayed at one time, but the menu will be scrolled if there are more entries than that.
Use auto or 0 to auto-size to fit the contents.
Can be denoted using percent sign, (e.g., 50%), to adjust dynamically based on the amount of menu entries.
prefix= # Whether to prefix the entry titles in the list with enumeration, alphabetic letters, or both. This is especially useful for quickly selecting an entry using the keyboard.
Unless the keepPrefix option is used, the prefix will be stripped off before printing/returning the result.
Possible values
numcardinal number: 1, 2, 3 ... 15, 16 ...
alphaalphabet letter: a-z (after z, it goes back to a)
alphanumalphanumeric: 1-9,a-z (after z, it goes back to 1)
alphanum0full alphanumeric: 0-9,a-z (after z, it goes back
to 0)
alphanum1hybrid alphanumeric: 1-9,0,a-z(after z, it goes
back to 1)
keepPrefix= false # Whether to keep the prefix in the selected entry when printing/returning the result.
This can be useful if you want to use the prefix initials to match entries.
Possible values: true (or 1), false (or 0).
rename= false # Whether renaming the menu entry summary is allowed. The output after renaming will follow this format: RENAMED <entry> <summary>
After renaming, the exit status is equal to 3.
Possible values: true (or 1), false (or 0).
[ opt=val ... ] # The special square bracket syntax to add menu entries, which accepts menuEntry options as-is. For example:
menu title='My menu' [ title='Option 1' summary='Summary 1' ] [ ... ]

See Common Options

menuEntry <options>

Adds a menu entry. This can only be used after the menu box has been set up. Menu entries will appear in the menu in the order they are added.

Option Default Description
title=
title+=
"" # The title of the menu entry.
summary=
summary+=
"" # The summary of the entry.
selected= false # Whether to preselect the entry. Same as --default-item argument in Whiptail/Dialog.
Possible values: true (or 1), false (or 0).
callback= # The callback to invoke when this entry is selected in the menu. The callback will be:
  • invoked as a local function if it ends with ()
  • executed if it's a file with the "execute" bit set
  • sourced as a shell script file if it's none of the above
In all cases, the callback should expect the title of the selected entry as first input parameter. When executed, the $? variable will contain the exit code from the renderer (Whiptail/Dialog).

NOTE:
  • This option takes precedence over the menu's callback parameter.
  • The callback execution will be sandboxed, i.e., it will run in a sub-shell. This ensures the interaction is isolated.
  • If the callback is a relative path to a file, then it will be searched starting from the working directory.
    Also, the CWD will be changed to where the callback file is located before executing/sourcing it. To disable, set changeToCallbackDir=false.
help # Print the usage screen and exit.
menuDraw

Performs the actual drawing of the menu. This can only be used after the menu box has been fully set up.

Option Description
help # Print the usage screen and exit.

Pause

Component to set up a new pause box and perform drawing to the terminal. Corresponds to the --pause argument in Dialog.

Note

In Whiptail, this feature is emulated using a menu box.

Demo | Show example code

Demo pause box (Dialog) Demo pause box (Whiptail)

List of commands:

pause <options>

Sets up a new pause box and draws it.

Option Default Description
seconds= # The amount of seconds to pause, after which the box will timeout.
The decimal part will be dropped.

See Common Options


Program

Sets up a new program box that will display the output of a command. Corresponds to the --prgbox, --programbox & --progressbox arguments in Dialog.

Note

In Whiptail, this feature is emulated using an input field with validation.

Important

When using process substitution to feed the program box, it's crucial to wait for the process substitution's PID to ensure correct behavior of the box, especially with hideOk="false" option. See example:

{ /bin/cmd; } > >(program text='Working in progress...') 2>&1
wait $!

# Pitfall: the result of $! could be empty if process substitution is fed by
# an external command ('/bin/cmd' in this case). As a workaround, wrap external
# command call using a Bash function or put everything in {...} block and
# redirect the output of the block as shown above.
# For more details, see https://unix.stackexchange.com/a/524844

The rationale behind that, is because the program box reads your program's output in a separate subshell. Once your program finishes, the subshell running the program box will be effectively detached from the main Bash process. By explicitly waiting for it before exiting your script or launching another box, you ensure the program box can clean up the screen and restore TTY input settings.

Also, this strictly requires Bash v4.4 for the process substitution's PID to be waitable. For Bash v4.3, use flock-style locking mechanism to achieve the same.

Demo | Show example code

Demo program box (Dialog) Demo program box (Whiptail)

List of commands:

program <options>

Sets up a new box that will display the output of a command.

Option Default Description
command=
command+=
# The command(s) to execute via sh -c <command> whose output will be displayed in the box.
If omitted, then the output will be read from stdin.
hideOk= false # Whether to hide the OK button after the command has completed.
Possible values: true (or 1), false (or 0).

Progress

Component to set up a new progress box, make adjustments on-the-fly, and perform drawing to the terminal. Corresponds to the --gauge & --mixedgauge arguments in Dialog/Whiptail.

Note

In Whiptail, the mixed progress is simulated using a normal progress.

Demo
progress | Show example code

Demo progress box (Dialog) Demo (mixed) progress box (Whiptail)

mixed progress | Show example code

Demo progress box (Dialog) Demo (mixed) progress box (Whiptail)

List of commands:

progress <options>

Sets up a new progress box and draws it.

Option Default Description
value= 0 # The initial value to calculate the percentage of the progress bar. If total option is not set, then this value is the % value of the progress bar. Can be updated using progressSet command.
Decimal part will be dropped.
total= 0 # The initial total or complete value used to calculate the % value that will be displayed in the progress bar. Here's the general formula: percentage=value/total*100. Can be updated using progressSet command.
Decimal part will be dropped.
entry= # The entry (row) name to add. Each entry must be paired with a state option. Any amount of entries can be added, or you can add them later with progressSet command. The entry name should be unique, so that its state can be updated using progressSet command.
IMPORTANT: the number of entry and state options must be equal. The following example will fail (2 entries and 1 state): progress entry='Entry1' state="$PROGRESS_N_A_STATE" entry='Entry2'
state= # The state of the entry. The entry state can be any string. If the string starts with a leading - (e.g., -20), then it will be suffixed with a % sign, such as 20%.
There are 10 special entry states encoded as digits (0-9), publicly available as numeric constant variables:
PROGRESS_SUCCEEDED_STATE (0)Succeeded
PROGRESS_FAILED_STATE (1)Failed
PROGRESS_PASSED_STATE (2)Passed
PROGRESS_COMPLETED_STATE (3)Completed
PROGRESS_CHECKED_STATE (4)Checked
PROGRESS_DONE_STATE (5)Done
PROGRESS_SKIPPED_STATE (6)Skipped
PROGRESS_IN_PROGRESS_STATE (7)In Progress
PROGRESS_BLANK_STATE (8)(blank)
PROGRESS_N_A_STATE (9)N/A

See Common Options

progressSet <options>

Performs adjustments on the progress box.

Option Default Description
text=
text+=
# The new string to display inside the progress box.
value= # The new value to calculate the percentage of the progress bar.
If total option is not set, then this value is the % value of the progress bar.
Decimal part will be dropped.
total= # The new total or complete value used to calculate the % value that will be displayed in the progress bar. Here's the general formula: percentage=value/total*100.
Decimal part will be dropped.
entry= # The entry (row) name to add or update, if it already exists.
state= # The state of the entry that is being added or updated.
help # Print the usage screen and exit.
progressExit

Manually causes the progress box to exit. Normally, the progress box will exit automatically as soon as it reaches 100% or when your script terminates.

Option Description
help # Print the usage screen and exit.

Range

Component to set up a new range box to select from a range of values, e.g., using a slider. Corresponds to the --rangebox argument in Dialog.

Note

In Whiptail, this feature is emulated using an input field with validation.

Demo | Show example code

Demo range box (Dialog) Demo range box (Whiptail)

List of commands:

range <options>

Sets up a new range box to select from a range of values, e.g., using a slider.

Option Default Description
min= 0 # The minimum value of the range.
Example: 0.
max= (minimum value) # The maximum value of the range.
Example: 10.
default= (minimum value) # The default value of the range.
Example: 5.

Selector

Component to set up a new file/directory selector box and perform drawing to the terminal. Corresponds to the --dselect & --fselect argument in Dialog.

Note

In Whiptail, this feature is emulated using a menu box.

Path Editor: the Cancel button has been replaced with Edit/Exit, which opens an input box allowing you to edit the path, similar to how it works in Dialog.

Quick Selection: as in Dialog, you can use incomplete paths to pre-select the first entry that partially match.

Navigation:

  • The . (dot) entry selects the current directory or the string "as-is", as provided in the path editor.
  • The .. (dot-dot) entry navigates to the parent directory.
  • Selecting an entry in the menu list will canonicalize the filepath using realpath command.
  • Pressing the ESC key exits the selector box. Alternatively, you should go through the path editor box to exit.
Demo | Show example code

Demo selector box (Dialog) Demo selector box (Whiptail)

List of commands:

selector <options>

Sets up a new file/directory selector box and draws it.

Option Default Description
filepath=
filepath+=
"" # The path to a file or directory. If a file path is provided, the path contents will be displayed, and the filename will be pre-selected.
Corresponds to the --fselect argument in Dialog.
This option is used by default.
directory=
directory+=
"" # The path to a directory. If a file path is provided, the filename will be discarded, and the path contents will be displayed instead.
Corresponds to the --dselect argument in Dialog.
NOTE: only directories will be visible in the selector box when using this option.

See Common Options


Text

Component to set up a new text box and perform drawing to the terminal. Corresponds to the --msgbox, --textbox, --tailbox & --tailboxbg arguments in Dialog/Whiptail.

Note

In Whiptail, the tailbox feature is emulated using a program box.

Demo | Show example code: text, text (file), text (file follow)

Demo text box (Dialog) Demo text box (Whiptail)

List of commands:

text <options>

Sets up a new text box and draws it.

Option Default Description
file=
file+=
# The path to a file whose contents to display in the text box. This takes precedence over the text option.
follow= false # Whether to follow the contents of the file as in tail -f command.
In Dialog, long lines can be scrolled horizontally using vi-style h (left) and l (right), or arrow-keys. A 0 resets the scrolling. In Whiptail, long lines are wrapped by default.
Possible values: true (or 1), false (or 0).
inBackground= false # Whether to follow the file in the background, as in tail -f & command, while displaying other widgets.
If no widgets are added using the --and-widget option, the display of the box will be postponed until another box is launched, which will then be used as widget.
NOTE: Dialog will perform all of the background widgets in the same process, polling for updates. You may use Tab to traverse between the widgets on the screen, and close them individually, e.g., by pressing ENTER.
Possible values: true (or 1), false (or 0).

See Common Options


Timepicker

Component to set up a new time picker box and perform drawing to the terminal. Corresponds to the --timebox argument in Dialog.

Note

In Whiptail, this feature is emulated using an input field with validation. The output (as well as the input) defaults to the format hh:mm:ss.

Demo | Show example code

Demo time picker box (Dialog) Demo time picker box (Whiptail)

List of commands:

timepicker <options>

Sets up a new time box and draws it.

Option Default Description
hour= (current hour) # The hour of the time picker.
Example: 15.
minute= (current minute) # The minute of the time picker.
Example: 10.
second= (current second) # The second of the time picker.
Example: 59.
timeFormat=
timeFormat+=
%H:%M:%S # The format of the outputted time string.
NOTE: in Dialog, the --time-format option will be used, which relies on strftime, whereas in Whiptail, the date command will be used. So, there may be slight differences in the format specifiers.
forceInputBox= false # Whether to force use of the input box instead of the Dialog's time box.
Possible values: true (or 1), false (or 0).

See Common Options

Communication between boxes

The boxlib simplifies not only box creation but also interaction between boxes and result capture.

Result capture

When no callbacks are attached to a box or its entries, or if the printResult=true option is set, the result(s) will be printed to stdout, one per line, after the box exited. Otherwise, the callback should expect the result(s) as input parameters.

Exit code capture

Unless a callback is specified via callback option on the box or its entries, or the propagateCallbackExitCode=false option is set, all boxes will exit with the status code returned by the corresponding Whiptail/Dialog box (see the DIAGNOSTICS section in the man page for codes). Otherwise, the $? variable will hold the box's exit code at the time the callback is invoked.

Status code propagation example

In this example, we create a radio list box that allows users to perform file actions on a dummy file. Observe how the return code is propagated backwards through the whole callback chain, up to the parent box, which is the radio list box.

FILE='/path/to/dummy/file.txt'

OPERATION_NOT_PERMITTED_CODE=5

function main() {
  list \
    type='radio' \
    title='Actions with file' \
    text="Choose what to do with file: $FILE\n" \
    text+="or press ESC to cancel" \
    printResult='true' # note that we need 'printResult' option to capture the choice
                       # name, so we can match it with the return status code
  
  listEntry title='Open' callback='open_file_handler()'
  listEntry title='Delete' callback='delete_file_handler()'
  
  choice="$(listDraw)"
  result_code=$?
  case "$choice" in
    Open) echo 'Open: status code:' $result_code;;
    Delete)
      case $result_code in
        "$OPERATION_NOT_PERMITTED_CODE")
        echo 'Delete: error: Operation not permitted.'
        ;;
        *) echo 'Delete: exit code' $result_code
      esac
      ;;
    *) echo 'User has not made a choice or canceled the radio list. ' \
        'Exit code: ' $result_code
  esac
}

function open_file_handler() {
  return 0
}

function delete_file_handler() {
  confirm \
    title='Delete file' \
    text="This action will delete the file: $FILE\n\n" \
    text+='Are you sure you want to continue?' \
  callback='confirm_delete_file_handler()'
  return $? # <= this is the return code from the confirm box callback
}

function confirm_delete_file_handler() {
  # Capture the status code from the confirm box
  local status=$?
  # The user answered "Yes"
  if [ $status -eq 0 ]; then
    # Simulate something went wrong, and we want to exit with a specific code
    return 5
  fi
  # Otherwise, propagate the code further, as the user could cancel the confirm
  # box with ESC key, which will return 255 instead
  return $status
}

main

When Open is selected, this is the expected output in terminal:

Open: status code: 0

When Delete is selected and user answered "Yes", this is the expected output in terminal:

Delete: error: Operation not permitted.
Variable capture

When using functions or sourceable shell-scripts as callback, you can easily pass arbitrary data to the parent box using the Bash builtin export command to mark variables for automatic export to the environment.

Example:

data='abcd'
export key='value'

function cb() {
  # The variable won't be passed to the parent's environment variable space, as
  # boxlib captures only global exported variables
  data='123'

  # Since the 'key' variable is already exported in the parent, it can be changed
  key='efgh'

  # This variable is (globally) declared in the callback's sandbox (sub-shell),
  # so it must be exported
  myVar='123'
  export myVar # or export myVar='123'

  # You can also export arrays with -x key (NOTE: also need -g or it will be local to
  # this function)
  declare -gx -a myArr=(1 2 3)

  # Or you can un-export a parent's variable to ensure changes will remain in
  # the callbacks's sandbox
  export -n dummy
  dummy=2
}

echo 'Before box start.'
export dummy=1
declare -p dummy # => declare -x dummy="1"
echo

confirm title='Test' callback='cb()'

echo
echo "data=$data" # => abcd
echo "key=$key" # => efgh
echo "myVar=$myVar" # => 123
echo "myArr=(${myArr[*]})" # => (1 2 3)
declare -p dummy # => declare -x dummy="1"

Must Know: since callbacks are executed in a "sandboxed" environment (i.e., a sub-shell) to prevent pollution and collisions, the boxlib implements the callback environment propagation mechanism. This mechanism migrates ONLY exported variables to the parent's environment variable space after the callback exits.

If you're going to use the callback environment propagation mechanism, note that it runs right after the callback exits. Using exit in your callbacks prevents the environment variable space propagation, as it causes the whole sandbox to exit earlier. ALWAYS use return instead, even in sourceable shell-scripts.

Global variables

$BOXLIB_USE_WHIPTAIL=1

This environment variable, when set to 1, will force Whiptail as renderer. The rendererPath/rendererName options will still take precedence, though. Example usage:

BOXLIB_USE_WHIPTAIL=1 ./my_app/main.sh 1 # or BOXLIB_USE_WHIPTAIL=1 bash ./my_app/main.sh
$BOXLIB_DEBUG=/path/to/file

This environment variable enables debug printing to file or terminal. It also accepts the debug option values. The latter (if used), will still take precedence, though. Example usage:

BOXLIB_DEBUG=stderr ./my_app/main.sh 1 # or BOXLIB_DEBUG=stderr bash ./my_app/main.sh
$BOXLIB_LOADED

This read-only variable is set after the library has been loaded (sourced). Example usage:

if [ ${BOXLIB_LOADED+xyz} ]; then
  echo 'boxlib is loaded.'
else
  echo 'boxlib is not loaded.'
fi

Troubleshooting

  • Set the BOXLIB_DEBUG environment variable, or the debug option at the very top of the file (e.g., the entrypoint script) to record logs (including backend renderer errors) to a file or print them all to terminal.

  • On some unexpected box render failures or if you happened to hit Ctrl+C during box drawing, the terminal may be left in a messed-up state (e.g., an invisible cursor, leftovers of the boxes, etc.). To fix your terminal, type the reset command, even if the input is invisible.

Useful links

About

Pure Bash library for building portable TUI apps with Whiptail/Dialog

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

0