=========
 shellia
=========

-------------------------------------
library for interactive shell scripts
-------------------------------------

:Author: bernd.schumacher@hpe.com
:Date: 2020-08-07
:Copyright: Bernd Schumacher <bernd.schumacher@hpe.com> (2007-2020)
:License: GNU General Public License, version 3
:Version: 0.1
:Manual section: 3
:Manual group: shell library

SYNOPSIS
========

  **. /usr/share/shellia/ia[.interactive|.check|.debug|]**

  **eval "$ia_init"**

  [[**ia_nocheck** [**-f**]] | [[**ia_stdout|ia_ignore** *regex*] ...]]

  **ia_add** *command-sequence*

  **dbg** [**debug-level** [**debug-topic** ...]] **debug-output**

  **ia** [**-c|-C**] [**-x**]

ENVIRONMENT
===========

  If **ia_logfile** contains the path to a writeable file, logfile output
  as seen by **ia** is appended to that file.

DESCRIPTION
===========

**shellia** is a library that allows one to run shell scripts silently or
interactive and helps to check errors of commands that are combined in
**steps**.

Each function of a shell program (including the main shell program) can
include a group of **steps**.
A group of **steps** should always be enclosed between an initializing line
**eval "$ia_init"** and the line with command **ia** which starts the execution
of the **steps**.
Each **step** is a *command-sequence* given as string argument to **ia_add**
The *command-sequence* contained in the string has the same requirements as
if it would be given to the shell command *eval*.

OPTIONS
=======

**. /usr/share/shellia/ia[.interactive|.check|.debug|]**
  To only load the **shellia** interactive library for basic interactive
  features use *. /usr/share/shellia/ia.interactive*.
  To only load shellia check library to check stderr, stdout and exit-value
  of **steps** use *. /usr/share/shellia/ia.check*.
  TO only load the shellia debug library, to add **debug-output** to a shell script
  use *. /usr/share/shellia/ia.debug*.
  To source all shellia libraries listed above is the suggested method and can
  be done with *. /usr/share/shellia/ia*.

**ia_nocheck**
  This command can be used, if the following **ia** command has enabled
  **check-mode** with option [**-c|-C**] for its group of **steps**.
  This will disable the check of *stderr* and *stdout* in the next single
  **ia_add** **step**.
  With option **-f** added, it will also disable checking the exit value
  of the next single step.
  Also if a **step** may exit the script, **ia_nocheck** can be used to
  prevent the message *ERROR: ia premature exit of ...*.

**ia_stdout** *regex*
  to use this command, the following **ia** command has to be used with option
  **-c**.
  *regex* is a regular expressions as defined in gawk(1), to define which
  output of the following **step** defined with **ia_add** will be
  standard output of the **step**.
  Each **step** can be preceded with any number of **ia_stdout** *regex* commands.
  Because the user can not suppress output allowed with **ia_stodut** with the
  option **-s**, **ia_stdout** should only be used, if the output is intended to be
  piped to another program.
  *regex* only needs to handle text without color-control-codes.

**ia_ignore** *regex*
  to use this command, the following **ia** command has to be used with either
  option **-c** or **-C**.
  *regex* is a regular expressions as defined in gawk(1), to define which
  output of the following **step** defined with **ia_add** will be
  ignored.
  Each **step** can be preceded with any number of **ia_ignore** *regex* commands.
  *regex* only needs to handle text without color-control-codes.

**-c**
  if this option is given to **ia** all **steps** previous added with
  **ia_add** will be checked with **check-mode** option **-c** (see
  **check-mode** below)

**-C**
  if this option is given to **ia** all **steps** previous added with
  **ia_add** will be checked with **check-mode** option **-C**. (see
  **check-mode** below)

**-x**
  if this option is given to **ia** all **steps** previous added with
  **ia_add** will use **trace-mode**. (see **trace-mode** below)

USAGE
=====

The examples listed below try to show the technical features of **shellia** in
very small examples.

Be aware that the **step** size in the example listed below is only
reasonable to demonstrate shellia features.
Please read shellia(7) **NOTES**, to learn about a reasonable size of
interactive **steps**.

Basic features
--------------

A script using the basic features to print *hello world* may look like::

  01  #!/bin/sh
  02  . /usr/share/shellia/ia.interactive
  03  eval "$ia_init"
  04  ia_add "echo \"hello\""
  05  ia_add "echo \"world\""
  06  ia

