There are scripts which we use over and over again in different projects. As they might be usable for you as well, we are publishing them here. Feel free to use it and report bugs if you should find one.
❗ You are taking a sneak peek at the next version. Please have a look at the README of the git tag in case you are looking for the documentation of the corresponding version. For instance, the README of v0.17.1.
Table of Content
We recommend you pull the scripts with the help of gget.
Alternatively you can
the sources.
Following the commands you need to execute to setup tegonal scripts via gget.
gget remote add -r tegonal-scripts -u https://github.com/tegonal/scriptsNow you can pull the scripts you want via:
export TEGONAL_SCRIPTS_VERSION="v0.17.1"
gget pull -r tegonal-scripts -t "$TEGONAL_SCRIPTS_VERSION" -p ...Note that dependencies have to be pulled manually and almost all scripts depend on src/setup.sh
which in turn depends on src/utility/source-once.sh and this one depends on src/utility/log.sh.
Many of the scripts depend on further scripts located in src/utility.
Therefore, for simplicity reasons, we recommend you pull src/setup.sh all files of src/utility in addition:
export TEGONAL_SCRIPTS_VERSION="v0.17.1"
gget pull -r tegonal-scripts -t "$TEGONAL_SCRIPTS_VERSION" -p src/setup.sh
gget pull -r tegonal-scripts -t "$TEGONAL_SCRIPTS_VERSION" -p src/utility/
We recommend you use the following code at the beginning of your script in case you want to source a file/function
(in the example below we want to use tegonal's io functions located in utility/io.sh):
#!/usr/bin/env bash
set -euo pipefail
shopt -s inherit_errexit
if ! [[ -v dir_of_tegonal_scripts ]]; then
# Assumes your script is in (root is project folder) e.g. /src or /scripts and
# the tegonal scripts have been pulled via gget and put into /lib/tegonal-scripts
dir_of_tegonal_scripts="$(cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" >/dev/null && pwd 2>/dev/null)/../lib/tegonal-scripts/src"
source "$dir_of_tegonal_scripts/setup.sh" "$dir_of_tegonal_scripts"
fi
sourceOnce "$dir_of_tegonal_scripts/utility/io.sh"Note that source "$dir_of_tegonal_scripts/setup.sh" will automatically source utility/source-once.sh and utility/log.sh
The scripts are ordered by topic:
The scripts under this topic (in directory ci) help out in performing CI steps.
Installs shellcheck v0.8.0.
Most likely used together with runShellcheck.
Following an example:
# run the install-shellcheck.sh in your github/gitlab workflow
# for instance, assuming you fetched this file via gget and remote name is tegonal-scripts
# then in a github workflow you would have
jobs:
steps:
- name: install shellcheck v0.8.0
run: ./lib/tegonal-scripts/src/ci/install-shellcheck.sh
# and most likely as well
- name: run shellcheck
run: ./scripts/run-shellcheck.sh# run the install-shellcheck.sh in your github/gitlab workflow
# for instance, assuming you fetched this file via gget and remote name is tegonal-scripts
# then in a github workflow you would have
jobs:
steps:
- name: install shellspec v0.28.1
run: ./lib/tegonal-scripts/src/ci/install-shellspec.sh
# and most likely as well
- name: run shellspec
run: shellspecThe scripts under this topic (in directory qa) perform checks or execute qa tools.
A function which expects the name of an array of dirs as first argument and a source path as second argument (which is passed to shellcheck via -P parameter). It then executes shellcheck for each *.sh in these directories with predefined settings for shellcheck.
#!/usr/bin/env bash
set -euo pipefail
shopt -s inherit_errexit
# Assumes tegonal's scripts were fetched with gget - adjust location accordingly
dir_of_tegonal_scripts="$(cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" >/dev/null && pwd 2>/dev/null)/../lib/tegonal-scripts/src"
source "$dir_of_tegonal_scripts/setup.sh" "$dir_of_tegonal_scripts"
source "$dir_of_tegonal_scripts/qa/run-shellcheck.sh"
# shellcheck disable=SC2034
declare -a dirs=(
"$dir_of_tegonal_scripts"
"$dir_of_tegonal_scripts/../scripts"
"$dir_of_tegonal_scripts/../spec"
)
declare sourcePath="$dir_of_tegonal_scripts"
runShellcheck dirs "$sourcePath"The scripts under this topic (in directory releasing) perform some steps of your release process.
Updates the version used in download badges and in the sneak peek banner. Requires that you follow one of the following schemas for the download badges:
[](<ANY_URL>/v0.2.0)
[](<ANY_URL>=v0.2.0)
And it searches for the following text for the sneak peek banner:
For instance, the [README of <YOUR_VERSION>](<ANY_URL>/tree/<YOUR_VERSION>/...)
Help:
Parameters:
-v the version which shall be used
-f|--file (optional) the file where search & replace shall be done -- default: ./README.md
-p|--pattern (optional) pattern which is used in a perl command (separator /) to search & replace additional occurrences. It should define two match groups and the replace operation looks as follows: \${1}$version\${2}
--help prints this help
--version prints the version of this script
Examples:
# update version for ./README.md
update-version-README.sh -v v0.1.0
# update version for ./docs/index.md
update-version-README.sh -v v0.1.0 -f ./docs/index.md
# update version for ./README.md
# also replace occurrences of the defined pattern
update-version-README.sh -v v0.1.0 -p "(VERSION=['\"])[^'\"]+(['\"])"
INFO: Version of update-version-README.sh is:
v0.18.0-SNAPSHOT
Full usage example:
#!/usr/bin/env bash
set -euo pipefail
shopt -s inherit_errexit
# Assumes tegonal's scripts were fetched with gget - adjust location accordingly
dir_of_tegonal_scripts="$(cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" >/dev/null && pwd 2>/dev/null)/../lib/tegonal-scripts/src"
source "$dir_of_tegonal_scripts/setup.sh" "$dir_of_tegonal_scripts"
"$dir_of_tegonal_scripts/releasing/update-version-README.sh" -v 0.1.0
# if you use it in combination with other tegonal-scripts files, then you might want to source it instead
sourceOnce "$dir_of_tegonal_scripts/releasing/update-version-README.sh"
# and then call the function
updateVersionReadme -v 0.2.0Sets the version placed before the Description section accordingly.
Help:
Parameters:
-v the version which shall be used
-d|--directory (optional) the working directory in which *.sh are searched (also in subdirectories) / you can also specify a file -- default: ./src
-p|--pattern (optional) pattern which is used in a perl command (separator /) to search & replace additional occurrences. It should define two match groups and the replace operation looks as follows: \${1}$version\${2}
--help prints this help
--version prints the version of this script
Examples:
# update version to v0.1.0 for all *.sh in ./src and subdirectories
update-version-scripts.sh -v v0.1.0
# update version to v0.1.0 for all *.sh in ./scripts and subdirectories
update-version-scripts.sh -v v0.1.0 -d ./scripts
# update version to v0.1.0 for all *.sh in ./src and subdirectories
# also replace occurrences of the defined pattern
update-version-scripts.sh -v v0.1.0 -p "(VERSION=['\"])[^'\"]+(['\"])"
INFO: Version of update-version-scripts.sh is:
v0.18.0-SNAPSHOT
Full usage example:
#!/usr/bin/env bash
set -euo pipefail
shopt -s inherit_errexit
# Assumes tegonal's scripts were fetched with gget - adjust location accordingly
dir_of_tegonal_scripts="$(cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" >/dev/null && pwd 2>/dev/null)/../lib/tegonal-scripts/src"
source "$dir_of_tegonal_scripts/setup.sh" "$dir_of_tegonal_scripts"
"$dir_of_tegonal_scripts/releasing/update-version-scripts.sh" -v 0.1.0
# if you use it in combination with other tegonal-scripts files, then you might want to source it instead
sourceOnce "$dir_of_tegonal_scripts/releasing/update-version-README.sh"
# and then call the function
updateVersionReadme -v 0.2.0Utility to comment/uncomment sections defined via
<!-- for main -->
...
<!-- for main end -->
and
<!-- for release -->
...
<!-- for release end -->
depending on the passed command:
- Passing
mainwill uncomment main sections and comment release sections. - Passing
releasewill uncomment release sections and comment main sections.
Help:
Parameters:
-c|--command either 'main' or 'release'
-f|--file (optional) the file where search & replace shall be done -- default: ./README.md
--help prints this help
--version prints the version of this script
Examples:
# comment the release sections in ./README.md and uncomment the main sections
toggle-sections.sh -c main
# comment the main sections in ./docs/index.md and uncomment the release sections
toggle-sections.sh -c release -f ./docs/index.md
INFO: Version of toggle-sections.sh is:
v0.18.0-SNAPSHOT
Full usage example:
#!/usr/bin/env bash
set -euo pipefail
shopt -s inherit_errexit
# Assumes tegonal's scripts were fetched with gget - adjust location accordingly
dir_of_tegonal_scripts="$(cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" >/dev/null && pwd 2>/dev/null)/../lib/tegonal-scripts/src"
source "$dir_of_tegonal_scripts/setup.sh" "$dir_of_tegonal_scripts"
"$dir_of_tegonal_scripts/releasing/toggle-sections.sh" -c main
# if you use it in combination with other files, then you might want to source it instead
sourceOnce "$dir_of_tegonal_scripts/releasing/toggle-sections.sh"
# and then call the function
toggleSections -c releaseIn case you use a sneak peek banner as we do in this repo, then this script can be used to hide it (before tagging) and show it again in the new dev cycle.
Help:
Parameters:
-c|--command either 'show' or 'hide'
-f|--file (optional) the file where search & replace shall be done -- default: ./README.md
--help prints this help
--version prints the version of this script
Examples:
# hide the sneak peek banner in ./README.md
sneak-peek-banner.sh -c hide
# show the sneak peek banner in ./docs/index.md
sneak-peek-banner.sh -c show -f ./docs/index.md
INFO: Version of sneak-peek-banner.sh is:
v0.18.0-SNAPSHOT
Full usage example:
#!/usr/bin/env bash
set -euo pipefail
shopt -s inherit_errexit
# Assumes tegonal's scripts were fetched with gget - adjust location accordingly
dir_of_tegonal_scripts="$(cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" >/dev/null && pwd 2>/dev/null)/../lib/tegonal-scripts/src"
source "$dir_of_tegonal_scripts/setup.sh" "$dir_of_tegonal_scripts"
"$dir_of_tegonal_scripts/releasing/sneak-peek-banner.sh" -c hide
# if you use it in combination with other files, then you might want to source it instead
sourceOnce "$dir_of_tegonal_scripts/releasing/sneak-peek-banner.sh"
# and then call the function
sneakPeekBanner -c showScript which releases a version for a repository containing files which don't need to be compiled or packaged. It is based on some conventions (see src/releasing/release-files.sh for more details):
- expects a version in format vX.Y.Z(-RC...)
- main is your default branch
- requires you to have a /scripts folder in your project root which contains:
- before-pr.sh which provides a parameterless function
beforePrand can be sourced (add${__SOURCED__:+return}before executingbeforePr) - prepare-next-dev-cycle.sh which provides function
prepareNextDevCyclewith parameters-vfor version and-pfor additionalPattern (see source for more detail). Also this file needs to be sourcable.
- before-pr.sh which provides a parameterless function
- there is a public key defined at .gget/signing-key.public.asc which will be used to verify the signatures which will be created
It then includes the following steps:
- some checks regarding git status
beforePr- rewrite sneak-peek banner
- toggle main/release sections in README
- update version in download badges and sneak-peek banner in README as well as
replace additional occurrences defined via-p|--pattern(see output of--helpfurther below for further details) - update version in script headers in /src and /scripts of your project
- (optional) sources ./scripts/additional-release-files-preparations.sh if it exists
beforePr- sign files via GPG where you define which files via
--sign-fn - commit
prepareNextDevCycle- (optional) sources ./scripts/additional-prepare-files-next-dev-cycle-steps.sh if it exists
- push changes
- tag and push tag
Useful if you want to release e.g. scripts which can then be fetched via gget.
Note, if your beforePr or your additional steps modifies beforePr or a file it depends on, then you need to source those files manually in your additional steps.
Help:
Parameters:
-v The version to release in the format vX.Y.Z(-RC...)
-k|--key The GPG private key which shall be used to sign the files
--sign-fn Function which is called to determine what files should be signed. It should be based find and allow to pass further arguments (we will i.a. pass -print0)
--project-dir (optional) The projects directory -- default: .
-p|--pattern (optional) pattern which is used in a perl command (separator /) to search & replace additional occurrences. It should define two match groups and the replace operation looks as follows: \${1}$version\${2}
-nv|--next-version (optional) the version to use for prepare-next-dev-cycle -- default: is next minor based on version
--prepare-only (optional) defines whether the release shall only be prepared (i.e. no push, no tag, no prepare-next-dev-cycle) -- default: false
--help prints this help
--version prints the version of this script
INFO: Version of release-files.sh is:
v0.18.0-SNAPSHOT
Full usage example:
#!/usr/bin/env bash
set -euo pipefail
shopt -s inherit_errexit
# Assumes tegonal's scripts were fetched with gget - adjust location accordingly
dir_of_tegonal_scripts="$(cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" >/dev/null && pwd 2>/dev/null)/../lib/tegonal-scripts/src"
source "$dir_of_tegonal_scripts/setup.sh" "$dir_of_tegonal_scripts"
function findScripts() {
find "src" -name "*.sh" -not -name "*.doc.sh" "$@"
}
# make the function visible to release-files.sh / not necessary if you source release-files.sh, see further below
declare -fx findScripts
# releases version v0.1.0 using the key 0x945FE615904E5C85 for signing
"$dir_of_tegonal_scripts/releasing/release-files.sh" -v v0.1.0 -k "0x945FE615904E5C85" --sign-fn findScripts
# releases version v0.1.0 using the key 0x945FE615904E5C85 for signing and
# searches for additional occurrences where the version should be replaced via the specified pattern in:
# - script files in ./src and ./scripts
# - ./README.md
"$dir_of_tegonal_scripts/releasing/release-files.sh" \
-v v0.1.0 -k "0x945FE615904E5C85" --sign-fn findScripts \
-p "(TEGONAL_SCRIPTS_VERSION=['\"])[^'\"]+(['\"])"
# in case you want to provide your own release.sh and only want to do some pre-configuration
# then you might want to source it instead
sourceOnce "$dir_of_tegonal_scripts/releasing/release-files.sh"
# and then call the function with your pre-configuration settings:
# here we define the function which shall be used to find the files to be signed
# since "$@" follows afterwards, one could still override it via command line arguments.
# put "$@" first, if you don't want that a user can override your pre-configuration
releaseFiles --sign-fn findScripts "$@"The scripts under this topic (in directory utility) are useful for bash programming as such.
Utility functions when dealing with arrays.
#!/usr/bin/env bash
# shellcheck disable=SC2034
set -euo pipefail
shopt -s inherit_errexit
# Assumes tegonal's scripts were fetched with gget - adjust location accordingly
dir_of_tegonal_scripts="$(cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" >/dev/null && pwd 2>/dev/null)/../lib/tegonal-scripts/src"
source "$dir_of_tegonal_scripts/setup.sh" "$dir_of_tegonal_scripts"
sourceOnce "$dir_of_tegonal_scripts/utility/array-utils.sh"
declare regex
regex=$(joinByChar '|' my regex alternatives)
declare -a commands=(add delete list config)
regex=$(joinByChar '|' "${commands[@]}")
joinByString ', ' a list of strings
declare -a names=(alwin darius fabian mike mikel robert oliver thomas)
declare employees
employees=$(joinByString ", " "${names[@]}")
echo ""
echo "Tegonal employees are currently: $employees"
function startingWithA() {
[[ $1 == a* ]]
}
declare -a namesStartingWithA=()
arrFilter names namesStartingWithA startingWithA
declare -p namesStartingWithA
declare -a everySecondName
arrTakeEveryX names everySecondName 2 0
declare -p everySecondName
declare -a everySecondNameStartingFrom1
arrTakeEveryX names everySecondNameStartingFrom1 2 1
declare -p everySecondNameStartingFrom1
arrStringEntryMaxLength names # 6Utility functions to interact with the user.
#!/usr/bin/env bash
set -euo pipefail
shopt -s inherit_errexit
# Assumes tegonal's scripts were fetched with gget - adjust location accordingly
dir_of_tegonal_scripts="$(cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" >/dev/null && pwd 2>/dev/null)/../lib/tegonal-scripts/src"
source "$dir_of_tegonal_scripts/setup.sh" "$dir_of_tegonal_scripts"
sourceOnce "$dir_of_tegonal_scripts/utility/ask.sh"
if askYesOrNo "shall I say hello"; then
echo "hello"
fiUtility functions which check some conditions like is passed arg the correct type etc.
#!/usr/bin/env bash
set -euo pipefail
shopt -s inherit_errexit
# Assumes tegonal's scripts were fetched with gget - adjust location accordingly
dir_of_tegonal_scripts="$(cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" >/dev/null && pwd 2>/dev/null)/../lib/tegonal-scripts/src"
source "$dir_of_tegonal_scripts/setup.sh" "$dir_of_tegonal_scripts"
sourceOnce "$dir_of_tegonal_scripts/utility/checks.sh"
function foo() {
# shellcheck disable=SC2034
local -rn arr=$1
local -r fn=$2
# resolves arr recursively via recursiveDeclareP and check that is a non-associative array
checkArgIsArray arr 1 # same as exitIfArgIsNotArray if set -e has an effect on this line
checkArgIsFunction "$fn" 2 # same as exitIfArgIsNotFunction if set -e has an effect on this line
function describeTriple(){
echo >&2 "array contains 3-tuples with names where the first value is the first-, the second the middle- and the third the lastname"
}
# check array with 3-tuples
checkArgIsArrayWithTuples arr 3 "names" 1 describeTriple
exitIfArgIsNotArray arr 1
exitIfArgIsNotFunction "$fn" 2
function describePair(){
echo >&2 "array contains 2-tuples with names where the first value is the first-, and the second the lastname"
}
# check array with 2-tuples
exitIfArgIsNotArrayWithTuples arr 2 "names" 1 describePair
}
if checkCommandExists "cat"; then
echo "do whatever you want to do..."
fi
# give a hint how to install the command
checkCommandExists "git" "please install it via https://git-scm.com/downloads"
# same as checkCommandExists but exits instead of returning non-zero in case command does not exist
exitIfCommandDoesNotExist "git" "please install it via https://git-scm.com/downloads"
# meant to be used in a file which is sourced where a contract exists between the file which `source`s and the sourced file
exitIfVarsNotAlreadySetBySource myVar1 var2 var3Utility functions around git.
#!/usr/bin/env bash
set -euo pipefail
shopt -s inherit_errexit
# Assumes tegonal's scripts were fetched with gget - adjust location accordingly
dir_of_tegonal_scripts="$(cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" >/dev/null && pwd 2>/dev/null)/../lib/tegonal-scripts/src"
source "$dir_of_tegonal_scripts/setup.sh" "$dir_of_tegonal_scripts"
sourceOnce "$dir_of_tegonal_scripts/utility/git-utils.sh"
declare currentBranch
currentBranch=$(currentGitBranch)
echo "current git branch is: $currentBranch"
if hasGitChanges; then
echo "do whatever you want to do..."
fi
if localGitIsAhead "main"; then
echo "do whatever you want to do..."
elif localGitIsAhead "main" "anotherRemote"; then
echo "do whatever you want to do..."
fi
if localGitIsBehind "main"; then
echo "do whatever you want to do..."
elif localGitIsBehind "main"; then
echo "do whatever you want to do..."
fi
if hasRemoteTag "v0.1.0"; then
echo "do whatever you want to do..."
elif hasRemoteTag "v0.1.0" "anotherRemote"; then
echo "do whatever you want to do..."
fi
echo "all existing tags on remote origin, starting from smallest to biggest version number"
remoteTagsSorted
# if you specify the name of the remote, then all additional arguments are passed to `sort` which is used internally
echo "all existing tags on remote upstream, starting from smallest to biggest version number"
remoteTagsSorted upstream -r
declare latestTag
latestTag=$(latestRemoteTag)
echo "latest tag on origin: $latestTag"
latestTag=$(latestRemoteTag upstream)
echo "latest tag on upstream: $latestTag"Utility functions which hopefully make it easier for you to deal with gpg
#!/usr/bin/env bash
set -euo pipefail
shopt -s inherit_errexit
# Assumes tegonal's scripts were fetched with gget - adjust location accordingly
dir_of_tegonal_scripts="$(cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" >/dev/null && pwd 2>/dev/null)/../lib/tegonal-scripts/src"
source "$dir_of_tegonal_scripts/setup.sh" "$dir_of_tegonal_scripts"
sourceOnce "$dir_of_tegonal_scripts/utility/gpg-utils.sh"
# import public-key.asc into gpg store located at ~/.gpg but ask for confirmation first
importGpgKey ~/.gpg ./public-key.asc --confirmation=true
# import public-key.asc into gpg store located at ~/.gpg and trust automatically
importGpgKey ~/.gpg ./public-key.asc --confirmation=false
# import public-key.asc into gpg store located at .gget/.gpg and trust automatically
importGpgKey .gget/.gpg ./public-key.asc --confirmation=false
# trust key which is identified via info.com in gpg store located at ~/.gpg
trustGpgKey ~/.gpg info.com#!/usr/bin/env bash
set -euo pipefail
shopt -s inherit_errexit
# Assumes tegonal's scripts were fetched with gget - adjust location accordingly
dir_of_tegonal_scripts="$(cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" >/dev/null && pwd 2>/dev/null)/../lib/tegonal-scripts/src"
source "$dir_of_tegonal_scripts/setup.sh" "$dir_of_tegonal_scripts"
sourceOnce "$dir_of_tegonal_scripts/utility/http.sh"
# downloads https://.../signing-key.public.asc and https://.../signing-key.public.asc.sig and verifies it with gpg
wgetAndVerify "https://github.com/tegonal/gget/.gget/signing-key.public.asc"#!/usr/bin/env bash
set -euo pipefail
shopt -s inherit_errexit
# Assumes tegonal's scripts were fetched with gget - adjust location accordingly
dir_of_tegonal_scripts="$(cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" >/dev/null && pwd 2>/dev/null)/../lib/tegonal-scripts/src"
source "$dir_of_tegonal_scripts/setup.sh" "$dir_of_tegonal_scripts"
sourceOnce "$dir_of_tegonal_scripts/utility/io.sh"
function readFile() {
cat "$1" >&3
echo "reading from 4 which was written to 3"
local line
while read -u 4 -r line; do
echo "$line"
done
}
# creates file descriptors 3 (output) and 4 (input) based on temporary files
# executes readFile and closes the file descriptors again
withCustomOutputInput 3 4 readFile "my-file.txt"
# First tries to set chmod 777 to the directory and all files within it and then deletes the directory
deleteDirChmod777 ".git"Utility functions to log messages including a severity level where logError writes to stderr
#!/usr/bin/env bash
set -euo pipefail
shopt -s inherit_errexit
# Assumes tegonal's scripts were fetched with gget - adjust location accordingly
dir_of_tegonal_scripts="$(cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" >/dev/null && pwd 2>/dev/null)/../lib/tegonal-scripts/src"
source "$dir_of_tegonal_scripts/setup.sh" "$dir_of_tegonal_scripts"
sourceOnce "$dir_of_tegonal_scripts/utility/log.sh"
logInfo "hello %s" "world"
# INFO: hello world
logInfo "line %s" 1 2 3
# INFO: line 1
# INFO: line 2
# INFO: line 3
logWarning "oho..."
# WARNING: oho...
logError "illegal state..."
# ERROR: illegal state...
seconds=54
logSuccess "import finished in %s seconds" "$seconds"
# SUCCESS: import finished in 54 seconds
die "fatal error, shutting down"
# ERROR: fatal error, shutting down
# exit 1
returnDying "fatal error, shutting down"
# ERROR: fatal error, shutting down
# return 1
# in case you don't want a newline at the end of the message, then use one of
logInfoWithoutNewline "hello"
# INFO: hello%
logWarningWithoutNewline "be careful"
logErrorWithoutNewline "oho"
logSuccessWithoutNewline "yay"
traceAndDie "fatal error, shutting down"
# ERROR: fatal error, shutting down
#
# Stacktrace:
# foo @ /opt/foo.sh:32:1
# bar @ /opt/bar.sh:10:1
# ...
# exit 1
traceAndReturnDying "fatal error, shutting down"
# ERROR: fatal error, shutting down
#
# Stacktrace:
# foo @ /opt/foo.sh:32:1
# bar @ /opt/bar.sh:10:1
# ...
# return 1
printStackTrace
# Stacktrace:
# foo @ /opt/foo.sh:32:1
# bar @ /opt/bar.sh:10:1
# main @ /opt/main.sh:4:1We provide three scripts helping in parsing command line arguments:
- parse-args.sh which expects named arguments
- parse-fn-args which is intended to be used in functions and only supports positional arguments
- parse-command.sh which simplifies the delegation to corresponding command functions
Full usage example:
#!/usr/bin/env bash
set -euo pipefail
shopt -s inherit_errexit
MY_LIB_VERSION="v1.1.0"
# Assumes tegonal's scripts were fetched with gget - adjust location accordingly
dir_of_tegonal_scripts="$(cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" >/dev/null && pwd 2>/dev/null)/../lib/tegonal-scripts/src"
source "$dir_of_tegonal_scripts/setup.sh" "$dir_of_tegonal_scripts"
sourceOnce "$dir_of_tegonal_scripts/utility/parse-args.sh"
# declare all parameter names here (used as identifier afterwards)
declare pattern version directory
# parameter definitions where each parameter definition consists of three values (separated via space)
# VARIABLE_NAME PATTERN HELP_TEXT
# where the HELP_TEXT is optional in the sense of that you can use an empty string
# in case you use shellcheck then you need to suppress the warning for the last variable definition of params
# as shellcheck doesn't get that we are passing `params` to parseArguments ¯\_(ツ)_/¯ (an open issue of shellcheck)
# shellcheck disable=SC2034
declare params=(
pattern '-p|--pattern' ''
version '-v' 'the version'
directory '-d|--directory' '(optional) the working directory -- default: .'
)
# optional: you can define examples which are included in the help text -- use an empty string for no example
declare examples
# `examples` is used implicitly in parse-args, here shellcheck cannot know it and you need to disable the rule
examples=$(
cat <<EOM
# analyse in the current directory using the specified pattern
analysis.sh -p "%{21}" -v v0.1.0
EOM
)
parseArguments params "$examples" "$MY_LIB_VERSION" "$@"
# in case there are optional parameters, then fill them in here before calling exitIfNotAllArgumentsSet
if ! [[ -v directory ]]; then directory="."; fi
exitIfNotAllArgumentsSet params "$examples" "$MY_LIB_VERSION"
# pass your variables storing the arguments to other scripts
echo "p: $pattern, v: $version, d: $directory"Full usage example:
#!/usr/bin/env bash
set -euo pipefail
shopt -s inherit_errexit
if ! [[ -v dir_of_tegonal_scripts ]]; then
# Assumes tegonal's scripts were fetched with gget - adjust location accordingly
dir_of_tegonal_scripts="$(cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" >/dev/null && pwd 2>/dev/null)/../lib/tegonal-scripts/src"
source "$dir_of_tegonal_scripts/setup.sh" "$dir_of_tegonal_scripts"
fi
sourceOnce "$dir_of_tegonal_scripts/utility/parse-fn-args.sh"
function myFunction() {
# declare the variable you want to use and repeat in `declare params`
local command dir
# as shellcheck doesn't get that we are passing `params` to parseFnArgs ¯\_(ツ)_/¯ (an open issue of shellcheck)
# shellcheck disable=SC2034
local -ra params=(command dir)
parseFnArgs params "$@"
# pass your variables storing the arguments to other scripts
echo "command: $command, dir: $dir"
}
function myFunctionWithVarargs() {
# in case you want to use a vararg parameter as last parameter then name your last parameter for `params` varargs:
local command dir varargs
# shellcheck disable=SC2034
local -ra params=(command dir varargs)
parseFnArgs params "$@"
# use varargs in another script
echo "command: $command, dir: $dir, varargs: ${varargs*}"
}Full usage example:
#!/usr/bin/env bash
set -euo pipefail
shopt -s inherit_errexit
MY_LIB_VERSION="v1.1.0"
# Assumes tegonal's scripts were fetched with gget - adjust location accordingly
dir_of_tegonal_scripts="$(cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" >/dev/null && pwd 2>/dev/null)/../lib/tegonal-scripts/src"
source "$dir_of_tegonal_scripts/setup.sh" "$dir_of_tegonal_scripts"
sourceOnce "$dir_of_tegonal_scripts/utility/parse-commands.sh"
# command definitions where each command definition consists of two values (separated via space)
# COMMAND_NAME HELP_TEXT
# where the HELP_TEXT is optional in the sense of that you can use an empty string
# in case you use shellcheck then you need to suppress the warning for the last variable definition of commands
# as shellcheck doesn't get that we are passing `commands` to parseCommands ¯\_(ツ)_/¯ (an open issue of shellcheck)
# shellcheck disable=SC2034
declare commands=(
add 'command to add people to your list'
config 'manage configuration'
login ''
)
# the function which is responsible to load the corresponding file which contains the function of this particular command
function sourceCommand() {
local -r command=$1
shift
sourceOnce "my-lib-$command.sh"
}
parseCommands commands "$MY_LIB_VERSION" sourceCommand my_lib_ "$@"Utility functions when parsing (see also Parse arguments).
#!/usr/bin/env bash
set -euo pipefail
shopt -s inherit_errexit
MY_LIBRARY_VERSION="v1.0.3"
if ! [[ -v dir_of_tegonal_scripts ]]; then
# Assumes tegonal's scripts were fetched with gget - adjust location accordingly
dir_of_tegonal_scripts="$(cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" >/dev/null && pwd 2>/dev/null)/../lib/tegonal-scripts/src"
source "$dir_of_tegonal_scripts/setup.sh" "$dir_of_tegonal_scripts"
fi
sourceOnce "$dir_of_tegonal_scripts/utility/parse-utils.sh"
function myParseFunction() {
while (($# > 0)); do
if [[ $1 == "--version" ]]; then
shift || die "could not shift by 1"
printVersion "$MY_LIBRARY_VERSION"
fi
#...
done
}Utility function to find out the initial declare statement after following declare -n statements
#!/usr/bin/env bash
# shellcheck disable=SC2034
set -euo pipefail
shopt -s inherit_errexit
# Assumes tegonal's scripts were fetched with gget - adjust location accordingly
dir_of_tegonal_scripts="$(cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" >/dev/null && pwd 2>/dev/null)/../lib/tegonal-scripts/src"
source "$dir_of_tegonal_scripts/setup.sh" "$dir_of_tegonal_scripts"
source "$dir_of_tegonal_scripts/utility/recursive-declare-p.sh"
declare -i tmp=1
declare -n ref1=tmp
declare -n ref2=ref1
declare -n ref3=ref2
declare r0 r1 r2 r3
r0=$(recursiveDeclareP tmp)
r1=$(recursiveDeclareP ref1)
r2=$(recursiveDeclareP ref2)
r3=$(recursiveDeclareP ref3)
printf "%s\n" "$r0" "$r1" "$r2" "$r3"
# declare -i tmp="1"
# declare -i tmp="1"
# declare -i tmp="1"
# declare -i tmp="1"If you want to include some code in markdown files (or any other HTML-like file) then replace-snippet.sh could come in handy.
Full usage example:
#!/usr/bin/env bash
set -euo pipefail
shopt -s inherit_errexit
# Assumes tegonal's scripts were fetched with gget - adjust location accordingly
dir_of_tegonal_scripts="$(cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" >/dev/null && pwd 2>/dev/null)/../lib/tegonal-scripts/src"
source "$dir_of_tegonal_scripts/setup.sh" "$dir_of_tegonal_scripts"
source "$dir_of_tegonal_scripts/utility/replace-snippet.sh"
declare file
file=$(mktemp)
echo "<my-script></my-script>" > "$file"
declare dir fileName output
dir=$(dirname "$file")
fileName=$(basename "$file")
output=$(echo "replace with your command" | grep "command")
# replaceSnippet file id dir pattern snippet
replaceSnippet my-script.sh my-script-help "$dir" "$fileName" "$output"
echo "content"
cat "$file"
# will search for <my-script-help>...</my-script-help> in the temp file and replace it with
# <my-script-help>
#
# <!-- auto-generated, do not modify here but in my-snippet -->
# ```
# output of executing $(myCommand)
# ```
# </my-script-help>Establishes a guard by creating a variable based on the file which shall be sourced.
#!/usr/bin/env bash
set -euo pipefail
shopt -s inherit_errexit
# Assumes tegonal's scripts were fetched with gget - adjust location accordingly
dir_of_tegonal_scripts="$(cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" >/dev/null && pwd 2>/dev/null)/../lib/tegonal-scripts/src"
source "$dir_of_tegonal_scripts/setup.sh" "$dir_of_tegonal_scripts"
source "$dir_of_tegonal_scripts/utility/source-once.sh"
sourceOnce "foo.sh" # creates a variable named sourceOnceGuard_foo__sh which acts as guard and sources foo.sh
sourceOnce "foo.sh" # will source nothing as sourceOnceGuard_foo__sh is already defined
unset sourceOnceGuard_foo__sh # unsets the guard
sourceOnce "foo.sh" # is sourced again and the guard established
# you can also use sourceAlways instead of unsetting and using sourceOnce.
sourceAlways "foo.sh"
# creates a variable named sourceOnceGuard_bar__foo__sh which acts as guard and sources bar/foo.sh
sourceOnce "bar/foo.sh"
# will source nothing, only the parent dir + file is used as identifier
# i.e. the corresponding guard is sourceOnceGuard_bar__foo__sh and thus this file is not sourced
sourceOnce "asdf/bar/foo.sh"
declare guard
guard=$(determineSourceOnceGuard "src/bar.sh")
# In case you don't want that a certain file is sourced, then you can define the guard yourself
# this will prevent that */src/bar.sh is sourced
printf -v "$guard" "%s" "true"Updates the Usage section of a bash file based on a sibling doc which is named *.doc.sh (e.g foo.sh and foo.doc.sh).
Moreover, it uses Replace Snippets to update a corresponding snippet in the specified files.
Full usage example:
#!/usr/bin/env bash
set -euo pipefail
shopt -s inherit_errexit
# Assumes tegonal's scripts were fetched with gget - adjust location accordingly
dir_of_tegonal_scripts="$(cd -- "$(dirname -- "${BASH_SOURCE[0]:-$0}")" >/dev/null && pwd 2>/dev/null)/../lib/tegonal-scripts/src"
source "$dir_of_tegonal_scripts/setup.sh" "$dir_of_tegonal_scripts"
source "$dir_of_tegonal_scripts/utility/update-bash-docu.sh"
find . -name "*.sh" \
-not -name "*.doc.sh" \
-not -path "**.history/*" \
-not -name "update-docu.sh" \
-print0 |
while read -r -d $'\0' script; do
declare script="${script:2}"
replaceSnippetForScript "$dir_of_tegonal_scripts/$script" "${script////-}" . README.md
doneOur thanks go to code contributors as well as all other contributors (e.g. bug reporters, feature request creators etc.)
You are more than welcome to contribute as well:
- star this repository if you like/use it
- open a bug if you find one
- Open a new discussion if you are missing a feature
- ask a question so that we better understand where our scripts need to improve.
- have a look at the help wanted issues if you would like to code.
The provided scripts are licensed under Apache 2.0.