About
"About" tools
Development blog
Contact
History
Privacy Statement
Terms & Conditions
How to use the tools
Color
"Color" tools
CMYK conversion
HTML Color list
RGB conversion
Websafe Color list
Data
"Data" tools
Bandwidth convert
Download calculator
Data size calculator
Finance
"Finance" tools
EV mileage calculator
Fuel Cost calculator
Natural Gas calculator
Mortgage loan calculator
Music
"Music" tools
Beats-per-minute
Audio delay
Music Frequency
Song length
Music Scale
Tap your tempo
Photo
"Photo" tools
Aspect ratio
Aspect Fit
Composition Calculator
Depth-of-Field Calculator
DPI calculator
Image filesize calculator
Light calculator (EV)
Megapixel calculator
Megapixel Aspects
Portrait distance
Physics
"Physics" tools
Acceleration calculator
Battery Capacity (Wh)
Distance calculator
Exponential growth
Gladwell's 10000 hours
Speed converter
tCO2e Calculator
Frequency Calculator
Electrical power
Wavelength Calculator
Video
"Video" tools
Audio file size calculator
Common bitrates
DCP file size
Foot-Lambert Calculator
Screen size
Video bitrate
Frame rate convert
Video file size calculator
Web
"Web" tools
Content Security Policy
Redirection check
Security Policy Framework check
HTTPS config check
HTML char map
Emoji char map
HTML encoder
SEO Performance
URL encoder
Bash Boilerplate Generator
Use this page to quickly generate a
bash
boilerplate
script
based on
bashew
You will start your script with all the typical code for option parsing, dependency checking, output, logging ... already implemented. You can focus on just the new functionality and save a lot of time.
Parameters
Author email
A
flag
is an optional on/off parameter like
-d/--debug
.
Flag 1
flag
option
secret
param
optional
multi
Flag 2
flag
option
secret
param
optional
multi
Flag 3
flag
option
secret
param
optional
multi
An
option
is a optional value parameter like
-e/--email [email]
.
Option 1
flag
option
secret
param
optional
multi
Option 2
flag
option
secret
param
optional
multi
Option 3
flag
option
secret
param
optional
multi
A
param
is an obligatory unnamed value parameter.
An
optional
is an optional unnamed value parameter.
An
multi
is a multiple values parameter (can only be last parameter).
Parameter 1
flag
option
secret
param
optional
multi
Parameter 2
flag
option
secret
param
optional
multi
Parameter 3
flag
option
secret
param
optional
multi
Parameter 4
flag
option
secret
param
optional
multi
Generate!
Instructions
Fill in your email, add the flags/options/parameters you need • press «Generate» • copy the code • paste it in your editor. • Now start coding the
main
part of the script
Scripts run on:
Linux (
Ubuntu/Debian/
Redhat/
Suse),
Busybox (Synology/QNAP),
MacOS,
Windows 10 WSL
Bash boilerplate
#!/usr/bin/env bash # created by: https://toolstud.io/bash/boilerplate.php # template : https://github.com/pforret/bashew/blob/master/template/normal.sh ### ============================================================================== ### SO HOW DO YOU PROCEED WITH YOUR SCRIPT? ### 1. define the flags/options/parameters and defaults you need in Option:config() ### 2. implement the different actions in Script:main() directly or with helper functions do_action1 ### 3. implement helper functions you defined in previous step ### ============================================================================== ### Created by author_name ( author_username ) on meta_today ### Based on https://github.com/pforret/bashew bashew_version script_version="0.0.1" # if there is a VERSION.md in this script's folder, that will have priority over this version number readonly script_author="you@example.com" readonly script_created="meta_today" readonly run_as_root=-1 # run_as_root: 0 = don't check anything / 1 = script MUST run as root / -1 = script MAY NOT run as root readonly script_description="package_description" function Option:config() { ### Change the next lines to reflect which flags/options/parameters you need ### flag: switch a flag 'on' / no value specified ### flag|<short>|<long>|<description> ### e.g. "-v" or "--VERBOSE" for VERBOSE output / default is always 'off' ### will be available as $<long> in the script e.g. $VERBOSE ### option: set an option / 1 value specified ### option|<short>|<long>|<description>|<default> ### e.g. "-e <extension>" or "--extension <extension>" for a file extension ### will be available a $<long> in the script e.g. $extension ### list: add an list/array item / 1 value specified ### list|<short>|<long>|<description>| (default is ignored) ### e.g. "-u <user1> -u <user2>" or "--user <user1> --user <user2>" ### will be available a $<long> array in the script e.g. ${user[@]} ### param: comes after the options ### param|<type>|<long>|<description> ### <type> = 1 for single parameters - e.g. param|1|output expects 1 parameter <output> ### <type> = ? for optional parameters - e.g. param|1|output expects 1 parameter <output> ### <type> = n for list parameter - e.g. param|n|inputs expects <input1> <input2> ... <input99> ### will be available as $<long> in the script after option/param parsing ### choice: is like a param, but when there are limited options ### choice|<type>|<long>|<description>|choice1,choice2,... ### <type> = 1 for single parameters - e.g. param|1|output expects 1 parameter <output> grep <<<" #commented lines will be filtered flag|h|help|show usage flag|Q|QUIET|no output flag|V|VERBOSE|also show debug messages flag|f|FORCE|do not ask for confirmation (always yes) option|L|LOG_DIR|folder for log files |$HOME/log/$script_prefix option|T|TMP_DIR|folder for temp files|/tmp/$script_prefix #option|W|WIDTH|width of the picture|800 choice|1|action|action to perform|action1,action2,check,env,update param|?|input|input file/text " -v -e '^#' -e '^\s*$' } ##################################################################### ## Put your Script:main script here ##################################################################### function Script:main() { #// the option $log_dir will be [$HOME/log/$script_prefix] or whatever the user specified with -l [value] or --log_dir [value] #// the option $tmp_dir will be [.tmp] or whatever the user specified with -t [value] or --tmp_dir [value] #// the parameter $inputfile has to be specified when starting the script #// the parameter $input has to be specified when starting the script IO:log "[$script_basename] $script_version started" Os:require "awk" case "${action,,}" in action1) #TIP: use «$script_prefix action1» to ... #TIP:> $script_prefix action1 do_action1 ;; action2) #TIP: use «$script_prefix action2» to ... #TIP:> $script_prefix action2 do_action2 ;; action3) #TIP: use «$script_prefix action3» to ... #TIP:> $script_prefix action3 # Os:require "convert" "imagemagick" # CONVERT $input $output ;; check | env) ## leave this default action, it will make it easier to test your script #TIP: use «$script_prefix check» to check if this script is ready to execute and what values the options/flags are #TIP:> $script_prefix check #TIP: use «$script_prefix env» to generate an example .env file #TIP:> $script_prefix env > .env Script:check ;; update) ## leave this default action, it will make it easier to test your script #TIP: use «$script_prefix update» to update to the latest version #TIP:> $script_prefix update Script:git_pull ;; *) IO:die "action [$action] not recognized" ;; esac IO:log "[$script_basename] ended after $SECONDS secs" #TIP: >>> bash script created with «pforret/bashew» #TIP: >>> for bash development, also check out «pforret/setver» and «pforret/progressbar» } ##################################################################### ## Put your helper scripts here ##################################################################### function do_action1() { IO:log "action1" # Examples of required binaries/scripts and how to install them # Os:require "ffmpeg" # Os:require "convert" "imagemagick" # Os:require "IO:progressbar" "basher install pforret/IO:progressbar" # (code) } function do_action2() { IO:log "action2" # (code) } ##################################################################### ################### DO NOT MODIFY BELOW THIS LINE ################### ##################################################################### action="" error_prefix="" git_repo_remote="" git_repo_root="" install_package="" os_kernel="" os_machine="" os_name="" os_version="" script_basename="" script_hash="?" script_lines="?" script_prefix="" shell_brand="" shell_version="" temp_files=() # set strict mode - via http://redsymbol.net/articles/unofficial-bash-strict-mode/ # removed -e because it made basic [[ testing ]] difficult set -uo pipefail IFS=$'\n\t' FORCE=0 help=0 #to enable VERBOSE even before option parsing VERBOSE=0 [[ $# -gt 0 ]] && [[ $1 == "-v" ]] && VERBOSE=1 #to enable QUIET even before option parsing QUIET=0 [[ $# -gt 0 ]] && [[ $1 == "-q" ]] && QUIET=1 txtReset="" txtError="" txtInfo="" txtInfo="" txtWarn="" txtBold="" txtItalic="" txtUnderline="" char_succes="OK " char_fail="!! " char_alert="?? " char_wait="..." info_icon="(i)" config_icon="[c]" clean_icon="[c]" require_icon="[r]" ### stdIO:print/stderr output function IO:initialize() { script_started_at="$(Tool:time)" IO:debug "script $script_basename started at $script_started_at" [[ "${BASH_SOURCE[0]:-}" != "${0}" ]] && sourced=1 || sourced=0 [[ -t 1 ]] && piped=0 || piped=1 # detect if output is piped if [[ $piped -eq 0 && -n "$TERM" ]]; then txtReset=$(tput sgr0) txtError=$(tput setaf 160) txtInfo=$(tput setaf 2) txtWarn=$(tput setaf 214) txtBold=$(tput bold) txtItalic=$(tput sitm) txtUnderline=$(tput smul) fi [[ $(echo -e '\xe2\x82\xac') == '€' ]] && unicode=1 || unicode=0 # detect if unicode is supported if [[ $unicode -gt 0 ]]; then char_succes="✅" char_fail="⛔" char_alert="✴️" char_wait="⏳" info_icon="🌼" config_icon="🌱" clean_icon="🧽" require_icon="🔌" fi error_prefix="${txtError}>${txtReset}" } function IO:print() { ((QUIET)) && true || printf '%b\n' "$*" } function IO:debug() { ((VERBOSE)) && IO:print "${txtInfo}# $* ${txtReset}" >&2 true } function IO:die() { IO:print "${txtError}${char_fail} $script_basename${txtReset}: $*" >&2 Os:beep Script:exit } function IO:alert() { IO:print "${txtWarn}${char_alert}${txtReset}: ${txtUnderline}$*${txtReset}" >&2 } function IO:success() { IO:print "${txtInfo}${char_succes}${txtReset} ${txtBold}$*${txtReset}" } function IO:announce() { IO:print "${txtInfo}${char_wait}${txtReset} ${txtItalic}$*${txtReset}" sleep 1 } function IO:progress() { ((QUIET)) || ( local screen_width screen_width=$(tput cols 2>/dev/null || echo 80) local rest_of_line rest_of_line=$((screen_width - 5)) if ((piped)); then IO:print "... $*" >&2 else printf "... %-${rest_of_line}b\r" "$* " >&2 fi ) } function IO:countdown() { local seconds=${1:-5} local message=${2:-Countdown :} local i if ((piped)); then IO:print "$message $seconds seconds" else for ((i = 0; i < "$seconds"; i++)); do IO:progress "${txtInfo}$message $((seconds - i)) seconds${txtReset}" sleep 1 done IO:print " " fi } ### interactive function IO:confirm() { ((FORCE)) && return 0 read -r -p "$1 [y/N] " -n 1 echo " " [[ $REPLY =~ ^[Yy]$ ]] } function IO:question() { local ANSWER local DEFAULT=${2:-} read -r -p "$1 ($DEFAULT) > " ANSWER [[ -z "$ANSWER" ]] && echo "$DEFAULT" || echo "$ANSWER" } function IO:log() { [[ -n "${log_file:-}" ]] && echo "$(date '+%H:%M:%S') | $*" >>"$log_file" } function Tool:calc() { awk "BEGIN {print $*} ; " } function Tool:round() { local number="${1}" local decimals="${2:-0}" awk "BEGIN {print sprintf( \"%.${decimals}f\" , $number )};" } function Tool:time() { if [[ $(command -v perl) ]]; then perl -MTime::HiRes=time -e 'printf "%f\n", time' elif [[ $(command -v php) ]]; then php -r 'printf("%f\n",microtime(true));' elif [[ $(command -v python) ]]; then python -c 'import time; print(time.time()) ' elif [[ $(command -v python3) ]]; then python3 -c 'import time; print(time.time()) ' elif [[ $(command -v node) ]]; then node -e 'console.log(+new Date() / 1000)' elif [[ $(command -v ruby) ]]; then ruby -e 'STDOUT.puts(Time.now.to_f)' else date '+%s.000' fi } function Tool:throughput() { local time_started="$1" [[ -z "$time_started" ]] && time_started="$script_started_at" local operations="${2:-1}" local name="${3:-operation}" local time_finished local duration local seconds time_finished="$(Tool:time)" duration="$(Tool:calc "$time_finished - $time_started")" seconds="$(Tool:round "$duration")" local ops if [[ "$operations" -gt 1 ]]; then if [[ $operations -gt $seconds ]]; then ops=$(Tool:calc "$operations / $duration") ops=$(Tool:round "$ops" 3) duration=$(Tool:round "$duration" 2) IO:print "$operations $name finished in $duration secs: $ops $name/sec" else ops=$(Tool:calc "$duration / $operations") ops=$(Tool:round "$ops" 3) duration=$(Tool:round "$duration" 2) IO:print "$operations $name finished in $duration secs: $ops sec/$name" fi else duration=$(Tool:round "$duration" 2) IO:print "$name finished in $duration secs" fi } ### string processing function Str:trim() { local var="$*" # remove leading whitespace characters var="${var#"${var%%[![:space:]]*}"}" # remove trailing whitespace characters var="${var%"${var##*[![:space:]]}"}" printf '%s' "$var" } function Str:lower() { if [[ -n "$1" ]]; then local input="$*" echo "${input,,}" else awk '{print tolower($0)}' fi } function Str:upper() { if [[ -n "$1" ]]; then local input="$*" echo "${input^^}" else awk '{print toupper($0)}' fi } function Str:ascii() { # remove all characters with accents/diacritics to latin alphabet # shellcheck disable=SC2020 sed 'y/àáâäæãåāǎçćčèéêëēėęěîïííīįìǐłñńôöòóœøōǒõßśšûüǔùǖǘǚǜúūÿžźżÀÁÂÄÆÃÅĀǍÇĆČÈÉÊËĒĖĘĚÎÏÍÍĪĮÌǏŁÑŃÔÖÒÓŒØŌǑÕẞŚŠÛÜǓÙǕǗǙǛÚŪŸŽŹŻ/aaaaaaaaaccceeeeeeeeiiiiiiiilnnooooooooosssuuuuuuuuuuyzzzAAAAAAAAACCCEEEEEEEEIIIIIIIILNNOOOOOOOOOSSSUUUUUUUUUUYZZZ/' } function Str:slugify() { # Str:slugify <input> <separator> # Str:slugify "Jack, Jill & Clémence LTD" => jack-jill-clemence-ltd # Str:slugify "Jack, Jill & Clémence LTD" "_" => jack_jill_clemence_ltd separator="${2:-}" [[ -z "$separator" ]] && separator="-" Str:lower "$1" | Str:ascii | awk '{ gsub(/[\[\]@#$%^&*;,.:()<>!?\/+=_]/," ",$0); gsub(/^ */,"",$0); gsub(/ *$/,"",$0); gsub(/ */,"-",$0); gsub(/[^a-z0-9\-]/,""); print; }' | sed "s/-/$separator/g" } function Str:title() { # Str:title <input> <separator> # Str:title "Jack, Jill & Clémence LTD" => JackJillClemenceLtd # Str:title "Jack, Jill & Clémence LTD" "_" => Jack_Jill_Clemence_Ltd separator="${2:-}" # shellcheck disable=SC2020 Str:lower "$1" | tr 'àáâäæãåāçćčèéêëēėęîïííīįìłñńôöòóœøōõßśšûüùúūÿžźż' 'aaaaaaaaccceeeeeeeiiiiiiilnnoooooooosssuuuuuyzzz' | awk '{ gsub(/[\[\]@#$%^&*;,.:()<>!?\/+=_-]/," ",$0); print $0; }' | awk '{ for (i=1; i<=NF; ++i) { $i = toupper(substr($i,1,1)) tolower(substr($i,2)) }; print $0; }' | sed "s/ /$separator/g" | cut -c1-50 } function Str:digest() { local length=${1:-6} if [[ -n $(command -v md5sum) ]]; then # regular linux md5sum | cut -c1-"$length" else # macos md5 | cut -c1-"$length" fi } # Gha: function should only be run inside of a Github Action function Gha:finish() { [[ -z "${RUNNER_OS:-}" ]] && IO:die "This should only run inside a Github Action, don't run it on your machine" local timestamp message git config user.name "Bashew Runner" git config user.email "actions@users.noreply.github.com" git add -A timestamp="$(date -u)" message="$timestamp < $script_basename $script_version" IO:print "Commit Message: $message" git commit -m "${message}" || exit 0 git pull --rebase git push IO:success "Commit OK!" } trap "IO:die \"ERROR \$? after \$SECONDS seconds \n\ \${error_prefix} last command : '\$BASH_COMMAND' \" \ \$(< \$script_install_path awk -v lineno=\$LINENO \ 'NR == lineno {print \"\${error_prefix} from line \" lineno \" : \" \$0}')" INT TERM EXIT # cf https://askubuntu.com/questions/513932/what-is-the-bash-command-variable-good-for Script:exit() { local temp_file for temp_file in "${temp_files[@]-}"; do [[ -f "$temp_file" ]] && ( IO:debug "Delete temp file [$temp_file]" rm -f "$temp_file" ) done trap - INT TERM EXIT IO:debug "$script_basename finished after $SECONDS seconds" exit 0 } Script:check_version() { ( # shellcheck disable=SC2164 pushd "$script_install_folder" &>/dev/null if [[ -d .git ]]; then local remote remote="$(git remote -v | grep fetch | awk 'NR == 1 {print $2}')" IO:progress "Check for updates - $remote" git remote update &>/dev/null if [[ $(git rev-list --count "HEAD...HEAD@{upstream}" 2>/dev/null) -gt 0 ]]; then IO:print "There is a more recent update of this script - run <<$script_prefix update>> to update" else IO:progress " " fi fi # shellcheck disable=SC2164 popd &>/dev/null ) } Script:git_pull() { # run in background to avoid problems with modifying a running interpreted script ( sleep 1 cd "$script_install_folder" && git pull ) & } Script:show_tips() { ((sourced)) && return 0 # shellcheck disable=SC2016 grep <"${BASH_SOURCE[0]}" -v '$0' | awk \ -v green="$txtInfo" \ -v yellow="$txtWarn" \ -v reset="$txtReset" \ ' /TIP: / {$1=""; gsub(/«/,green); gsub(/»/,reset); print "*" $0} /TIP:> / {$1=""; print " " yellow $0 reset} ' | awk \ -v script_basename="$script_basename" \ -v script_prefix="$script_prefix" \ '{ gsub(/\$script_basename/,script_basename); gsub(/\$script_prefix/,script_prefix); print ; }' } Script:check() { local name if [[ -n $(Option:filter flag) ]]; then IO:print "## ${txtInfo}boolean flags${txtReset}:" Option:filter flag | grep -v help | while read -r name; do declare -p "$name" | cut -d' ' -f3- done fi if [[ -n $(Option:filter option) ]]; then IO:print "## ${txtInfo}option defaults${txtReset}:" Option:filter option | while read -r name; do declare -p "$name" | cut -d' ' -f3- done fi if [[ -n $(Option:filter list) ]]; then IO:print "## ${txtInfo}list options${txtReset}:" Option:filter list | while read -r name; do declare -p "$name" | cut -d' ' -f3- done fi if [[ -n $(Option:filter param) ]]; then if ((piped)); then IO:debug "Skip parameters for .env files" else IO:print "## ${txtInfo}parameters${txtReset}:" Option:filter param | while read -r name; do declare -p "$name" | cut -d' ' -f3- done fi fi if [[ -n $(Option:filter choice) ]]; then if ((piped)); then IO:debug "Skip choices for .env files" else IO:print "## ${txtInfo}choice${txtReset}:" Option:filter choice | while read -r name; do declare -p "$name" | cut -d' ' -f3- done fi fi IO:print "## ${txtInfo}required commands${txtReset}:" Script:show_required } Option:usage() { IO:print "Program : ${txtInfo}$script_basename${txtReset} by ${txtWarn}$script_author${txtReset}" IO:print "Version : ${txtInfo}v$script_version${txtReset} (${txtWarn}$script_modified${txtReset})" IO:print "Purpose : ${txtInfo}$script_description${txtReset}" echo -n "Usage : $script_basename" Option:config | awk ' BEGIN { FS="|"; OFS=" "; oneline="" ; fulltext="Flags, options and parameters:"} $1 ~ /flag/ { fulltext = fulltext sprintf("\n -%1s|--%-12s: [flag] %s [default: off]",$2,$3,$4) ; oneline = oneline " [-" $2 "]" } $1 ~ /option/ { fulltext = fulltext sprintf("\n -%1s|--%-12s: [option] %s",$2,$3 " <?>",$4) ; if($5!=""){fulltext = fulltext " [default: " $5 "]"; } oneline = oneline " [-" $2 " <" $3 ">]" } $1 ~ /list/ { fulltext = fulltext sprintf("\n -%1s|--%-12s: [list] %s (array)",$2,$3 " <?>",$4) ; fulltext = fulltext " [default empty]"; oneline = oneline " [-" $2 " <" $3 ">]" } $1 ~ /secret/ { fulltext = fulltext sprintf("\n -%1s|--%s <%s>: [secret] %s",$2,$3,"?",$4) ; oneline = oneline " [-" $2 " <" $3 ">]" } $1 ~ /param/ { if($2 == "1"){ fulltext = fulltext sprintf("\n %-17s: [parameter] %s","<"$3">",$4); oneline = oneline " <" $3 ">" } if($2 == "?"){ fulltext = fulltext sprintf("\n %-17s: [parameter] %s (optional)","<"$3">",$4); oneline = oneline " <" $3 "?>" } if($2 == "n"){ fulltext = fulltext sprintf("\n %-17s: [parameters] %s (1 or more)","<"$3">",$4); oneline = oneline " <" $3 " …>" } } $1 ~ /choice/ { fulltext = fulltext sprintf("\n %-17s: [choice] %s","<"$3">",$4); if($5!=""){fulltext = fulltext " [options: " $5 "]"; } oneline = oneline " <" $3 ">" } END {print oneline; print fulltext} ' } function Option:filter() { Option:config | grep "$1|" | cut -d'|' -f3 | sort | grep -v '^\s*$' } function Script:show_required() { grep 'Os:require' "$script_install_path" | grep -v -E '\(\)|grep|# Os:require' | awk -v install="# $install_package " ' function ltrim(s) { sub(/^[ "\t\r\n]+/, "", s); return s } function rtrim(s) { sub(/[ "\t\r\n]+$/, "", s); return s } function trim(s) { return rtrim(ltrim(s)); } NF == 2 {print install trim($2); } NF == 3 {print install trim($3); } NF > 3 {$1=""; $2=""; $0=trim($0); print "# " trim($0);} ' | sort -u } function Option:initialize() { local init_command init_command=$(Option:config | grep -v "VERBOSE|" | awk ' BEGIN { FS="|"; OFS=" ";} $1 ~ /flag/ && $5 == "" {print $3 "=0; "} $1 ~ /flag/ && $5 != "" {print $3 "=\"" $5 "\"; "} $1 ~ /option/ && $5 == "" {print $3 "=\"\"; "} $1 ~ /option/ && $5 != "" {print $3 "=\"" $5 "\"; "} $1 ~ /choice/ {print $3 "=\"\"; "} $1 ~ /list/ {print $3 "=(); "} $1 ~ /secret/ {print $3 "=\"\"; "} ') if [[ -n "$init_command" ]]; then eval "$init_command" fi } function Option:has_single() { Option:config | grep 'param|1|' >/dev/null; } function Option:has_choice() { Option:config | grep 'choice|1' >/dev/null; } function Option:has_optional() { Option:config | grep 'param|?|' >/dev/null; } function Option:has_multi() { Option:config | grep 'param|n|' >/dev/null; } function Option:parse() { if [[ $# -eq 0 ]]; then Option:usage >&2 Script:exit fi ## first process all the -x --xxxx flags and options while true; do # flag <flag> is saved as $flag = 0/1 # option <option> is saved as $option if [[ $# -eq 0 ]]; then ## all parameters processed break fi if [[ ! $1 == -?* ]]; then ## all flags/options processed break fi local save_option save_option=$(Option:config | awk -v opt="$1" ' BEGIN { FS="|"; OFS=" ";} $1 ~ /flag/ && "-"$2 == opt {print $3"=1"} $1 ~ /flag/ && "--"$3 == opt {print $3"=1"} $1 ~ /option/ && "-"$2 == opt {print $3"=${2:-}; shift"} $1 ~ /option/ && "--"$3 == opt {print $3"=${2:-}; shift"} $1 ~ /list/ && "-"$2 == opt {print $3"+=(${2:-}); shift"} $1 ~ /list/ && "--"$3 == opt {print $3"=(${2:-}); shift"} $1 ~ /secret/ && "-"$2 == opt {print $3"=${2:-}; shift #noshow"} $1 ~ /secret/ && "--"$3 == opt {print $3"=${2:-}; shift #noshow"} ') if [[ -n "$save_option" ]]; then if echo "$save_option" | grep shift >>/dev/null; then local save_var save_var=$(echo "$save_option" | cut -d= -f1) IO:debug "$config_icon parameter: ${save_var}=$2" else IO:debug "$config_icon flag: $save_option" fi eval "$save_option" else IO:die "cannot interpret option [$1]" fi shift done ((help)) && ( Option:usage Script:check_version IO:print " " echo "### TIPS & EXAMPLES" Script:show_tips ) && Script:exit local option_list local option_count local choices local single_params ## then run through the given parameters if Option:has_choice; then choices=$(Option:config | awk -F"|" ' $1 == "choice" && $2 == 1 {print $3} ') option_list=$(xargs <<<"$choices") option_count=$(wc <<<"$choices" -w | xargs) IO:debug "$config_icon Expect : $option_count choice(s): $option_list" [[ $# -eq 0 ]] && IO:die "need the choice(s) [$option_list]" local choices_list local valid_choice local param for param in $choices; do [[ $# -eq 0 ]] && IO:die "need choice [$param]" [[ -z "$1" ]] && IO:die "need choice [$param]" IO:debug "$config_icon Assign : $param=$1" # check if choice is in list choices_list=$(Option:config | awk -F"|" -v choice="$param" '$1 == "choice" && $3 = choice {print $5}') valid_choice=$(tr <<<"$choices_list" "," "\n" | grep "$1") [[ -z "$valid_choice" ]] && IO:die "choice [$1] is not valid, should be in list [$choices_list]" eval "$param=\"$1\"" shift done else IO:debug "$config_icon No choices to process" choices="" option_count=0 fi if Option:has_single; then single_params=$(Option:config | awk -F"|" ' $1 == "param" && $2 == 1 {print $3} ') option_list=$(xargs <<<"$single_params") option_count=$(wc <<<"$single_params" -w | xargs) IO:debug "$config_icon Expect : $option_count single parameter(s): $option_list" [[ $# -eq 0 ]] && IO:die "need the parameter(s) [$option_list]" for param in $single_params; do [[ $# -eq 0 ]] && IO:die "need parameter [$param]" [[ -z "$1" ]] && IO:die "need parameter [$param]" IO:debug "$config_icon Assign : $param=$1" eval "$param=\"$1\"" shift done else IO:debug "$config_icon No single params to process" single_params="" option_count=0 fi if Option:has_optional; then local optional_params local optional_count optional_params=$(Option:config | grep 'param|?|' | cut -d'|' -f3) optional_count=$(wc <<<"$optional_params" -w | xargs) IO:debug "$config_icon Expect : $optional_count optional parameter(s): $(echo "$optional_params" | xargs)" for param in $optional_params; do IO:debug "$config_icon Assign : $param=${1:-}" eval "$param=\"${1:-}\"" shift done else IO:debug "$config_icon No optional params to process" optional_params="" optional_count=0 fi if Option:has_multi; then #IO:debug "Process: multi param" local multi_count local multi_param multi_count=$(Option:config | grep -c 'param|n|') multi_param=$(Option:config | grep 'param|n|' | cut -d'|' -f3) IO:debug "$config_icon Expect : $multi_count multi parameter: $multi_param" ((multi_count > 1)) && IO:die "cannot have >1 'multi' parameter: [$multi_param]" ((multi_count > 0)) && [[ $# -eq 0 ]] && IO:die "need the (multi) parameter [$multi_param]" # save the rest of the params in the multi param if [[ -n "$*" ]]; then IO:debug "$config_icon Assign : $multi_param=$*" eval "$multi_param=( $* )" fi else multi_count=0 multi_param="" [[ $# -gt 0 ]] && IO:die "cannot interpret extra parameters" fi } function Os:require() { local install_instructions local binary local words local path_binary # $1 = binary that is required binary="$1" path_binary=$(command -v "$binary" 2>/dev/null) [[ -n "$path_binary" ]] && IO:debug "️$require_icon required [$binary] -> $path_binary" && return 0 # $2 = how to install it IO:alert "$script_basename needs [$binary] but it cannot be found" words=$(echo "${2:-}" | wc -w) install_instructions="$install_package $1" [[ $words -eq 1 ]] && install_instructions="$install_package $2" [[ $words -gt 1 ]] && install_instructions="${2:-}" if ((FORCE)); then IO:announce "Installing [$1] ..." eval "$install_instructions" else IO:alert "1) install package : $install_instructions" IO:alert "2) check path : export PATH=\"[path of your binary]:\$PATH\"" IO:die "Missing program/script [$binary]" fi } function Os:folder() { if [[ -n "$1" ]]; then local folder="$1" local max_days=${2:-365} if [[ ! -d "$folder" ]]; then IO:debug "$clean_icon Create folder : [$folder]" mkdir -p "$folder" else IO:debug "$clean_icon Cleanup folder: [$folder] - delete files older than $max_days day(s)" find "$folder" -mtime "+$max_days" -type f -exec rm {} \; fi fi } function Os:follow_link() { [[ ! -L "$1" ]] && echo "$1" && return 0 ## if it's not a symbolic link, return immediately local file_folder link_folder link_name symlink file_folder="$(dirname "$1")" ## check if file has absolute/relative/no path [[ "$file_folder" != /* ]] && file_folder="$(cd -P "$file_folder" &>/dev/null && pwd)" ## a relative path was given, resolve it symlink=$(readlink "$1") ## follow the link link_folder=$(dirname "$symlink") ## check if link has absolute/relative/no path [[ -z "$link_folder" ]] && link_folder="$file_folder" ## if no link path, stay in same folder [[ "$link_folder" == \.* ]] && link_folder="$(cd -P "$file_folder" && cd -P "$link_folder" &>/dev/null && pwd)" ## a relative link path was given, resolve it link_name=$(basename "$symlink") IO:debug "$info_icon Symbolic ln: $1 -> [$link_folder/$link_name]" Os:follow_link "$link_folder/$link_name" ## recurse } function Os:notify() { # cf https://levelup.gitconnected.com/5-modern-bash-scripting-techniques-that-only-a-few-programmers-know-4abb58ddadad local message="$1" local source="${2:-$script_basename}" [[ -n $(command -v notify-send) ]] && notify-send "$source" "$message" # for Linux [[ -n $(command -v osascript) ]] && osascript -e "display notification \"$message\" with title \"$source\"" # for MacOS } function Os:busy() { # show spinner as long as process $pid is running local pid="$1" local message="${2:-}" local frames=("|" "/" "-" "\\") ( while kill -0 "$pid" &>/dev/null; do for frame in "${frames[@]}"; do printf "\r[ $frame ] %s..." "$message" sleep 0.5 done done printf "\n" ) } function Os:beep() { if [[ -n "$TERM" ]]; then tput bel fi } function Script:meta() { script_prefix=$(basename "${BASH_SOURCE[0]}" .sh) script_basename=$(basename "${BASH_SOURCE[0]}") execution_day=$(date "+%Y-%m-%d") script_install_path="${BASH_SOURCE[0]}" IO:debug "$info_icon Script path: $script_install_path" script_install_path=$(Os:follow_link "$script_install_path") IO:debug "$info_icon Linked path: $script_install_path" script_install_folder="$(cd -P "$(dirname "$script_install_path")" && pwd)" IO:debug "$info_icon In folder : $script_install_folder" if [[ -f "$script_install_path" ]]; then script_hash=$(Str:digest <"$script_install_path" 8) script_lines=$(awk <"$script_install_path" 'END {print NR}') fi # get shell/operating system/versions shell_brand="sh" shell_version="?" [[ -n "${ZSH_VERSION:-}" ]] && shell_brand="zsh" && shell_version="$ZSH_VERSION" [[ -n "${BASH_VERSION:-}" ]] && shell_brand="bash" && shell_version="$BASH_VERSION" [[ -n "${FISH_VERSION:-}" ]] && shell_brand="fish" && shell_version="$FISH_VERSION" [[ -n "${KSH_VERSION:-}" ]] && shell_brand="ksh" && shell_version="$KSH_VERSION" IO:debug "$info_icon Shell type : $shell_brand - version $shell_version" if [[ "$shell_brand" == "bash" && "${BASH_VERSINFO:-0}" -lt 4 ]]; then IO:die "Bash version 4 or higher is required - current version = ${BASH_VERSINFO:-0}" fi os_kernel=$(uname -s) os_version=$(uname -r) os_machine=$(uname -m) install_package="" case "$os_kernel" in CYGWIN* | MSYS* | MINGW*) os_name="Windows" ;; Darwin) os_name=$(sw_vers -productName) # macOS os_version=$(sw_vers -productVersion) # 11.1 install_package="brew install" ;; Linux | GNU*) if [[ $(command -v lsb_release) ]]; then # 'normal' Linux distributions os_name=$(lsb_release -i | awk -F: '{$1=""; gsub(/^[\s\t]+/,"",$2); gsub(/[\s\t]+$/,"",$2); print $2}') # Ubuntu/Raspbian os_version=$(lsb_release -r | awk -F: '{$1=""; gsub(/^[\s\t]+/,"",$2); gsub(/[\s\t]+$/,"",$2); print $2}') # 20.04 else # Synology, QNAP, os_name="Linux" fi [[ -x /bin/apt-cyg ]] && install_package="apt-cyg install" # Cygwin [[ -x /bin/dpkg ]] && install_package="dpkg -i" # Synology [[ -x /opt/bin/ipkg ]] && install_package="ipkg install" # Synology [[ -x /usr/sbin/pkg ]] && install_package="pkg install" # BSD [[ -x /usr/bin/pacman ]] && install_package="pacman -S" # Arch Linux [[ -x /usr/bin/zypper ]] && install_package="zypper install" # Suse Linux [[ -x /usr/bin/emerge ]] && install_package="emerge" # Gentoo [[ -x /usr/bin/yum ]] && install_package="yum install" # RedHat RHEL/CentOS/Fedora [[ -x /usr/bin/apk ]] && install_package="apk add" # Alpine [[ -x /usr/bin/apt-get ]] && install_package="apt-get install" # Debian [[ -x /usr/bin/apt ]] && install_package="apt install" # Ubuntu ;; esac IO:debug "$info_icon System OS : $os_name ($os_kernel) $os_version on $os_machine" IO:debug "$info_icon Package mgt: $install_package" # get last modified date of this script script_modified="??" [[ "$os_kernel" == "Linux" ]] && script_modified=$(stat -c %y "$script_install_path" 2>/dev/null | cut -c1-16) # generic linux [[ "$os_kernel" == "Darwin" ]] && script_modified=$(stat -f "%Sm" "$script_install_path" 2>/dev/null) # for MacOS IO:debug "$info_icon Version : $script_version" IO:debug "$info_icon Created : $script_created" IO:debug "$info_icon Modified : $script_modified" IO:debug "$info_icon Lines : $script_lines lines / md5: $script_hash" IO:debug "$info_icon User : $USER@$HOSTNAME" # if run inside a git repo, detect for which remote repo it is if git status &>/dev/null; then git_repo_remote=$(git remote -v | awk '/(fetch)/ {print $2}') IO:debug "$info_icon git remote : $git_repo_remote" git_repo_root=$(git rev-parse --show-toplevel) IO:debug "$info_icon git folder : $git_repo_root" fi # get script version from VERSION.md file - which is automatically updated by pforret/setver [[ -f "$script_install_folder/VERSION.md" ]] && script_version=$(cat "$script_install_folder/VERSION.md") # get script version from git tag file - which is automatically updated by pforret/setver [[ -n "$git_repo_root" ]] && [[ -n "$(git tag &>/dev/null)" ]] && script_version=$(git tag --sort=version:refname | tail -1) } function Script:initialize() { log_file="" if [[ -n "${TMP_DIR:-}" ]]; then # clean up TMP folder after 1 day Os:folder "$TMP_DIR" 1 fi if [[ -n "${LOG_DIR:-}" ]]; then # clean up LOG folder after 1 month Os:folder "$LOG_DIR" 30 log_file="$LOG_DIR/$script_prefix.$execution_day.log" IO:debug "$config_icon log_file: $log_file" fi } function Os:tempfile() { local extension=${1:-txt} local file="${TMP_DIR:-/tmp}/$execution_day.$RANDOM.$extension" IO:debug "$config_icon tmp_file: $file" temp_files+=("$file") echo "$file" } function Os:import_env() { local env_files if [[ $(pwd) == "$script_install_folder" ]]; then env_files=( "$script_install_folder/.env" "$script_install_folder/.$script_prefix.env" "$script_install_folder/$script_prefix.env" ) else env_files=( "$script_install_folder/.env" "$script_install_folder/.$script_prefix.env" "$script_install_folder/$script_prefix.env" "./.env" "./.$script_prefix.env" "./$script_prefix.env" ) fi local env_file for env_file in "${env_files[@]}"; do if [[ -f "$env_file" ]]; then IO:debug "$config_icon Read dotenv: [$env_file]" local clean_file clean_file=$(Os:clean_env "$env_file") # shellcheck disable=SC1090 source "$clean_file" && rm "$clean_file" fi done } function Os:clean_env() { local input="$1" local output="$1.__.sh" [[ ! -f "$input" ]] && IO:die "Input file [$input] does not exist" IO:debug "$clean_icon Clean dotenv: [$output]" awk <"$input" ' function ltrim(s) { sub(/^[ \t\r\n]+/, "", s); return s } function rtrim(s) { sub(/[ \t\r\n]+$/, "", s); return s } function trim(s) { return rtrim(ltrim(s)); } /=/ { # skip lines with no equation $0=trim($0); if(substr($0,1,1) != "#"){ # skip comments equal=index($0, "="); key=trim(substr($0,1,equal-1)); val=trim(substr($0,equal+1)); if(match(val,/^".*"$/) || match(val,/^\047.*\047$/)){ print key "=" val } else { print key "=\"" val "\"" } } } ' >"$output" echo "$output" } IO:initialize # output settings Script:meta # find installation folder [[ $run_as_root == 1 ]] && [[ $UID -ne 0 ]] && IO:die "user is $USER, MUST be root to run [$script_basename]" [[ $run_as_root == -1 ]] && [[ $UID -eq 0 ]] && IO:die "user is $USER, CANNOT be root to run [$script_basename]" Option:initialize # set default values for flags & options Os:import_env # overwrite with .env if any if [[ $sourced -eq 0 ]]; then Option:parse "$@" # overwrite with specified options if any Script:initialize # clean up folders Script:main # run Script:main program Script:exit # exit and clean up else # just disable the trap, don't execute Script:main trap - INT TERM EXIT fi
Copy to clipboard
Based on
pforret/bashew
Examples
Example:
convert_a_file.sh [action] [outputfile] [inputfile(s)]
Example:
convert_a_file.sh [inputfile]
Example:
send_an_email.sh --from [from] [destination] [subject] [body]
Example:
compress_files.sh --test --method [method} [zipfile] [file(s)]
References
If you often write small bash scripts to automate/facilitate repeating tasks, you don't want to lose time re-implementing option parsing, debug messages, OS detection, confirmation questions. I've been using this
boilerplate bash
code since 2017. The only thing missing was a configurable code generator. And here it is!