In line 02 the **shellia** library **ia.interactive** is loaded.
Line 03 initializes **shellia** and is needed before **shellia** functions can
be used.
It also consumes all shellia standard options, so that the remaining
options will be *script-specific-options*.
Line 04 and 05 are using **ia_add** to add the *echo* commands.
The execution starts in line 06 with the **ia** command.

If the script is called without options, it only prints *hello* and *world*.
If it is called with option **-i** it will run in interactive mode, as
discussed in shellia(1) **Basic features**.

On **debian** systems this script may be found at
*/usr/share/doc/shellia/examples/hello_world*.

Interactive-mode
----------------

A script using a function in **interactive-mode** may look like::

  01 . /usr/share/shellia/ia.interactive
  02 say_hello()
  03 {
  04   local name
  05   eval "$ia_init"
  06   ia_add "name=\"$1\""
  07   ia_add "echo \"hello \$name\""
  08   ia
  09 }
  10 eval "$ia_init"
  11 ia_add "say_hello <-i> -- \"$1\""
  12 ia

Line 01, 10, 12 are equivalent as in the previous example.
Line 02, 03, 04, 07 and 09 are normal shell statements, with no additional
comments.
Line 05 to initialize **shellia** has to be done in each function.
Line 06 sets variable name to $1.
Line 08 starts the **steps** and is needed in all functions.
Line 11 uses the special syntax *<-i>* which allows one to switch **-i** in
**interactive-mode**.

On **debian** systems this script may be found at
*/usr/share/doc/shellia/examples/hello_world_fun*.

See shellia(1) **Interactive-mode** to read about using this script.

check-mode
----------

Before demonstrating how to use the **check-mode** option **-c** or **-C** we first
start with the following script, without any **check-mode** option::

  01 . /usr/share/shellia/ia
  02 say_hello()
  03 {
  04   echo "hello $1"
  05   echo "end of function say_hello" >&2
  06   return 5
  07 }
  08 eval "$ia_init"
  09 ia_add "say_hello \"$1\""
  10 ia

If we run this script we get the following output and exit value::

  01 $ ./hello_world_check "shellia user"
  02 hello shellia user
  03 end of function say_hello
  04 $ echo $?
  05 5

In the script lines 02 to 07 the function *say_hello* is defined.
To call this function **shellia** specific syntax is used in script line 08 to
10.
Let's assume the function *say_hello* may represent an external program,
that we have to use, but are not allowed to change it.
But we want to change the output presented to the user:

 * the output "end of function say_hello" makes no sense for the user.
   We know it is harmless, and we just want to suppress it.
 * output starting with "hello" is what we want.
   But any other output, that we may not have seen until now, should be
   displayed as error.
 * We want to see every nonzero exit value as an error.
   Only an exit value of 5 seems to be ok and should not be displayed as error.

We can get this with **check-mode** option **-c** or **-C**.
There is a disccussion about which option to use in shellia(7)
**When to use check option -c and -C**.

check-mode -c
-------------

To get the wanted behaviour described in previous chapter with
**check-mode** option **-c** we change script line 10 to::

  10 ia -c

and get the following output::

  01 $ ./hello_world_check "shellia user"
  02 --- output with ERROR from <hello_world_check> running <say_hello "shellia user"> ---
  03 e:hello
  04 e:end of function say_hello
  05 e:exit=5
  06 --- (q)uit (r)edo (i)gnore (s)witch interactive ---
  07 ?

Output line 01 shows where errors happended.
The output lines 03 to 05 start with *e:* to classify the output as errors.
Line 03 is also shown as error, because shellia does not know,
that it is the wanted output. Line 04 is what we want to be suppressed.
And line 05 now shows every nonzero exit as an error.
To make the program to behave as wanted we can add the folloging lines +a, +b
and +c before script line 09::

  +a ia_stdout "^hello "
  +b ia_ignore "end of function say_hello$"
  +c ia_ignore "^exit=5$"
  09 ia_add "say_hello \"$1\""

Line +a will display the output we want to see, and line +b and +c will ignore
and suppresses what we do not want to see.

check-mode -C
-------------

A file that starts to fix *say_hello* with **check-mode** option **-C** is
provided as example and the usage is explaind in shellia(1).
On **debian** systems this script may be found at
*/usr/share/doc/shellia/examples/hello_world_error*.
The changed script line from the example in chapter **check-mode** is::

  10 ia -C

