The Shell

The loader is the script $FW_HOME/bin/shell/shell.sh. It provides an interactive shell to either execute shell commands or tasks. Most shell commands are realized by tasks, so the actual shell implementation is rather simple.

The initial settings are shown in the source block below. Line 1 restricts bash, providing a safer execution environment.

1
set -o errexit -o pipefail -o noclobber -o nounset

Test Parent and and load Configuration

The shell then tests if it is run from the right parent (i.e. the loader) and it loads the temporary configuration. The parent test is rather simple: if FW_HOME or FW_L1_CONFIG are not set, it is very likely that the shell has not been started by the loader. Otherwise, the temporary configuration can be loaded (line 5). The configuration is set for running in the shell (line 6). This information is used by the API functions in the console to determine what output (log) level and what error/warning counters to use.

1
2
3
4
5
6
if [[ -z ${FW_HOME:-} || -z ${FW_L1_CONFIG-} ]]; then
    printf " ==> please run from framework or application\n\n"
    exit 40
fi
source $FW_L1_CONFIG
CONFIG_MAP["RUNNING_IN"]="shell"

Include

Next, the shell includes the framework’s API functions (lines 1-2) and the functions to maintain its command history (line 3). Then the error and warning counters are reset (set to 0), and a simple new line is printed (if messages are allowed).

1
2
3
4
5
6
7
source $FW_HOME/bin/api/_include
source $FW_HOME/bin/api/describe/task.sh
source $FW_HOME/bin/shell/history.sh

ConsoleResetErrors
ConsoleResetWarnings
ConsoleMessage "\n"

Settings

Now the shell realizes its core settings. These settings are:

  • SCMD - used to store shell input

  • SARG - used to store arguments (only used for history now)

  • STIME - used to set the time when a command was entered, and later for time calculations

  • RELOAD_CFG - a flag to indicate if the temporary configuration should be reloaded. This flag is required for instance when the task set alters settings.

  • HISTORY - a map with the history of commands #end::settings[]

1
2
3
4
5
6
SCMD=                           # a shell-command from input
SARG=                           # argument(s), if any, for a shell command
STIME=                          # time a command was entered
RELOAD_CFG=false                # flag to reload configuration, e.g. after a change of settings
declare -A HISTORY              # the shell's history of executed commands
HISTORY[-1]="help"              # dummy first entry, size calculation doesn't seem to work otherwise

Inner Loop: FWInterpreter

The inner loop is an interpreter for all input the shell wants to add to its history. This input might be the request to execute a task, or a command that the shell realizes using a task, or a simple command. The inner loop is defined as a function.

1
2
3
4
5
FWInterpreter() {
    case "$SCMD" in
        # ...
    esac
}

Inside this function, the inner loop, the shell tests first for all input that it can associate to a command. If none of these tests is satisfied, it assumes that the input is actually a task to be executed with parameters.

Execute a Scenario

The first command tested is to execute a scenario. This command starts with either execute-scenario or es. No argument means error. Some argument means the name of a scenario. In this case, execute the scenario and put the command into the history.

1
2
3
4
5
6
7
8
9
10
11
12
13
        execute-scenario | es)
            printf "\n    execute-scenario/rs requires a scenario as argument\n\n"
            ;;
        "execute-scenario "*)
            SARG=${SCMD#*execute-scenario }
            ExecuteScenario $SARG
            ShellAddCmdHistory
            ;;
        "es "*)
            SARG=${SCMD#*es }
            ExecuteScenario $SARG
            ShellAddCmdHistory
            ;;

Clear Screen

This a simple command without arguments. The clear screen functionality is realized by printing the ANSI escape sequence for clear screen.

1
2
3
4
        clear-screen | "clear-screen "* | cls | "cls "*)
            printf "\033c"
            ShellAddCmdHistory
            ;;

Time

Print the current time.

1
2
3
4
        time | "time "* | T | "T "*)
            printf "\n    %s\n\n" "$STIME"
            ShellAddCmdHistory
            ;;

Execute the task list-configuration with default settings, which displays a list with the current configuration.

1
2
3
4
        configuration | "configuration "* | c | "c "*)
            ${DMAP_TASK_EXEC["list-configuration"]}
            ShellAddCmdHistory
            ;;

Execute the task statistics with default settings, which displays an overview of statistic information.

1
2
3
4
        statistic | "statistic "* | s | "s "*)
            ${DMAP_TASK_EXEC["statistics"]}
            ShellAddCmdHistory
            ;;

List Tasks

Execute the task list-tasks with default settings, which displays an list of loaded tasks.

1
2
3
4
        tasks | "tasks "* | t | "t "*)
            ${DMAP_TASK_EXEC["list-tasks"]}
            ShellAddCmdHistory
            ;;

Comments

Do nothing if the input starts with the comment character # in any variation.

1
2
        "" | "#" | "#"* | "# "*)
            ;;

All other Input

All other input is interpreted as the request to execute a task with optional arguments.

1
2
3
4
5
6
7
8
9
        *)
            SARG="$SCMD"
            ExecuteTask "$SARG"
            ShellAddCmdHistory

            case "$SCMD" in
                "set "* | "setting "*) RELOAD_CFG=true;;
            esac
            ;;

Outer Loop: FWShell

The outer loop is the shell’s main loop reading input from standard input and interpreting it. While input is read (a line finished with an enter), the line is read into the variable SCMD and a time stamp is written into STIME. Then SCMD is evaluated.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
FWShell() {
    while read -a args; do
        SCMD="${args[@]:-}" <&3
        STIME=$(date +"%T")
        case "$SCMD" in
            # ...
        esac

        if [[ $RELOAD_CFG == true ]]; then
            source $FW_L1_CONFIG
            CONFIG_MAP["RUNNING_IN"]="shell"
            RELOAD_CFG=false
        fi
        if ConsoleIsPrompt; then ConsoleMessage "${CONFIG_MAP["SHELL_PROMPT"]}"; fi
    done
}

Inside this function, the outer loop, the shell tests first for shell commands. If none of these tests is satisfied, it calls the inner loop to deal with the input.

Once finished with the input, the value of RELOAD_CFG to see if the configuration has to be reloaded. Finally, a new prompt is displayed.

Help

If help is requested, display the command help file for the current print mode.

1
2
3
            help | h | "?")
                cat ${CONFIG_MAP["FW_HOME"]}/etc/help/commands.${CONFIG_MAP["PRINT_MODE"]}
                ;;

History

History means to print the history or to run a commend stored in the history. Both functionalities are provided by the history function.

1
2
3
4
5
6
7
8
            !*)
                SARG=${SCMD#*!}
                ShellCmdHistory
                ;;
            history*)
                SARG=${SCMD#*history}
                ShellCmdHistory
                ;;

Exit

If exit is requested, the shell leaves the outer loop.

1
2
3
            exit | quit | q | bye)
                break
                ;;

All other Input

All other input is forwarded to the inner loop for further evaluation.

1
2
3
            *)
                FWInterpreter
                ;;

Run the Shell

The final lines run the actual shell. First, input is redirected to #3 while the shell is running (line 1). This allows to read lines from the standard input into the shell. Next, the first prompt is displayed (line 2). Then the outer loop is called (line 3). When the outer loop is finished, the redirection is reverted (line 4). Now the shell is finished and the process returns to the loader.

1
2
3
4
exec 3</dev/tty || exec 3<&0
if ConsoleIsPrompt; then ConsoleMessage "${CONFIG_MAP["SHELL_PROMPT"]}"; fi
FWShell
exec 3<&-