Skip to content

Instantly share code, notes, and snippets.

@vladshut
Created May 3, 2021 11:19
Show Gist options
  • Select an option

  • Save vladshut/a80e840064b0813bdf52d1b28e1cfc14 to your computer and use it in GitHub Desktop.

Select an option

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)
#!/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