and gives the following output::

  01 $ ./hello_world_error "shellia user"
  02 --- output with ERROR from <hello_world_error> running <say_hello "shellia user"> ---
  03 e:end of function say_hello
  04 e:exit=5
  05 s:hello
  --- (q)uit (r)edo (i)gnore (s)witch interactive ---
  ?

In line 05 *s:* already classifies the output as *stdout*.
To make the program behave as wanted we can add the folloging lines +a and
+b before script line 09::

  +a ia_ignore "end of function say_hello$"
  +b ia_ignore "^exit=5$"
  09 ia_add "say_hello \"$1\""

debug-mode
----------

The library *ia.debug* includes functions to add **debug-output** to a shell script.
The *script.using.shellia* can then be called with a *debug-runtime-config*
to define the **debug-level** and to select **debug-topics**.
See shellia(1) **debug-mode** to learn about *debug-runtime-config*.

A simple script to add **debug-output** without **debug-level** or **debug-topics** is::

  01 . /usr/share/shellia/ia
  02 say_hello_world()
  03 {
  04   dbg "say_hello function start"
  05   echo "hello world"
  06   dbg "say_hello function end"
  07 }
  08 eval "$ia_init"
  09 ia_add "dbg \"main program\""
  10 ia_add say_hello_world
  11 ia

Line 01, 08, 10 and 11 are already explained in a previous example.
Line 04, 06 and 09 adds **debug-output**.
Because no **debug-level** is defined the lowest possible value *1* is used.

In this script **debug-output** can be turned on when the script is started
with a runtime **debug-level** greater or equal to *1*.

If you want to use **debug-level** *3* in the function, you can change the
following lines::

  04   dbg 3 "say_hello function start"
  06   dbg 3 "say_hello function end"

Now a **debug-runtime-level** of at least 3 is needed to produce output for
the changed lines.

Sometimes it is not enough to have **debug-levels** and
you need **debug-topics** to define which **debug-output** has to be shown.
You can give a free defined **debug-topic** to each *debug-statement* line.
Normally you should only use a limited number of **debug-topics** to make things
not to complicated.

We change 2 lines to the free invented **debug-topics** called *start* and
*end*::

  04   dbg 3 start "say_hello function start"
  06   dbg 3 end "say_hello function end"

The script now should look like the *hello_world_debug* script, that
on **debian** systems may be found at
*/usr/share/doc/shellia/examples/hello_world_debug*.

See shellia(1) **debug-mode** to read about using this script.

log-mode
--------

The library *ia.log* includes functions to add logging to a shell script.

In the logfile each line starts with a number of bars.
Following lines with the same number of bars, result from the same function,
which is used as header.

A simple script with logging called *hello_world_log* is provided as example.
On **debian** systems it may be found at
*/usr/share/doc/shellia/examples/hello_world_log*.
A part of this script is::

  01 ia_logfile="$(mktemp)"
  02 export ia_logfile
  03 . /usr/share/shellia/ia
  04 say_hello()
  05 {
  06   local name
  07   eval "$ia_init"
  08   ia_add "dbg \"function say_hello called with $# arguments\""
  09   ia_add "name=\"$1\""
  10   ia_stdout "^hello"
  11   ia_add "echo \"hello \$name\""
  12   ia -c
  13 }
  14 eval "$ia_init"
  15 ia_add "dbg \"main program begin\""
  16 ia_nocheck # do not check the already checked output of next line
  17 ia_add "say_hello <-i> -- \"$1\""
  18 ia -c

In line 03 the **ia** library is loaded, which also loads **ia.log**.
The logging is simply enabled by setting variable **ia_logfile** in line 01 with the path
to the logfile and exporting it in line 02.
The new logfile output will be appended to the logfile.

To see 2 levels of logging in the logfile we have a main program and a function in the
example.
Both use **check-mode** with *ia -c* in line 07 and 14 which allows one to log checked output.
To not double check the function *say_hello* in the main program line 16 *ia_nocheck*
is added to not check the output of line 17.

The output to the logfile is discussed in shellia(1).

trace-mode
----------

**Trace-mode** can be enabled with *ia -x* for a group of **steps**.
When tracemode is enabled, before each **step** *set -x* is called and after
each stet *set +x* is called.
This way, no **shellia** internal commands are traced.

