From d7b4ee9d964f7bb9baee6ea68b870746610889a2 Mon Sep 17 00:00:00 2001 From: Adrian Date: Tue, 18 Jun 2024 14:54:57 +0200 Subject: [PATCH] initial setup --- .gitignore | 1 + README.md | 103 ++++++++++++++++++++ build | 2 + src/DEBIAN/control | 7 ++ src/usr/bin/driglibash-args | 90 ++++++++++++++++++ src/usr/bin/driglibash-base | 185 ++++++++++++++++++++++++++++++++++++ tests/driglibash-args.sh | 50 ++++++++++ tests/driglibash-base.sh | 83 ++++++++++++++++ tests/run.sh | 9 ++ 9 files changed, 530 insertions(+) create mode 100644 .gitignore create mode 100755 README.md create mode 100755 build create mode 100644 src/DEBIAN/control create mode 100755 src/usr/bin/driglibash-args create mode 100755 src/usr/bin/driglibash-base create mode 100755 tests/driglibash-args.sh create mode 100755 tests/driglibash-base.sh create mode 100755 tests/run.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c00df13 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.deb diff --git a/README.md b/README.md new file mode 100755 index 0000000..de3bfe2 --- /dev/null +++ b/README.md @@ -0,0 +1,103 @@ +# Driglibash +Simple helper script for bash, in bash. +Those scripts are not optimized at all, they may be slow and ressources consuming. They are made for simplicity. + +## Installation +``` +sudo wget https://raw.githubusercontent.com/adrian-amaglio/driglibash/master/driglibash-base -O /usr/bin/driglibash-base +sudo wget https://raw.githubusercontent.com/adrian-amaglio/driglibash/master/driglibash-args -O /usr/bin/driglibash-args +``` + +## Driglibash-base +This file contains some very basic functions that are wildely used. Since bash is pain to read, you better do a lot of them. + +### yell +Print its arguments on stderr. + +### die +Like yell but clean and exit too. + +### root_or_die +Execute `die` if the user is not root + +### clean +Store its last argument as command and execute them all when called without arguments. Example : +``` +$ clean "echo file" +$ clean +file +``` +You can add a first argument in (pre/post/del) to add your command before/after the others or to remove it from the list +Clean is automatically called on SIGINT and by `die`. You should call it at your scripts end to handle the cleaning. + +### section +Print a separation line that is the size of the terminal. If an argument is provided, it will be displayed in the middle of the line. +``` +$ section 'Unit tests' +====================================== Unit tests ====================================== +``` + +### run +Execute a command. Run a clean and exit if it returns a non zero status. +If you set the variable `driglibash_run_retry`, you will be asked to retry a failing command. + +### start +Execute a command in background, add a clean task that kill it, and returns the pid. +Be careful ! Only the PID of the started process is returned. If the process forks, driglibash can’t stop it when needed. + +### where +Tells you where (in absolute path) is the script being executed. Useful when you want to access som files next to your executable. If the `--follow` argument is provided, `where` will follow symlinks and find the linked script file. +MUST BE RUN BEFORE ANY `cd` + +### repeat +Print its first argument, the second argument times. +``` +$ repeat '=' 10 +========== +``` + + +## driglibash-args +A little framework for argument parsing in bash. It already contains driglibash-base. +Just fill the arrays `usage` et `varia` and the value +Usage exemple : +``` +# Thoses two lines are important for bash +declare -A usage +declare -A varia + +version="alpha nightly 0.0.1 pre-release unstable" +summary="$0 [options] " + +# if the script is called with -t option, tst will be set to true +usage[t]="Start qemu after the installation" +varia[t]=tst +tst=false + +# if the script is called with -i option, tst will be an array of every argument passed to -i (must repeat -i) +usage[i]="Install the provided package. Not implemented" +varia[i]=install +declare -a install + +. driglibash-args +# After this line, driglibash-base is available and your variables are set according to arguments +# Exemple. If you call : +# ./this-script -i package2 -t -i package1 +# tst will be true and install will be an array containing (package2 package1) +``` + +## Build .deb +Expected output: +``` +$ ./build +dpkg-deb: building package 'driglibash' in 'driglibash.deb'. +``` + +## Tests +Expected output: +``` +$ cd tests +$ ./run.sh +OK. PASSED. +OK. PASSED. +``` diff --git a/build b/build new file mode 100755 index 0000000..c13cd8a --- /dev/null +++ b/build @@ -0,0 +1,2 @@ +#!/bin/bash +dpkg-deb --build src driglibash.deb diff --git a/src/DEBIAN/control b/src/DEBIAN/control new file mode 100644 index 0000000..18ad7ef --- /dev/null +++ b/src/DEBIAN/control @@ -0,0 +1,7 @@ +Package: driglibash +Version: 1.0-0 +Priority: optional +Depends: sed +Maintainer: Adrian Amaglio +Architecture: all +Description: Bash script helpers and arg framework diff --git a/src/usr/bin/driglibash-args b/src/usr/bin/driglibash-args new file mode 100755 index 0000000..aa78649 --- /dev/null +++ b/src/usr/bin/driglibash-args @@ -0,0 +1,90 @@ +#!/bin/bash + +############################################################################### +# https://github.com/adrianamaglio/driglibash-arg +############################################################################### + + +# Usage : +# +# version="alpha nightly 0.0.1 pre-release unstable" +# summary="$0 [options] " +# +# usage[t]="Start qemu after the installation" +# varia[t]=tst +# tst=false +# +# usage[i]="Install the provided package. Not implemented" +# varia[i]=install +# declare -a install +# +# usage[k]="Keep the temporar mountpoints" +# varia[k]=keep +# keep=false +# +# usage[e]="bash command file to execute in the chroot. - to read from stdin" +# varia[e]=execute +# declare -a execute + +. /usr/local/bin/driglibash-base + +#TODO keep order usage options +# Print usage and exit in error +usage() { + yell "Version: $version" + yell "Usage: $summary" + yell "Parameters:" + yell " -h print this help, version and exit." + for key in "${!usage[@]}" ; do + if [ "$(driglibash_arg_type "$key")" == "single_value" ] ; then + name="${varia[$key]}" + default=" (default : ${!name})" + else + default= + fi + yell " -$key ${usage[$key]}$default" + done + exit 0 +} + +# Guess the variable type +# Boolean, list or string +driglibash_arg_type() { + if [ $# -ne 1 ] ; then + die "Bad driglibash_arg_type usage"; + fi + + name="${varia[$1]}" + if [ "$name" == "" ] ; then die "Variable name is empty for key $1" ; fi + if [ "${!name}" == "false" ] ; then + echo "boolean" + elif [ -n "$( declare -p "$name" 2>/dev/null | grep 'declare \-a')" ] ; then + echo "array" + else + echo "single_value" + fi +} + +# Generate getopts string # +getopts_string=":h" +for key in ${!usage[@]} ; do + needs_parameter= + if [ "$(driglibash_arg_type "$key")" != "boolean" ] ; then needs_parameter=":" ; fi + getopts_string="$getopts_string$key$needs_parameter" +done + +# Loop throught options # +while getopts "$getopts_string" opt; do + case $opt in + h) usage;; + :) die "Option -$OPTARG requires an argument.";; + \?) die "Invalid option: -$OPTARG";; + *) + name="${varia[$opt]}" + if [ "${!name}" == "false" ] ; then eval $name=true + elif [ -n "$( declare -p "$name" 2>/dev/null | grep 'declare \-a')" ] ; then safe="${!name} $OPTARG" ; eval $name=\$safe + else eval $name=\$OPTARG + fi;; + esac +done ; shift $((OPTIND-1)) + diff --git a/src/usr/bin/driglibash-base b/src/usr/bin/driglibash-base new file mode 100755 index 0000000..739f2b5 --- /dev/null +++ b/src/usr/bin/driglibash-base @@ -0,0 +1,185 @@ +############################################################################### +# Driglibash pack 1 +# Usual helper functions for bash scripts +# https://github.com/adrianamaglio/driglibash +############################################################################### + +# Set to true to make a pause at each step +if [ ! -v driglibash_step_by_step ] ; then + driglibash_step_by_step=false +fi + +# Set to watever you want to have a prefix +if [ ! -v driglibash_section_prefix ] ; then + driglibash_section_prefix="" +fi + + +trap 'die "Received sigint"' INT + +# Output on standard error output +yell() { + echo >&2 -e "$@" +} + +# Print an error, clean and exit +die() { + yell "$@" + clean + exit 1 +} + +# Exit on error if not root +root_or_die() { + if [ "$UID" -ne 0 ] ; then + die "You need to be root" + fi +} + + +# Output first parameter, second parameter times +repeat() { + printf "$1"'%.s' $(eval "echo {1.."$(($2))"}") +} + +# Output a "section title" to visually separate different script part +# TODO local variables +# TODO fixed place left aligned +section(){ + text="$driglibash_section_prefix$1" + if [ -n "$text" ] ; then + len="${#text}" + max_len="$(($(tput cols)-2))" + if [ "$len" -ge "$max_len" ] ; then + right=5 + left=5 + else + left="$((($max_len - $len)/2))" + right="$left" + fi + else + left=80 + right=0 + fi + + # If the character number was rounded down + if [ "$(($left + $right + $len +1 ))" -eq "$max_len" ] ; then + left="$(($left+ 1))" + fi + + repeat '=' "$left" + if [ "$right" -ge 1 ] ; then + echo -n " $text " + repeat '=' "$right" + echo + fi + + if "$driglibash_step_by_step" ; then + echo "Press enter to proceed" + read + fi +} +alias step=section + +# Execute a command and die if it returns with error # +run() { + while true ; do + "$@" + code=$? + if [ "$code" -ne 0 ] ; then + yell "command [$*] failed with exit code '$code'" + if [ -n "$driglibash_run_retry" ] ; then + echo "Retry ? Retry (y), skip the command (s) or exit script(n) [Y/s/n] ?" + read answer + if [ "$answer" = "y" ] || [ "$answer" = "Y" ] || [ -z "$answer" ] ; then + continue + elif [ "$answer" = "s" ] || [ "$answer" = "S" ] ; then + return "$code" + fi + fi + die "Aborting" + else + break + fi + done +} + + +# Execute a commad in background and return its pid +start(){ + "$@" & + pid=$! + clean pre "kill $pid" + return $pid +} + +# Clean exit # +# Record command lines passed as argument and execute them all when called without args # +# One argument = One command # +# TODO append or prepend according to arg +declare -a driglibash_clean_actions +clean() { + if [ "$#" -eq 0 ] ; then + echo "Cleaning" + for action in "${driglibash_clean_actions[@]}" ; do + echo "driglibash_clean> $action" + $action + done + elif [ "$#" -eq 1 ] ; then + driglibash_clean_actions+=("$1") + elif [ "$#" -eq 2 ] ; then + case "$1" in + "pre") + declare -a tmp + tmp=("${driglibash_clean_actions[@]}") + driglibash_clean_actions=("$2") + driglibash_clean_actions+=("${tmp[@]}") + ;; + "post") + driglibash_clean_actions+=("$2") + ;; + "del") + for i in "${!driglibash_clean_actions[@]}" ; do + if [ "$2" = "${driglibash_clean_actions[$i]}" ] ; then + unset driglibash_clean_actions[$i] + break + fi + done + ;; + *) + die "driglibash_clean: action '$1' not supported" + esac + else + die "driglibash_clean : Bad clean usage, receveid more than two args" + fi +} + +# tells where your executable is (absolute path). Follow simlinks if any argument provided +# Must be called in first place +where() { + if [ ! -v "1" ] ; then + cd -P "$(dirname "$( realpath "$0" )")" && pwd + else + SOURCE="$0" + while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink + DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" + SOURCE="$(readlink "$SOURCE")" + [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located + done + DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" + + echo $DIR + fi +} + +# Add the line $1 in file $2 if not present +line_in_file() { + if [ "$#" -ne 2 ] ; then die "Bad usage of 'line_in_file'. Got '$#' parameters : '$@'" ; fi + if [ -z "$1" ] ; then die "Line arg is emtpy in 'line_in_file'" ; fi + line="$1" + if [ -z "$2" ] ; then die "File arg is emtpy in 'line_in_file'" ; fi + file="$2" + if [ ! -f "$file" ] ; then run touch "$file" ; fi + + grep -q -x -F "$line" "$file" || echo "$line" >> "$file" +} diff --git a/tests/driglibash-args.sh b/tests/driglibash-args.sh new file mode 100755 index 0000000..dd5a9d6 --- /dev/null +++ b/tests/driglibash-args.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +fail () { + echo -e "$@" >&2 + exit 1 +} + +cat > script.sh <<- EOF +declare -A usage +declare -A varia + +version="alpha nightly 0.0.1 pre-release unstable" +summary="\$0 [options] " + +usage[t]="Start qemu after the installation" +varia[t]=tst +tst=false + +usage[u]="u" +varia[u]=u +u=false + +usage[i]="Install the provided package. Not implemented" +varia[i]=install +declare -a install + +usage[e]="bash command file to execute in the chroot. - to read from stdin" +varia[e]=execute +execute="default" + +usage[a]="bash command file to execute in the chroot. - to read from stdin" +varia[a]=a +a="default" + +. ../src/usr/bin/driglibash-args + +if [ "\$tst" != "true" ] ; then echo "\$tst tst not true" ; exit 1 ; fi +if [ "\$u" != "false" ] ; then echo "\$u u not false" ; exit 1 ; fi +if [ "\$install" != " in1 in2" ] ; then echo "\$install install not right" ;exit 1 ; fi +if [ "\$execute" != "ex" ] ; then echo "\$execute execute not true" ;exit 1 ; fi +if [ "\$a" != "default" ] ; then echo "\$a a not true" ;exit 1 ; fi +EOF + +bash script.sh -i in1 -e ex -i in2 -t +s="$?" +if [ "$s" -ne "0" ] ; then + fail "The script exited with a non zero status : $s" +fi +rm script.sh +echo "OK. PASSED." diff --git a/tests/driglibash-base.sh b/tests/driglibash-base.sh new file mode 100755 index 0000000..c10e575 --- /dev/null +++ b/tests/driglibash-base.sh @@ -0,0 +1,83 @@ +#!/bin/bash + + +fail () { + echo -e "$@" >&2 + exit 1 +} + +here="$(pwd)" +. "$driglibash_base" + + +############################################################################### +# here +############################################################################### + +# Test from this directory +if [ "$(where)" != "$here" ] ; then + echo $(where) + echo $here + fail "where != here" +fi + + +############################################################################### +# yelle +############################################################################### + +bash > out 2> err <<-EOF + . "$driglibash_base" + yell "coucou" +EOF + +if [ "$?" -ne "0" ] ; then fail "'yell' does not exit with status 0" ; fi +if [ "$(cat out)" != "" ] ; then fail "'yell' stdout is not empty (got $(cat out))" ; fi +if [ "$(cat err)" != "coucou" ] ; then fail "'yell' stderr is not correct (got $(cat err))" ; fi + + +############################################################################### +# die +############################################################################### + +bash > out 2> err <<-EOF + . "$driglibash_base" + die "coucou" +EOF + +if [ "$?" -ne "1" ] ; then fail "'die' does not exit with status 1" ; fi +if [ "$(cat out)" == "" ] ; then fail "'die' stdout is not empty" ; fi +if [ "$(cat err)" != "coucou" ] ; then fail "'die' stderr is not correct :\n$(cat err | hexdump -c)" ; fi + + +############################################################################### +# clean +############################################################################### + +clean 'a a a a' +if [ "${driglibash_clean_actions[@]}" != "a a a a" ] ; then fail "clean with only command does not work" ; fi + +clean 'b b b' +if [ "${driglibash_clean_actions[0]}" != "a a a a" ] ; then fail "clean append does not work 0" ; fi +if [ "${driglibash_clean_actions[1]}" != "b b b" ] ; then fail "clean append does not work 1" ; fi + +clean del 'b b b' +if [ "${driglibash_clean_actions[0]}" != "a a a a" ] ; then fail "clean del does not work 0" ; fi +if [ -n "${driglibash_clean_actions[1]}" ] ; then fail "clean del does not work 1" ; fi + +clean post 'b b b' +if [ "${driglibash_clean_actions[0]}" != "a a a a" ] ; then fail "clean post does not work 0 (got '${driglibash_clean_actions[0]}')" ; fi +if [ "${driglibash_clean_actions[1]}" != "b b b" ] ; then fail "clean post does not work 1 (got '${driglibash_clean_actions[0]}')" ; fi + +clean pre 'b b b' +if [ "${driglibash_clean_actions[0]}" != "b b b" ] ; then fail "clean pre does not work 0 (got '${driglibash_clean_actions[0]}')" ; fi +if [ "${driglibash_clean_actions[1]}" != "a a a a" ] ; then fail "clean pre does not work 1 (got '${driglibash_clean_actions[1]}')" ; fi +if [ "${driglibash_clean_actions[2]}" != "b b b" ] ; then fail "clean pre does not work 2 (got '${driglibash_clean_actions[2]}')" ; fi + + + +############################################################################### +# end +############################################################################### +rm out err +echo "OK. PASSED." diff --git a/tests/run.sh b/tests/run.sh new file mode 100755 index 0000000..9c8f28d --- /dev/null +++ b/tests/run.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +set -a +driglibash_base=../src/usr/bin/driglibash-base +driglibash_args=../src/usr/bin/driglibash-args +set +a + +./driglibash-base.sh +./driglibash-args.sh