cbugk's doctrine of writing busybox and bash scripts.
- Clone this repo and navigate in.
- Under cmd:
- Create a directory for your program and add Main.sh. Add env.sh if you would like to seperate variables from Main.sh
- Under fcn:
- Add your own function definitions. Remember to export functions.
- Then, bind your program into a monoscript and optionally run it.
git clone https://github.com/cbugk/busybash
cd busybash
mkdir cmd/hello
# Mind the tab characters and <<-
cat > cmd/hello/Main.sh <<-MAIN
#!/bin/bash
function Main() {
printf 'Hello World!\n'
printf "This is ${WHO}\n"
}; export -f Main;
MAIN
# Mind that ${USER} will be evaluated
cat > cmd/hello/env.sh <<-ENVSH
WHO=${USER}
ENVSH
chmod u+x ./bb/bind.sh # make executable
./bb/build.sh cmd/hello
./out/hello.sh
Shell scripting is a tedious work when performed without structure. Even then it lacks the ease of use, which compilers provide for programing languages.
busybash repository aims to set a standard for writing, sharing, and deploying my (cbugk) shell scripts. For instance, providing traceback and immediate halt of execution are essential.
I tried using ash and busybox only. Unfortunately, bashisms are, let alone
convienient, straight necessary to be able to use
declare -n alias="${original_var}"
by-reference variable passing or
export -f
function exports.
However, I do not think this is the case for GNU Utils; busybox should be small, portable, and standard enough.
Alpine-based containers are the base target. However, embedded linux systems can also benefit from this approach.
Note that, user code is not subject to alpine/busybox restriction, so GNU core-utils divergences can be used on custom functions.
Roles of directories:
bb
: Stores busybash commandsbb/core
: Stores shell options, traps, and templates for creating monoscripts.
cmd
: Stores program entry point functions.- Imported at the end of generated scripts.
main.bash
should be namespaced by resulting monoscript's name in case of multiple different scripts.env.sh
file besideMain.sh
will be concatenated within and at the top of main function. Can be used to override genericenv.sh
file under sct.
fcn
: Stores any user function.- Scripts are imported by path, directory trees represent namespacing.
- Functions and variables should conform to naming schema.
- Scripts should only define and export a function.
sct
: Stores conventional scripts.- Should be snake_case and have all lowercase letters.
env.sh
should reside here and only define global variables.
out
: Stores bound monoscripts.- filename is the same as parent directory of entrypoint script
(e.g.
Main.sh
)
- filename is the same as parent directory of entrypoint script
(e.g.
Roles of bb
scripts:
bind.sh
: Generates self-contained scripts frommain
functions.run.sh
: Executes provided main function with dependencies sourced and shell arguments passed.test.sh
: Checks unit test of a single function.
Following are to overcome short-comings of shell scripting at the expense of long variable/function names. These are my (cbugk) personal preferences, betterments are welcome.
- File names:
- Script files must have the same name as defined function without its namespace. Namespacing is done through directory structure.
- Script file extensions must be
.sh
. - Conventional scripts are placed under sct and can be namespaced.
- Conventional scripts should have a descriptive name for their task. It is suggested that, these only be used sparsely and instead _function_s be utilized.
- Globals:
- Globals are discouraged. Using globals to pass values is further
discouraged. Exceptions are following, are expected to not change through
out the programs life.
- Functions.
- __CONSTANT__s, either from shell or within
main()
.
- Any variable which needs to be used by another function must be
explicitly provided as an out-parameter into it. They are by-reference
variable aliases or expansion indirection:
var1=foobar; declare -n alias1=var1; printf "${alias1}\n"
var1=foobar; var2='var1'; printf "${!var2}\n
)
- Globals are discouraged. Using globals to pass values is further
discouraged. Exceptions are following, are expected to not change through
out the programs life.
- Values/Properties:
- Names are snake_case with lower-case characters only, e.g.
foo_bar="baz";
.- Use of two consecutive underscores (i.e.
__
) is reserved for namespacing thus prohibited. - Use of leading underscore (e.g.
_foo_bar
) is forbidden for namespacing as well.
- Use of two consecutive underscores (i.e.
- Variables must be expanded with
"${}"
, braces are obligatory.- This allows use of more than 9 parameters without
shift
in while loops, i.e."${10}"
.
- This allows use of more than 9 parameters without
- Each and every variable expansion must have quotation.
declare -r foo="${2}bar"
, notfoo=$2bar
.- This is to make sure no empty expansion goes unnoticed and changes position or arguments of functions.
- Every property (variable) is declared with
declare
/local
.- With
-i
flag for integers. - With
-r
flag for read-only values. CONSTANT is not appropriate. - With
-n
flag for (pass-by-reference) out-parameters. - See GNU BASH Docs for more
- With
- Names must be uniqely identifiable. Namespacing is highly encouraged.
- e.g.
baz__foo_bar
wherebaz
is the namespace and__
is variable namespace seperator.
- e.g.
- Names are snake_case with lower-case characters only, e.g.
- Functions:
- Names are PascalCase e.g.
FooBar
, however, namespaces exceptionally are seperated by underscore e.g.Abc_FooBar
. - Functions are declared with function keyword and () is
adjacent, whereas brace opening is not.
- e.g.
function FooBar() {
- e.g.
- To ensure subprocess function calls work, every function is exported right
after being declared. Same holds for nested functions, if any.
- e.g.
}; export -f FooBar;
.
- e.g.
- Names must be uniquely identifiable. Namespacing is highly encouraged.
- e.g.
function Abc_FooBar() {
whereAbc
is the namespace and_
is function namespace seperator.
- e.g.
- Properties should be namespaced with the owner function, e.g.
abc_foobar__baz
.- Although a mouthful, this overcomes unintentionally overwriting other functions' properties.
abc_foobar
is the namespace for the variableabc_foobar__baz
, whereasAbc
is the namespace for the owner functionAbc_FooBar
.
local
is preffered overdeclare
for function properties. Not a requirement though.- Tests are created alongside their respective functions.
- Names should be prefixed with TEST_.
- e.g.
function TEST_Abc_FooBar() {
forfunction Abc_FooBar() {
.
- e.g.
- Prefix is in capitals to distinguish from namespaces.
- Names should be prefixed with TEST_.
- Names are PascalCase e.g.
- Subprocesses:
- Use of
`command`
is highly discouraged, instead use"$(some_command "$(another_command)")"
for readability and nestability
- Use of
- Redirections:
- Place a space or a dash between here-doc and termination word.
- To preserve initial tab characters, use
<< EOF
. - To remove tab characters and be able to allign code, use
<<-EOF
- To preserve initial tab characters, use
- For here-doc termination word, try to avoid
EOF
and use something descriptive instead.- e.g.
<<-SCRIPT
,<< CSV
,<<-MOCK_DATA
.
- e.g.
- Place a space or a dash between here-doc and termination word.
Remember, this is a shell scripting guide at best. Ideas here are an amalgam of others'. Suggestions, issues, and merge requests are welcome.