A simple script with **trace-mode** called *hello_world_trace* is provided as example.
On **debian** systems it may be found at
*/usr/share/doc/shellia/examples/hello_world_trace*.
A part of this script is::

  01 . /usr/share/shellia/ia
  02 write_file() {
  03   eval "$ia_init"
  04   ia_add "echo \"hello $2\" >$1"
  05   ia -c -x
  06 }
  07 show_file() {
  08   eval "$ia_init"
  09   ia_stdout "^hello"
  10   ia_add "cat $1"
  11   ia -c -x
  12 }
  13 eval "$ia_init"
  14 ia_add "file=\"\$(mktemp)\""
  15 ia_add "write_file <-i> -- \"\$file\" \"$1\""
  16 ia_stdout "."
  17 ia_add "show_file <-i> -- \"\$file\""
  18 ia_add "rm -f \"\$file\""
  19 ia -c -x

Without **trace-mode** (without **-x** in line 05 11 and 19) this script would
just print one line starting with *hello*.
The following output is printed when calling ::

  $ /usr/share/doc/shellia/examples/hello_world_trace "shellia user"
  + mktemp
  + file=/tmp/tmp.zCDaHg9dFP
  + echo hello shellia user
  + write_file -- /tmp/tmp.zCDaHg9dFP shellia user
  + cat /tmp/tmp.zCDaHg9dFP
  + show_file -- /tmp/tmp.zCDaHg9dFP
  hello shellia user
  + rm -f /tmp/tmp.zCDaHg9dFP

Because **log-mode** is enabled the following log is written::

  |hello_world_trace
  |+ mktemp
  |+ file=/tmp/tmp.zCDaHg9dFP
  ||hello_world_trace/write_file
  ||+ echo hello shellia user
  |+ write_file -- /tmp/tmp.zCDaHg9dFP shellia user
  ||hello_world_trace/show_file
  ||+ cat /tmp/tmp.zCDaHg9dFP
  ||s:hello shellia user
  |+ show_file -- /tmp/tmp.zCDaHg9dFP
  |s:hello shellia user
  |+ rm -f /tmp/tmp.zCDaHg9dFP

The log shows, that **trace-mode** output is included in the
**log-mode** output hierarchy.
To get **trace-mode** output in a logfile, it is important
that **log-mode** and also **check-mode** is enabled.

Also if **no-check** is used for a **step**, this step
will not create **trace-mode** output in the logfile.

variable in Interactive-mode
----------------------------

If you want to enable the user to see the value of a variable,
before it is used, with key press **v**, you can write
*\$<var>* instead of *\\\$var* or *\\\${var}*.

high level functions
====================

ia_ssh
------

  **ia_add** "**ia_ssh** *ssh-options* [**--fd** *fd*] [**--vars** *vars*] *destination* *shellia-script*"

Options::

  <ssh-options>     ssh options described in ssh(1)
  <fd>              the filedescriptors <fd> will be transferred
  <vars>            the variables <vars> will be provided
  <destination>     ssh destination described in ssh(1)
  <shellia-script>  script that can use the transferred filedescriptors
                    and variables

**ia_ssh** transfers additional to the filedescriptors **stderr** and **stdout** the shellia
filescriptors **$ia_direct_stderr_fd** and **$ia_log_fd** with **ssh**.
Also the variables listed in **$ia_inherit_vars** are provided to the remote *shellia-script*.

With **$ia_log_fd** the remote *shellia-script* does not need to use an own logfile
and the log can be written to the local logfile.

Warning:
The order of data transferred by filedescriptors may change within a single shellia step.


PREFIX
======

Commands or global variables brought by **shellia** normally start with **ia** (see **NAME** in shellia(7)).
If **ia.debug** is used there will also be commands and global variables that start with *dbg*.
But if you already use one or both of this prefixes, you can change them, while loading
the library.

The example changes **ia** to *mycmd* and *dbg* to *myprnt*::

  ia_prefix=mycmd
  dbg_prefix=myprnt
  . /usr/share/shellia/ia

Now the new prefixes have to be used, e.g. *ia_add* is now *mycmd_add*.

WARNING
=======

It is not always possible to separate **sellia**'s internal variables from script variables.
For this reason variable names starting with **ia_** should not be used in scripts.

SEE ALSO
========

shellia(1), shellia(7), gawk(1)
