Created
May 3, 2021 11:19
-
-
Save vladshut/a80e840064b0813bdf52d1b28e1cfc14 to your computer and use it in GitHub Desktop.
Resizes an image and squares it up by padding and adding border. (for Instagram)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| ### ============================================================================== | |
| ### SO HOW DO YOU PROCEED WITH YOUR SCRIPT? | |
| ### 1. define the options/parameters and defaults you need in list_options() | |
| ### 2. define dependencies on other programs/scripts in list_dependencies() | |
| ### 3. implement the different actions in main() with helper functions | |
| ### 4. implement helper functions you defined in previous step | |
| ### ============================================================================== | |
| ### Created by vs ( vs ) on 2021-05-03 | |
| ### Based on https://github.com/pforret/bashew 1.16.1 | |
| script_version="0.0.1" # if there is a VERSION.md in this script's folder, it will take priority for version number | |
| readonly script_author="fred.shut.vlad@gmail.com" | |
| readonly script_created="2021-05-03" | |
| 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 | |
| list_options() { | |
| ### 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 | |
| echo -n " | |
| #commented lines will be filtered | |
| flag|h|help|show usage | |
| flag|q|quiet|no output | |
| flag|v|verbose|output more | |
| flag|f|force|do not ask for confirmation (always yes) | |
| flag|r|remove|remove original | |
| option|s|size|size of the output image (default is the smae as original) | |
| option|bs|bordersize|size of the border|0 | |
| option|c|color|color for the border |white | |
| option|l|log_dir|folder for log files |$HOME/log/$script_prefix | |
| option|t|tmp_dir|folder for temp files|/tmp/$script_prefix | |
| option|o|output|out file template (:ofolder: - original file folder, :oname: - original file name, :oext: - original file extension)|:ofolder:/:oname:_squared.:oext: | |
| param|1|input|input files (example: test/*.jpg) | |
| " | grep -v '^#' | grep -v '^\s*$' | |
| } | |
| ##################################################################### | |
| ## Put your main script here | |
| ##################################################################### | |
| main() { | |
| log_to_file "[$script_basename] $script_version started" | |
| action="modify_images" | |
| action=$(lower_case "$action") | |
| case $action in | |
| 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 | |
| check_script_settings | |
| ;; | |
| 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 check | |
| update_script_to_latest | |
| ;; | |
| *) | |
| modify_images | |
| ;; | |
| esac | |
| log_to_file "[$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 | |
| ##################################################################### | |
| modify_images() { | |
| log_to_file "modify_images [$input]" | |
| require_binary "convert" | |
| folder=$(pwd) | |
| i=1 | |
| for img in $input ; do | |
| outputfile=$(outputfile $img) | |
| squareup $img $outputfile | |
| add_border $outputfile $outputfile | |
| echo "${i} ${img} -> ${outputfile}" | |
| if flag_set "remove" | |
| then | |
| echo "Remove original $img" | |
| rm "$img" | |
| fi | |
| i=$(( i+1 )) | |
| done | |
| } | |
| outputfile() { | |
| local infile=$1 | |
| inputfilename=$(basename "$infile") | |
| ofolder=$(dirname "$infile") | |
| oname=${inputfilename%%.*} | |
| oext=${inputfilename##*.} | |
| outputfile=${output//":ofolder:"/$ofolder} | |
| outputfile=${outputfile//":oname:"/$oname} | |
| outputfile=${outputfile//":oext:"/$oext} | |
| echo "$outputfile" | |
| } | |
| squareup() { | |
| # setup temporary images and auto delete upon exit | |
| # use mpc/cache to hold input image temporarily in memory | |
| local infile=$1 | |
| local outfile=$2 | |
| tmpA="$tmp_dir/squareup_$$.mpc" | |
| tmpB="$tmp_dir/squareup_$$.cache" | |
| trap "rm -f $tmpA $tmpB;" 0 | |
| trap "rm -f $tmpA $tmpB; exit 1" 1 2 3 15 | |
| trap "rm -f $tmpA $tmpB; exit 1" ERR | |
| # test if infile exists and compute dimensions | |
| if convert -quiet "$infile" +repage "$tmpA" | |
| then | |
| width=`identify -format "%w" $tmpA` | |
| height=`identify -format "%h" $tmpA` | |
| else | |
| out "--- FILE $infile DOES NOT EXIST OR IS NOT AN ORDINARY FILE, NOT READABLE OR HAS ZERO SIZE ---" | |
| fi | |
| # compute max and min dimensions | |
| if [ $width -gt $height ] | |
| then | |
| max=$width | |
| min=$height | |
| else | |
| max=$height | |
| min=$width | |
| fi | |
| if [ "$size" = "" ] | |
| then | |
| dim=$max | |
| else | |
| # get % value if specified from size | |
| factor=`echo "$size" | sed -n 's/\([.0-9]*\)%$/\1/ p'` | |
| if [ "$factor" = "" ] | |
| then | |
| # test if contains a period | |
| factor2=`echo "$size" | sed -n 's/\([0-9]*[.][0-9]*\)$/\1/ p'` | |
| [ "$factor2" != "" ] && errMsg "SIZE=$size IS NOT AN INTEGER" | |
| fi | |
| # compute pixel equivalent desired size | |
| if [ "$factor" = "" ] | |
| then | |
| dim=$size | |
| [ $width -gt $height ] && scale="${dim}x" || scale="x${dim}" | |
| else | |
| dim=`echo "scale=0; $factor * $max / 100" | bc` | |
| [ $width -gt $height ] && scale="${dim}x" || scale="x${dim}" | |
| fi | |
| fi | |
| # convert trans to none for bgcolor so that background is transparent | |
| [ "$color" = "trans" ] && color="none" | |
| # add resize if size != "" | |
| [ "$size" = "" ] && resize="" || resize="-filter $filter -resize $scale" | |
| # process image | |
| convert \( -size ${dim}x${dim} xc:$color \) \( $tmpA $resize \) -gravity "Center" -composite +repage "$outfile" | |
| } | |
| add_border() { | |
| local infile=$1 | |
| local outfile=$2 | |
| # process image | |
| convert "$infile" -bordercolor "$color" -border "$bordersize" "$outfile" | |
| } | |
| ##################################################################### | |
| ################### DO NOT MODIFY BELOW THIS LINE ################### | |
| ##################################################################### | |
| # 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' | |
| hash() { | |
| length=${1:-6} | |
| if [[ -n $(command -v md5sum) ]]; then | |
| # regular linux | |
| md5sum | cut -c1-"$length" | |
| else | |
| # macos | |
| md5 | cut -c1-"$length" | |
| fi | |
| } | |
| force=0 | |
| help=0 | |
| verbose=0 | |
| #to enable verbose even before option parsing | |
| [[ $# -gt 0 ]] && [[ $1 == "-v" ]] && verbose=1 | |
| quiet=0 | |
| #to enable quiet even before option parsing | |
| [[ $# -gt 0 ]] && [[ $1 == "-q" ]] && quiet=1 | |
| ### stdout/stderr output | |
| initialise_output() { | |
| [[ "${BASH_SOURCE[0]:-}" != "${0}" ]] && sourced=1 || sourced=0 | |
| [[ -t 1 ]] && piped=0 || piped=1 # detect if output is piped | |
| if [[ $piped -eq 0 ]]; then | |
| col_reset="\033[0m" | |
| col_red="\033[1;31m" | |
| col_grn="\033[1;32m" | |
| col_ylw="\033[1;33m" | |
| else | |
| col_reset="" | |
| col_red="" | |
| col_grn="" | |
| col_ylw="" | |
| fi | |
| [[ $(echo -e '\xe2\x82\xac') == '€' ]] && unicode=1 || unicode=0 # detect if unicode is supported | |
| if [[ $unicode -gt 0 ]]; then | |
| char_succ="✅" | |
| char_fail="⛔" | |
| char_alrt="✴️" | |
| char_wait="⏳" | |
| info_icon="🌼" | |
| config_icon="🌱" | |
| clean_icon="🧽" | |
| require_icon="🔌" | |
| else | |
| char_succ="OK " | |
| char_fail="!! " | |
| char_alrt="?? " | |
| char_wait="..." | |
| info_icon="(i)" | |
| config_icon="[c]" | |
| clean_icon="[c]" | |
| require_icon="[r]" | |
| fi | |
| error_prefix="${col_red}>${col_reset}" | |
| } | |
| out() { ((quiet)) && true || printf '%b\n' "$*"; } | |
| debug() { if ((verbose)); then out "${col_ylw}# $* ${col_reset}" >&2; else true; fi; } | |
| die() { out "${col_red}${char_fail} $script_basename${col_reset}: $*" >&2 ; tput bel ; safe_exit ; } | |
| alert() { out "${col_red}${char_alrt}${col_reset}: $*" >&2 ; } | |
| success() { out "${col_grn}${char_succ}${col_reset} $*"; } | |
| announce() { out "${col_grn}${char_wait}${col_reset} $*"; sleep 1 ; } | |
| 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 flag_set ${piped:-0}; then | |
| out "$*" >&2 | |
| else | |
| printf "... %-${rest_of_line}b\r" "$* " >&2 | |
| fi | |
| ) | |
| } | |
| log_to_file() { [[ -n ${log_file:-} ]] && echo "$(date '+%H:%M:%S') | $*" >>"$log_file"; } | |
| ### string processing | |
| lower_case() { echo "$*" | tr '[:upper:]' '[:lower:]'; } | |
| upper_case() { echo "$*" | tr '[:lower:]' '[:upper:]'; } | |
| slugify() { | |
| # slugify <input> <separator> | |
| # slugify "Jack, Jill & Clémence LTD" => jack-jill-clemence-ltd | |
| # slugify "Jack, Jill & Clémence LTD" "_" => jack_jill_clemence_ltd | |
| separator="${2:-}" | |
| [[ -z "$separator" ]] && separator="-" | |
| # shellcheck disable=SC2020 | |
| echo "$1" | | |
| tr '[:upper:]' '[:lower:]' | | |
| tr 'àáâäæãåāçćčèéêëēėęîïííīįìłñńôöòóœøōõßśšûüùúūÿžźż' 'aaaaaaaaccceeeeeeeiiiiiiilnnoooooooosssuuuuuyzzz' | | |
| awk '{ | |
| gsub(/[\[\]@#$%^&*;,.:()<>!?\/+=_]/," ",$0); | |
| gsub(/^ */,"",$0); | |
| gsub(/ *$/,"",$0); | |
| gsub(/ */,"-",$0); | |
| gsub(/[^a-z0-9\-]/,""); | |
| print; | |
| }' | | |
| sed "s/-/$separator/g" | |
| } | |
| title_case() { | |
| # title_case <input> <separator> | |
| # title_case "Jack, Jill & Clémence LTD" => JackJillClemenceLtd | |
| # title_case "Jack, Jill & Clémence LTD" "_" => Jack_Jill_Clemence_Ltd | |
| separator="${2:-}" | |
| # shellcheck disable=SC2020 | |
| echo "$1" | | |
| tr '[:upper:]' '[:lower:]' | | |
| 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 | |
| } | |
| ### interactive | |
| confirm() { | |
| # $1 = question | |
| flag_set $force && return 0 | |
| read -r -p "$1 [y/N] " -n 1 | |
| echo " " | |
| [[ $REPLY =~ ^[Yy]$ ]] | |
| } | |
| ask() { | |
| # $1 = variable name | |
| # $2 = question | |
| # $3 = default value | |
| # not using read -i because that doesn't work on MacOS | |
| local ANSWER | |
| read -r -p "$2 ($3) > " ANSWER | |
| if [[ -z "$ANSWER" ]]; then | |
| eval "$1=\"$3\"" | |
| else | |
| eval "$1=\"$ANSWER\"" | |
| fi | |
| } | |
| trap "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 | |
| safe_exit() { | |
| [[ -n "${tmp_file:-}" ]] && [[ -f "$tmp_file" ]] && rm "$tmp_file" | |
| trap - INT TERM EXIT | |
| debug "$script_basename finished after $SECONDS seconds" | |
| exit 0 | |
| } | |
| flag_set() { [[ "$1" -gt 0 ]]; } | |
| show_usage() { | |
| out "Program: ${col_grn}$script_basename $script_version${col_reset} by ${col_ylw}$script_author${col_reset}" | |
| out "Updated: ${col_grn}$script_modified${col_reset}" | |
| out "Description: Resizes an image and squares it up by padding and adding border." | |
| echo -n "Usage: $script_basename" | |
| list_options | | |
| 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 " …>" | |
| } | |
| } | |
| END {print oneline; print fulltext} | |
| ' | |
| } | |
| check_last_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}')" | |
| progress "Check for latest version - $remote" | |
| git remote update &> /dev/null | |
| if [[ $(git rev-list --count "HEAD...HEAD@{upstream}" 2>/dev/null) -gt 0 ]] ; then | |
| out "There is a more recent update of this script - run <<$script_prefix update>> to update" | |
| fi | |
| fi | |
| # shellcheck disable=SC2164 | |
| popd &> /dev/null | |
| ) | |
| } | |
| update_script_to_latest(){ | |
| # run in background to avoid problems with modifying a running interpreted script | |
| ( | |
| sleep 1 | |
| cd "$script_install_folder" && git pull | |
| ) & | |
| } | |
| show_tips() { | |
| ((sourced)) && return 0 | |
| # shellcheck disable=SC2016 | |
| grep <"${BASH_SOURCE[0]}" -v '$0' \ | |
| | awk \ | |
| -v green="$col_grn" \ | |
| -v yellow="$col_ylw" \ | |
| -v reset="$col_reset" \ | |
| ' | |
| /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 ; | |
| }' | |
| } | |
| check_script_settings() { | |
| if [[ -n $(filter_option_type flag) ]]; then | |
| out "## ${col_grn}boolean flags${col_reset}:" | |
| filter_option_type flag | | |
| while read -r name; do | |
| if ((piped)); then | |
| eval "echo \"$name=\$${name:-}\"" | |
| else | |
| eval "echo -n \"$name=\$${name:-} \"" | |
| fi | |
| done | |
| out " " | |
| out " " | |
| fi | |
| if [[ -n $(filter_option_type option) ]]; then | |
| out "## ${col_grn}option defaults${col_reset}:" | |
| filter_option_type option | | |
| while read -r name; do | |
| if ((piped)); then | |
| eval "echo \"$name=\$${name:-}\"" | |
| else | |
| eval "echo -n \"$name=\$${name:-} \"" | |
| fi | |
| done | |
| out " " | |
| out " " | |
| fi | |
| if [[ -n $(filter_option_type list) ]]; then | |
| out "## ${col_grn}list options${col_reset}:" | |
| filter_option_type list | | |
| while read -r name; do | |
| if ((piped)); then | |
| eval "echo \"$name=(\${${name}[@]})\"" | |
| else | |
| eval "echo -n \"$name=(\${${name}[@]}) \"" | |
| fi | |
| done | |
| out " " | |
| out " " | |
| fi | |
| if [[ -n $(filter_option_type param) ]]; then | |
| if ((piped)); then | |
| debug "Skip parameters for .env files" | |
| else | |
| out "## ${col_grn}parameters${col_reset}:" | |
| filter_option_type param | | |
| while read -r name; do | |
| # shellcheck disable=SC2015 | |
| ((piped)) && eval "echo \"$name=\\\"\${$name:-}\\\"\"" || eval "echo -n \"$name=\\\"\${$name:-}\\\" \"" | |
| done | |
| echo " " | |
| fi | |
| fi | |
| } | |
| filter_option_type() { | |
| list_options | grep "$1|" | cut -d'|' -f3 | sort | grep -v '^\s*$' | |
| } | |
| init_options() { | |
| local init_command | |
| init_command=$(list_options | | |
| 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 ~ /list/ {print $3 "=(); "} | |
| $1 ~ /secret/ {print $3 "=\"\"; "} | |
| ') | |
| if [[ -n "$init_command" ]]; then | |
| eval "$init_command" | |
| fi | |
| } | |
| expects_single_params() { list_options | grep 'param|1|' >/dev/null; } | |
| expects_optional_params() { list_options | grep 'param|?|' >/dev/null; } | |
| expects_multi_param() { list_options | grep 'param|n|' >/dev/null; } | |
| parse_options() { | |
| if [[ $# -eq 0 ]]; then | |
| show_usage >&2 | |
| safe_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=$(list_options | | |
| 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) | |
| debug "$config_icon parameter: ${save_var}=$2" | |
| else | |
| debug "$config_icon flag: $save_option" | |
| fi | |
| eval "$save_option" | |
| else | |
| die "cannot interpret option [$1]" | |
| fi | |
| shift | |
| done | |
| ((help)) && ( | |
| show_usage | |
| check_last_version | |
| out " " | |
| echo "### TIPS & EXAMPLES" | |
| show_tips | |
| ) && safe_exit | |
| ## then run through the given parameters | |
| if expects_single_params; then | |
| single_params=$(list_options | grep 'param|1|' | cut -d'|' -f3) | |
| list_singles=$(echo "$single_params" | xargs) | |
| single_count=$(echo "$single_params" | count_words) | |
| debug "$config_icon Expect : $single_count single parameter(s): $list_singles" | |
| [[ $# -eq 0 ]] && die "need the parameter(s) [$list_singles]" | |
| for param in $single_params; do | |
| [[ $# -eq 0 ]] && die "need parameter [$param]" | |
| [[ -z "$1" ]] && die "need parameter [$param]" | |
| debug "$config_icon Assign : $param=$1" | |
| eval "$param=\"$1\"" | |
| shift | |
| done | |
| else | |
| debug "$config_icon No single params to process" | |
| single_params="" | |
| single_count=0 | |
| fi | |
| if expects_optional_params; then | |
| optional_params=$(list_options | grep 'param|?|' | cut -d'|' -f3) | |
| optional_count=$(echo "$optional_params" | count_words) | |
| debug "$config_icon Expect : $optional_count optional parameter(s): $(echo "$optional_params" | xargs)" | |
| for param in $optional_params; do | |
| debug "$config_icon Assign : $param=${1:-}" | |
| eval "$param=\"${1:-}\"" | |
| shift | |
| done | |
| else | |
| debug "$config_icon No optional params to process" | |
| optional_params="" | |
| optional_count=0 | |
| fi | |
| if expects_multi_param; then | |
| #debug "Process: multi param" | |
| multi_count=$(list_options | grep -c 'param|n|') | |
| multi_param=$(list_options | grep 'param|n|' | cut -d'|' -f3) | |
| debug "$config_icon Expect : $multi_count multi parameter: $multi_param" | |
| ((multi_count > 1)) && die "cannot have >1 'multi' parameter: [$multi_param]" | |
| ((multi_count > 0)) && [[ $# -eq 0 ]] && die "need the (multi) parameter [$multi_param]" | |
| # save the rest of the params in the multi param | |
| if [[ -n "$*" ]]; then | |
| debug "$config_icon Assign : $multi_param=$*" | |
| eval "$multi_param=( $* )" | |
| fi | |
| else | |
| multi_count=0 | |
| multi_param="" | |
| # [[ $# -gt 0 ]] && die "cannot interpret extra parameters" | |
| fi | |
| } | |
| require_binary(){ | |
| binary="$1" | |
| path_binary=$(command -v "$binary" 2>/dev/null) | |
| [[ -n "$path_binary" ]] && debug "️$require_icon required [$binary] -> $path_binary" && return 0 | |
| words=$(echo "${2:-}" | wc -l) | |
| if ((force)) ; then | |
| announce "Installing $1 ..." | |
| case $words in | |
| 0) eval "$install_package $1" ;; | |
| # require_binary ffmpeg -- binary and package have the same name | |
| 1) eval "$install_package $2" ;; | |
| # require_binary convert imagemagick -- binary and package have different names | |
| *) eval "${2:-}" | |
| # require_binary primitive "go get -u github.com/fogleman/primitive" -- non-standard package manager | |
| esac | |
| else | |
| case $words in | |
| 0) install_instructions="$install_package $1" ;; | |
| 1) install_instructions="$install_package $2" ;; | |
| *) install_instructions="${2:-}" | |
| esac | |
| alert "$script_basename needs [$binary] but it cannot be found" | |
| alert "1) install package : $install_instructions" | |
| alert "2) check path : export PATH=\"[path of your binary]:\$PATH\"" | |
| die "Missing program/script [$binary]" | |
| fi | |
| } | |
| folder_prep() { | |
| if [[ -n "$1" ]]; then | |
| local folder="$1" | |
| local max_days=${2:-365} | |
| if [[ ! -d "$folder" ]]; then | |
| debug "$clean_icon Create folder : [$folder]" | |
| mkdir -p "$folder" | |
| else | |
| 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 | |
| } | |
| count_words() { wc -w | awk '{ gsub(/ /,""); print}'; } | |
| recursive_readlink() { | |
| [[ ! -L "$1" ]] && echo "$1" && return 0 | |
| local file_folder | |
| local link_folder | |
| local link_name | |
| file_folder="$(dirname "$1")" | |
| # resolve relative to absolute path | |
| [[ "$file_folder" != /* ]] && link_folder="$(cd -P "$file_folder" &>/dev/null && pwd)" | |
| local symlink | |
| symlink=$(readlink "$1") | |
| link_folder=$(dirname "$symlink") | |
| link_name=$(basename "$symlink") | |
| [[ -z "$link_folder" ]] && link_folder="$file_folder" | |
| [[ "$link_folder" == \.* ]] && link_folder="$(cd -P "$file_folder" && cd -P "$link_folder" &>/dev/null && pwd)" | |
| debug "$info_icon Symbolic ln: $1 -> [$symlink]" | |
| recursive_readlink "$link_folder/$link_name" | |
| } | |
| lookup_script_data() { | |
| readonly script_prefix=$(basename "${BASH_SOURCE[0]}" .sh) | |
| readonly script_basename=$(basename "${BASH_SOURCE[0]}") | |
| readonly execution_day=$(date "+%Y-%m-%d") | |
| #readonly execution_year=$(date "+%Y") | |
| script_install_path="${BASH_SOURCE[0]}" | |
| debug "$info_icon Script path: $script_install_path" | |
| script_install_path=$(recursive_readlink "$script_install_path") | |
| debug "$info_icon Linked path: $script_install_path" | |
| readonly script_install_folder="$( cd -P "$( dirname "$script_install_path" )" && pwd )" | |
| debug "$info_icon In folder : $script_install_folder" | |
| if [[ -f "$script_install_path" ]]; then | |
| script_hash=$(hash <"$script_install_path" 8) | |
| script_lines=$(awk <"$script_install_path" 'END {print NR}') | |
| else | |
| # can happen when script is sourced by e.g. bash_unit | |
| script_hash="?" | |
| script_lines="?" | |
| 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" | |
| debug "$info_icon Shell type : $shell_brand - version $shell_version" | |
| readonly 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) # Ubuntu | |
| os_version=$(lsb_release -r) # 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 | |
| debug "$info_icon System OS : $os_name ($os_kernel) $os_version on $os_machine" | |
| 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 | |
| debug "$info_icon Last modif : $script_modified" | |
| debug "$info_icon Script ID : $script_lines lines / md5: $script_hash" | |
| debug "$info_icon Creation : $script_created" | |
| debug "$info_icon Running as : $USER@$HOSTNAME" | |
| # if run inside a git repo, detect for which remote repo it is | |
| if git status &>/dev/null; then | |
| readonly git_repo_remote=$(git remote -v | awk '/(fetch)/ {print $2}') | |
| debug "$info_icon git remote : $git_repo_remote" | |
| readonly git_repo_root=$(git rev-parse --show-toplevel) | |
| debug "$info_icon git folder : $git_repo_root" | |
| else | |
| readonly git_repo_root="" | |
| readonly git_repo_remote="" | |
| 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) | |
| } | |
| prep_log_and_temp_dir() { | |
| tmp_file="" | |
| log_file="" | |
| if [[ -n "${tmp_dir:-}" ]]; then | |
| folder_prep "$tmp_dir" 1 | |
| tmp_file=$(mktemp "$tmp_dir/$execution_day.XXXXXX") | |
| debug "$config_icon tmp_file: $tmp_file" | |
| # you can use this temporary file in your program | |
| # it will be deleted automatically if the program ends without problems | |
| fi | |
| if [[ -n "${log_dir:-}" ]]; then | |
| folder_prep "$log_dir" 30 | |
| log_file="$log_dir/$script_prefix.$execution_day.log" | |
| debug "$config_icon log_file: $log_file" | |
| fi | |
| } | |
| import_env_if_any() { | |
| env_files=("$script_install_folder/.env" "$script_install_folder/$script_prefix.env" "./.env" "./$script_prefix.env") | |
| for env_file in "${env_files[@]}"; do | |
| if [[ -f "$env_file" ]]; then | |
| debug "$config_icon Read config from [$env_file]" | |
| # shellcheck disable=SC1090 | |
| source "$env_file" | |
| fi | |
| done | |
| } | |
| initialise_output # output settings | |
| lookup_script_data # find installation folder | |
| [[ $run_as_root == 1 ]] && [[ $UID -ne 0 ]] && die "user is $USER, MUST be root to run [$script_basename]" | |
| [[ $run_as_root == -1 ]] && [[ $UID -eq 0 ]] && die "user is $USER, CANNOT be root to run [$script_basename]" | |
| init_options # set default values for flags & options | |
| import_env_if_any # overwrite with .env if any | |
| if [[ $sourced -eq 0 ]]; then | |
| parse_options "$@" # overwrite with specified options if any | |
| prep_log_and_temp_dir # clean up debug and temp folder | |
| main # run main program | |
| safe_exit # exit and clean up | |
| else | |
| # just disable the trap, don't execute main | |
| trap - INT TERM EXIT | |
| fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment