The Only Sh-bang

Table of Contents

1 The Only Sh-Bang You'll Ever Need

1.1 Abstract   ln family

A method to avoid the un-necessary proliferation of the sh-bang in shell scripts is presented. A single shell script may be copied or, more favorably, linked (as in the Unix ln(1) sense) to any single command name which is the parent name of a family of functions.

This method promotes turning function libraries into an app. The family name becomes the executable, and it's sub-functions are called as the first argument to the command.

1.2 The code   shbang

This code is the one executable script required in the method. Here's the current scenario:

$ ln -f sh-bang cmmd   # eg. ftp, but once, then
$ ...                  # any time later
$ ftp cmmd             # e.g. run

The runtime app, a collection of all the necessary functions was previously built in this way:

$ app_for ftp          # writes ftp_app, followed by
$ ln -f sh-bang ftp  

In the case of ftp this works, since the system command is now the secure ftp, namely sftp. Then to run the command in any directory,

ftp run                # is directory-smart, having set up
ftp common             # which returns the destination directory

These commands execute functions from the ftp_app collection, which is sourced in the ftp script, the code below. The functions exist in a command line available library, probably, but not necessarily ftplib. In this case, the functions are named ftp_run, ftp_common.

1.2.1 executable

The one executable script, sh-bang, which is the link to the various family names.

#!/usr/bin/env bash

app_trace   () { read -p "$*;  more? "  < /dev/tty; }
functionsin ()
{
    : list the family functions in a conforming APP
    : app_trace $*
    grep "^${1}_" ${1}_app | sed "

        # replace family name w/ 4 spaces
        s/^${1}_/    /   

        # trim the (), leaving only names
        s/ *().*$//
    ";
}

# app_trace 0 $0 $@

# app_trace 1 ${0}_app ${0}_${1} ${*:2}

set -- ${0}_app $(basename ${0}_${1}) ${*:2}

# app_trace 2 $*

app=$(which $1); shift

# app_trace WHICH $app $*

[[ -f ${app:-/dev/null} ]] || {

    echo "No APPLICATION for [which] $1"
    exit 1
}

# app_trace 4 source $app

source $app

declare -f $1 > /dev/null || {

    printf "No Function $1 in $app. These are available:\n"
    functionsin $(basename $0)
    exit 2
}

# app_trace "$ $@"

"$@"

This script may be linked to any name which adhere's to these requirements:

  1. relies on a function library named {script}_app
  2. the library built to satisfy all function dependencies,
  3. which has functions named {script}_[something] …
  4. any of which may take arguments,
  5. and only call functions in the _app (satified by 2nd requirement)

Since the script name is the zero argument, the set builtin is used to assign these positional parameters,

  1. {script}_app
  2. app_{first arg}
  3. subsequent args

The workhorse here is the $@ at the end of the script. It expands into:

"$1" "$2" ...

or, in the example: command args ... Here is a discussion of Shell Parameter Expansion.

This code requires preparation of a run-time library, since it sources an app, configured to contain only the necessary functions.

1.2.2 library

At the moment, two libraries support these two commands. It seems possible to unite them in a single library.

  • first banglib, supporting sh-bang:
    • bang_doc – points to this paragraph
    • bang_test – exercises some of the functions, a demonstration
    • bang_iam – links the name (with supporting application) as a sh-bang script
    • bang_files – bang resources, including local files, n.b .banger.log
    • bang_saved – using logged bang files, updates link to the master sh-bang.
    • bang_who – default concatenates the banged list.

In order to create a bang-eligible library, see Apps from Libs

1.3 The Portable plan

In order to take full of advantage of the run command, we need a portable function to either return an error code from the function, or exit from the script. This will take a little cogitation, since it seems unlikely to replace the behavior of the return built-in. Is there a return-up means?

If so, a function, the name returnOrExit seems apt. Called as an execution vector: $( returnOrExit ) returns either return or exit, so, when executed, either exit from the shell script or return from the function.

It uses running to detecting a main (command-line) command at the top of the call stack.

Before settling on the running_eg, here are a few experiments:

... report_notfile ${1:-"no file arg"} && { returnOrExit 1 || return 1; }

Or

... report_notfile ${1:-"no file arg"} && { running && exit 1 || return 1; }

And what we've arrived at.

... report_notfile ${1:-"no file arg"} && $(returnOrExit) 1

What remains is testing the set -E option; it may be possible to avoid the returnOrExit altogether, and simply return N (non-zero) if I understand that the error return may be propagated up the call stack.

1.4 Functions

Functions appearing here:

functions
not
replace
report_notfile
running
running_eg
shell
source
usage

Before settling on the running_eg, here are a few experiments:

... report_notfile ${1:-"no file arg"} && { returnOrExit 1 || return 1; }

Or

... report_notfile ${1:-"no file arg"} && { running && exit 1 || return 1; }

And what we've arrived at.

... report_notfile ${1:-"no file arg"} && $(returnOrExit) 1

What remains is testing the set -E option; it may be possible to avoid the returnOrExit altogether, and simply return N (non-zero) if I understand that the error return may be propagated up the call stack.

Author: Marty McGowan

Created: 2019-05-31 Fri 12:15

Validate