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.
-
🧠 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 acrossLinux
,*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%
andheight=50%
options to dynamically scale with the terminal!
- Want to easily loop a box (e.g. a menu) until user exits? Just set
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 of a menu box created with Whiptail and with boxlib.
Whiptail-only | the equivalent 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 |
See more demos examples here.
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.
-
Create a Git project
git init
-
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
- As submodule
-
Create an entrypoint script file (will name it
main.sh
) in the project root directory & make it executabletouch 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
-
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.
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
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.
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 with0
or1
indicating the enabled debug state.
Possible values stdout
prints debug to the standard output stderr
prints debug to the standard error <filename>
writes debug to the given filename. isDialogRenderer
# Check whether Dialog is the default renderer. Will print true
orfalse
and also exit with0
or1
.reset
# Reset the configuration to defaults. help
# Print the usage screen and exit.
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.
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:
$? variable will contain the exit code from the renderer (Whiptail/Dialog).NOTE:
|
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
(or1
),false
(or0
).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 withloop=true
option.
Possible values:true
(or1
),false
(or0
).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
(or1
),false
(or0
).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 pressedESC
key orCancel
button or answeredNo
.
When set totrue
, the callback will always be invoked, and the$?
variable variable will contain the exit code from the renderer.
Possible values:true
(or1
),false
(or0
).printResult=
false
# Whether to print the result(s) to stdout, each on a new line, after the box exits.
Possible values:true
(or1
),false
(or0
).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 theabortOnCallbackFailure=true
option, to cause the whole callback chain, if any, to be interrupted.
Possible values:true
(or1
),false
(or0
).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
(or1
),false
(or0
).hideBreadcrumb=
false
# Whether to hide the box from the breadcrumbs stack displayed at the top of the screen.
Possible values:true
(or1
),false
(or0
).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, thesleep
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
(or1
),false
(or0
).topleft=
false
# Whether to put window in top-left corner.
Same as using Whiptail/Dialog's--topleft/--begin 0 0
option.
Possible values:true
(or1
),false
(or0
).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'
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
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 onstrftime
, whereas in Whiptail, thedate
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
(or1
),false
(or0
).
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
List of commands:
confirm <options>
Sets up a new confirm box and draws it.
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
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 ofnano
,nano-tiny
, orvi
.
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
(or1
),false
(or0
).
Component to set up a form box and perform drawing to the terminal. Corresponds to the --form
, --mixedform
& --passwordform
arguments in Dialog.
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
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.
Useauto
or0
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 ] [ ... ]
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 input
A standard input field that allows to enter and edit text hidden
A field that stores sensitive data not visible to the user,
like passwordsreadonly
A non-editable field that displays information to the
user, similar to a labeltitle=
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 ofwidth
is used instead.titleX=
# The x-coordinate of the field's title within the form's window.
This is automatically computed ifcolumns
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 ifcolumns
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 ifcolumns
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 astitleY
.
This is automatically computed ifcolumns
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.
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
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 thesleep
option.
Possible values:true
(or1
),false
(or0
).
Component to set up a new input box and perform drawing to the terminal. Corresponds to the --inputbox
& --passwordbox
arguments in Dialog/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.
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
checkbox list | Show example code
radiobox list | Show example code
tree list | Show example code
List of commands:
list <options>
Sets up a new list box.
Option Default Description type=
check
# The type of the list box.
Possible values build
displays two lists, side-by-side. The results are written
in the order displayed in the selected-window.check
similar 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.radio
similar to a menu, but allows to select either a single
entry or none at all.tree
similar to a radio list, but displays entries organized
as a tree. The depth is controlled using thedepth
.
The entrytitle
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.
Useauto
or0
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 thekeepPrefix
option is used, the prefix will be stripped off before printing/returning the result.
Possible values num
cardinal number: 1
,2
,3
...15
,16
...alpha
alphabet letter: a-z
(afterz
, it goes back toa
)alphanum
alphanumeric: 1-9,a-z
(afterz
, it goes back to1
)alphanum0
full alphanumeric: 0-9,a-z
(afterz
, it goes back
to0
)alphanum1
hybrid alphanumeric: 1-9,0,a-z
(afterz
, it goes
back to1
)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
(or1
),false
(or0
).[ 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' ] [ ... ]
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
(or1
),false
(or0
).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: In all cases, the callback should expect the title of the selected entry as first input parameter. When executed, the
- 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
$?
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, setchangeToCallbackDir=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.
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.
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.
Useauto
or0
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 thekeepPrefix
option is used, the prefix will be stripped off before printing/returning the result.
Possible values num
cardinal number: 1
,2
,3
...15
,16
...alpha
alphabet letter: a-z
(afterz
, it goes back toa
)alphanum
alphanumeric: 1-9,a-z
(afterz
, it goes back to1
)alphanum0
full alphanumeric: 0-9,a-z
(afterz
, it goes back
to0
)alphanum1
hybrid alphanumeric: 1-9,0,a-z
(afterz
, it goes
back to1
)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
(or1
),false
(or0
).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 to3
.
Possible values:true
(or1
),false
(or0
).[ 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' ] [ ... ]
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
(or1
),false
(or0
).callback=
# The callback to invoke when this entry is selected in the menu. The callback will be: In all cases, the callback should expect the title of the selected entry as first input parameter. When executed, the
- 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
$?
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, setchangeToCallbackDir=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.
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
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.
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 wait
able.
For Bash v4.3, use flock
-style locking mechanism to achieve the same.
Demo | Show example code
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
(or1
),false
(or0
).
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.
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 usingprogressSet
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 usingprogressSet
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 withprogressSet
command. The entry name should be unique, so that its state can be updated usingprogressSet
command.
IMPORTANT: the number ofentry
andstate
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 as20%
.
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
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.
Iftotal
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.
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
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
.
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
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.
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)
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-styleh
(left) andl
(right), or arrow-keys. A0
resets the scrolling. In Whiptail, long lines are wrapped by default.
Possible values:true
(or1
),false
(or0
).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
(or1
),false
(or0
).
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
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 onstrftime
, whereas in Whiptail, thedate
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
(or1
),false
(or0
).
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 thepropagateCallbackExitCode=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 } mainWhen 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 builtinexport
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 usereturn
instead, even in sourceable shell-scripts.
$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
-
Set the
BOXLIB_DEBUG
environment variable, or thedebug
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 thereset
command, even if the input is invisible.