====== Bash ======
===== Substitution dans les variables =====
* Chercher/remplacer : ''${variable/search_pattern/replacement}'' => remplacer la première occurrence de ''search_pattern'' par ''replacement'' dans de contenu de la variable ''variable''.
* Pour chercher explicitement **au début** du contenu de la variable : ''${parameter/#search_pattern/replacement}''
* Pour chercher explicitement **à la fin** du contenu de la variable : ''${parameter/%search_pattern/replacement}''
* Pour remplacer **toutes les occurences** : ''%%${parameter//search_pattern/replacement}%%''
* Mise en **majuscule** (upper case) : ''${variable^}'' => mise en majuscule du première caractère du contenu de la variable ''variable''
* Pour mettre **tout en majuscule** : ''${variable^^}''
* Mise en **minuscule** (lower case) : ''${variable,}'' => mise en minuscule du première caractère du contenu de la variable ''variable''
* Pour mettre **tout en minuscule** : ''${variable,,}''
===== Array =====
* déclaration : ''array=( 1 2 3 )'' ou ''declare -a array=( 1 2 3 )''
* déclaration d'un tableau associatif : ''declare -A array=( ['key1']='value1' ['key2']='value2' ['key3']='value3' )''
* déclaration d'un tableau en lecture seule : ''declare -Ar ro_array=( [...] )'' ou ''declare -ar ro_array=( [...] )''
* ajouter un élément : ''array+=( 4 )''
* ajouter un élément à un tableau associatif : ''array+=( ['key']='value' )''
* lister tous les éléments d'un tableau (=valeur dans le cas d'un tableau associatif) : ''${array[@]}''
* lister toutes les clés d'un tableau associatif : ''${!array[@]}''
* récupérer le nombre d'élements d'un tableau : ''echo ${#array[@]}''
* construire un tableau à partir d'une chaîne de caractères : Cela dépend du séparateur utilisé :
* avec un retour à la ligne : ''%%mapfile -t myarray <<< "$ALL"%%''
* avec un espace (ou autre caractère unique et "simple) : ''%%IFS=" " read -ra myarray <<< "$ALL"%%''
* ajouter des valeurs à un tableau existant : ''%%mapfile -t -O "${#myarray[@)}" myarray <<< "$ALL"%%''
* ajouter depuis un fichier : ''%%mapfile -t myarray < /path/to/file%%''
* ajouter depuis la sortie d'une commande : ''%%mapfile -t myarray < <( grep -vE '^#' /path/to/file | grep -vE '^\s*$' )%%''
* Note : voir la fonction ''explode'' pour une version générique
==== Fonctions utiles ====
=== in_array ===
function in_array() {
local needle=$1 el
shift
for el in "$@"; do
[ "$el" = "$needle" ] && return 0
done
return 1
}
**Utilisation :**
array=(1 2 3)
in_array 1 ${array[@]} && echo IN
in_array 5 ${array[@]} && echo OUT
=== is_empty ===
function is_empty() {
[ $# -gt 0 ] && return 1
return 0
}
**Utilisation :**
array=(1 2 3)
is_empty $array && echo empty
! is_empty $array && echo not empty
=== array_filter ===
function array_filter() {
local values=() x=0 v
for v in "$@"; do
if [[ "$v" == "--" ]]; then
x=1
elif [[ $x -eq 0 ]]; then
values+=( "$v" )
else
mapfile -t values < <( printf '%s\n' "${values[@]}" | grep -Ev "^${v}$" )
fi
done
printf '%s\n' "${values[@]}"
}
**Utilisation :**
a=(a b c d e)
array_filter ${a[@]} -- c e
# Output:
# a
# b
# d
=== array_intersect ===
function array_intersect() {
local result_var=$1
declare -ga "$result_var=()"
shift
local array1=()
local array2=()
local switch_to_array2=0
for v in "$@"; do
if [ "$v" == "--" ]; then
switch_to_array2=1
elif [ $switch_to_array2 -eq 0 ]; then
array2+=( "$v" )
else
array1+=( "$v" )
fi
done
for i in "${array1[@]}"; do
for j in "${array2[@]}"; do
if [[ $i == $j ]]; then
declare -ga "$result_var+=( \"$i\" )"
break
fi
done
done
}
**Utilisation :**
a=(a b c d e)
b=(c d)
array_intersect c "${a[@]}" -- "${b[@]}"
echo "${c[@]}"
# Result:
c d
=== implode ===
function implode() {
local d=${1-} f=${2-}
if shift 2; then
printf %s "$f" "${@/#/$d}"
fi
}
**Utilisation :**
array=(1 2 3)
echo $( implode "," "${array[@]}" )
# Output: 1,2,3
echo -e "- $( implode "\n- " "${array[@]}" )"
# Output:
# - 1
# - 2
# - 3
=== explode ===
function explode() {
local output_var=$1 seperator=$2
declare -ga "$output_var=()"
mapfile -t "$output_var" < <( tr "$seperator" '\n' <<< "${@:3}" | grep -v '^$' )
}
**Utilisation :**
explode myarray " " "1 2 3" "4 5"
declare -p myarray
# Output:
# declare -a myarray=([0]="1" [1]="2" [2]="3" [3]="4" [4]="5" [5]="6")
explode myarray "\n" "
1
2
3"
declare -p myarray
# Output:
# declare -a myarray=([0]="1" [1]="2" [2]="3")
=== check_regex ===
function check_regex() {
[[ $(grep -Ec "$2" <<< "$1") -eq 1 ]] && return 0
return 1
}
=== check_int ===
**Pré-requis :** [[#check_regex]]
function check_int() {
check_regex "$1" '^-?[0-9]+$' || return 1
[[ -n "$2" ]] && [[ $1 -lt $2 ]] && return 1
[[ -n "$3" ]] && [[ $1 -gt $3 ]] && return 1
return 0
}
=== format_duration ===
function format_duration {
local t=$1
local d=$((t/60/60/24))
local h=$((t/60/60%24))
local m=$((t/60%60))
local s=$((t%60))
[[ $d -gt 0 ]] && printf '%d days and ' $d
printf '%02d:%02d:%02d' $h $m $s
}
=== format_size ===
**Pré-requis :** [[#check_int]]
declare -ra _FORMAT_SIZE_UNITS=( tb gb mb kb b )
declare -rA _FORMAT_SIZE_UNITS_FACTOR=(
["tb"]=1099511627776 ["gb"]=1073741824 ["mb"]=1048576 ["kb"]=1024 ["b"]=1 )
function format_size() {
local size="" unit=kb allow_zero=0 negative=0 opt
for opt in "$@"; do
opt="${opt,,}"
if [[ ${#opt} -gt 2 ]] && \
[[ "${_FORMAT_SIZE_UNITS_FACTOR[${opt:2}]:-null}" != "null" ]]; then
unit=${opt:2}
elif [[ ${#opt} -gt 1 ]] && \
[[ "${_FORMAT_SIZE_UNITS_FACTOR[${opt:1}]:-null}" != "null" ]]; then
unit=${opt:1}
elif [[ "$opt" == "--allow-zero" ]] || [[ "$opt" == "-z" ]]; then
allow_zero=1
elif [[ -z "$size" ]]; then
size=$opt
[[ "$size" == "null" ]] && echo -n null && return
check_int "$size" || { echo -n "format_size: invalid value '$size'"; return 1; }
else
echo -n "format_size: invalid parameter '$opt'"
return 1
fi
done
if [[ $size -eq 0 ]]; then
[[ $allow_zero -eq 0 ]] && return
echo -n "0${_FORMAT_SIZE_UNITS[${#_FORMAT_SIZE_UNITS[@]} - 1]}"
elif [[ $size -lt 0 ]]; then
(( size=size*-1 ))
negative=1
fi
(( size=size*${_FORMAT_SIZE_UNITS_FACTOR[$unit]} ))
for unit in "${_FORMAT_SIZE_UNITS[@]}"; do
[[ $size -lt ${_FORMAT_SIZE_UNITS_FACTOR[$unit]} ]] && continue
if [[ $size -eq ${_FORMAT_SIZE_UNITS_FACTOR[$unit]} ]]; then
size=1
else
size=$( echo "scale=1; $size/${_FORMAT_SIZE_UNITS_FACTOR[$unit]}"|bc|sed 's/\.0$//' )
fi
[[ $negative -eq 1 ]] && size=$( echo "$size*-1"|bc )
echo -n "${size}${unit^^}"
return
done
}
=== dump_ass_array ===
function dump_ass_array() {
declare -n aarr="$1"
echo "\"$1\" = {"
for key in "${!aarr[@]}"; do
printf ' "%s" => "%s"\n' "$key" "${aarr[$key]}"
done
echo "}"
}
**Utilisation :**
declare -A myarray
myarray[a]="b"
myarray[c]="d"
dump_ass_array myarray
=== var_dump ===
declare -p variableName
=== sprint ===
**Pré-requis :** [[#implode]]
#
# Styled text printing helper
#
declare -rA COLORS=(
[black]=30 [red]=31 [green]=32 [brown]=33 [blue]=34 [purple]=35 [cyan]=36 [light_grey]=37
[default]=39 [dark_grey]=90 [light_red]=91 [light_green]=92 [yellow]=93 [light_blue]=94
[light_purple]=95 [light_cyan]=96 [white]=97
)
declare -rA BACKGROUND_COLORS=(
[black]=40 [red]=41 [green]=42 [brown]=43 [blue]=44 [purple]=45 [cyan]=46 [ligth_grey]=47
[default]=49 [dark_grey]=100 [light_red]=101 [light_green]=102 [yellow]=103 [light_blue]=104
[light_purple]=105 [light_cyan]=106 [white]=107
)
declare -rA TEXT_STYLES=(
[normal]=0 [bold]=1 [dim]=2 [italic]=3 [underline]=4 [blink]=5 [inverted_colors]=7 [hidden]=8
[strikethrough]=9
)
declare -rA RESET_STYLES=(
[all]=0 [bold]=21 [dim]=22 [underline]=24 [blink]=25 [inverted_colors]=27 [hidden]=28
)
function sprint() {
local idx=1 opt value no_newline=0 output=""
local -a styles=() text=()
__sprint() {
[[ -n "$output" ]] && output+=" "
[[ "${#styles}" -gt 0 ]] && \
output+="\e[$(implode ';' "${styles[@]}")m" && \
styles=()
output+="${text[*]}"
text=()
}
while [[ $idx -le $# ]]; do
opt=${!idx}
case $opt in
-c|--color)
[[ "${#text}" -gt 0 ]] && __sprint
((idx++))
value="${!idx,,}"
if [[ "${COLORS[$value]:-null}" == "null" ]]; then
echo -n "sprint: invalid color '${!idx}'"
return 1
fi
styles+=( "${COLORS[$value]}" )
;;
-b|--bg)
[[ "${#text}" -gt 0 ]] && __sprint
((idx++))
value="${!idx,,}"
if [[ "${BACKGROUND_COLORS[$value]:-null}" == "null" ]]; then
echo -n "sprint: invalid background color '${!idx}'"
return 1
fi
styles+=( "${BACKGROUND_COLORS[$value]}" )
;;
-s|--style)
[[ "${#text}" -gt 0 ]] && __sprint
((idx++))
value="${!idx,,}"
if [[ "${TEXT_STYLES[$value]:-null}" == "null" ]]; then
echo -n "sprint: invalid text style '${!idx}'"
return 1
fi
styles+=( "${TEXT_STYLES[$value]}" )
;;
-r|--reset)
[[ "${#text}" -gt 0 ]] && __sprint
((idx++))
value="${!idx,,}"
if [[ "${RESET_STYLES[$value]:-null}" == "null" ]]; then
echo -n "sprint: invalid reset option '${!idx}'"
return 1
fi
output+="\e[${RESET_STYLES[$value]}m"
;;
-n)
no_newline=1
;;
-h|--help)
echo "usage: sprint [-n] [-c color] [-b color] [-s style] [words] [-r what] [...]"
echo " -c / --color [color] Colored text. Available colors:"
implode ", " "${!COLORS[@]}" | \
fold -w 53 -s | sed "s/^/ /g"
echo
echo " -b / --bg [color] Text background color. Available colors:"
implode ", " "${!BACKGROUND_COLORS[@]}" | \
fold -w 53 -s | sed "s/^/ /g"
echo
echo " -s / --style [style] Text style. Available styles:"
implode ", " "${!TEXT_STYLES[@]}" | \
fold -w 53 -s | sed "s/^/ /g"
echo
echo " -r / --reset [what] Reset some previously specified styles. Available reset clauses:"
implode ", " "${!RESET_STYLES[@]}" | \
fold -w 53 -s | sed "s/^/ /g"
echo
echo " -n Do not add new line"
echo " [words] Words that compose the text"
;;
*)
text+=( "$opt" )
esac
((idx++))
done
__sprint
output+="\e[${RESET_STYLES[all]}m"
if [[ "$no_newline" -eq 1 ]]; then
echo -en "$output"
else
echo -e "$output"
fi
}
**Exemple :**
sprint -c red '[' -s blink ERROR -r blink ']' -r all Really bad error occurred -s dim -s italic '(see log for details)'
Résultat :
{{:informatique:outils:sprint.svg?500|}}
===== Gestion des paramètres =====
#!/bin/bash
DEBUG=0
BIN_PATH="/bin/binary"
EXTRA_ARGS=()
function usage() {
error="$1"
[ -n "$error" ] && echo "$error"
cat << EOF
Usage : $(basename $0) [-d] [-b /path/to/binary]
-b [path] Binary path (default: $BIN_PATH)
-d Debug mode
-X Enable bash tracing (=set -x)
-h Show this message
EOF
[ -n "$error" ] && exit 1
exit 0
}
function debug() {
[ $DEBUG -eq 1 ] && >&2 echo -e "$( date '+%Y-%m-%d %H:%M:%S' ) - $@"
}
idx=1
while [ $idx -le $# ]
do
OPT=${!idx}
case $OPT in
-d)
DEBUG=1
;;
-h)
usage
;;
-b)
((idx++))
BIN_PATH=${!idx}
if [ ! -x "$BIN_PATH" ]
then
usage "Invalid binary path ($BIN_PATH)"
fi
;;
-X)
set -x
;;
*)
EXTRA_ARGS+=( $OPT )
;;
esac
((idx++))
done
debug "Extra args: ${EXTRA_ARGS[@]}"
===== Barre de progression =====
declare -A PBARS
# Create a progress bar
# Arguments:
# - the progress bar title (default: Progress)
# - total count (default: 100)
# - bar size (default: use all the width of the terminal with a minimum of 5 caracters)
# - the name of the variable use to store the progress bar ID (default: PBAR)
function pbar_create() {
local id=${4:-PBAR}
# Initialize progress bar information
PBARS["${id}_START_TIME"]="$( date +%s )"
[ -n "$1" ] && PBARS["${id}_TITLE"]="$1" || PBARS["${id}_TITLE"]="Progress"
[ -n "$2" ] && PBARS["${id}_TOTAL"]="$2" || PBARS["${id}_TOTAL"]=100
[ -n "$3" ] && PBARS["${id}_SIZE"]="$3" || PBARS["${id}_SIZE"]=0
PBARS["${id}_CURRENT"]=0
PBARS["${id}_LAST_UPDATE"]=0
PBARS["${id}_END_TIME"]=0
# Draw the progress bar for a first time
pbar_draw "$id"
}
# Finish a progress bar
# Arguments:
# - the ID of the progress bar (default: PBAR)
function pbar_finish() {
local id=${1:-PBAR}
# Force a last update of the progess bar
PBARS["${id}_END_TIME"]="$( date +%s )"
pbar_draw "$@"
# Unset progress bar info
unset 'PBARS[${id}_START_TIME]'
unset 'PBARS[${id}_TITLE]'
unset 'PBARS[${id}_TOTAL]'
unset 'PBARS[${id}_CURRENT]'
unset 'PBARS[${id}_LAST_UPDATE]'
unset 'PBARS[${id}_END_TIME]'
echo
}
# Draw the progress bar
# Arguments:
# - the ID of the progress bar (default: PBAR)
# - extra message to display in the progress bar (before the ETA, optional)
# - all extra arguments will be use to compute the extra message using printf
function pbar_draw() {
local id=${1:-PBAR}
# Compute extra message
local extra=${2:-}
# shellcheck disable=SC2059
[[ -n "$extra" ]] && [[ $# -gt 2 ]] && extra=$( printf "$extra" "${@:3}" )
# Only update progress bar one time by second
local now; now=$(date +%s)
[[ "${PBARS[${id}_END_TIME]}" -eq 0 ]] && [[ $now -eq ${PBARS[${id}_LAST_UPDATE]} ]] && return
# Compute progress percentage
local perc
(( perc=${PBARS[${id}_CURRENT]}*100/${PBARS[${id}_TOTAL]} ))
# Compute line without the progress bar
local line line_items line_pad size line_length term_height term_width bar_done bar_pad
line_items=(
"${PBARS[${id}_TITLE]}"
"[]"
"${PBARS[${id}_CURRENT]}/${PBARS[${id}_TOTAL]} (${perc}%)"
)
[ -n "$extra" ] && line_items+=( "- $extra" )
# Add ETA (or total duration if finish)
if [[ "${PBARS[${id}_END_TIME]}" -eq 0 ]]; then
# Compute duration, total duration, ETA & speed
local duration total_duration speed eta
(( duration=now-${PBARS[${id}_START_TIME]} ))
if [[ "${PBARS[${id}_CURRENT]}" -gt 0 ]]; then
(( total_duration=duration*${PBARS[${id}_TOTAL]}/${PBARS[${id}_CURRENT]} ))
speed=$( bc <<< "scale=1; ${PBARS[${id}_CURRENT]}/$duration" )
else
total_duration=0
speed="?"
fi
(( eta=total_duration-duration ))
line_items+=(
"- ETA: $(format_duration $eta)"
"- $( printf "(%s / %s, %s/s)" "$(format_duration $duration)" "$(format_duration $total_duration)" "$speed" )"
)
else
local total_duration
(( total_duration=${PBARS[${id}_END_TIME]}-${PBARS[${id}_START_TIME]} ))
line_items+=( "- Total duration: $(format_duration $total_duration)" )
fi
# Compute progress bar length (if not configured)
# shellcheck disable=SC2034
read -r term_height term_width < <(stty size)
size=${PBARS[${id}_SIZE]}
if [[ "$size" -eq 0 ]]; then
line_length=$( wc -c <<< "${line_items[*]}" )
(( size=term_width-line_length ))
[[ $size -lt 5 ]] && size=5
fi
# Set progress bar text
(( bar_done=perc*size/100 ))
(( bar_pad=size-bar_done ))
line_items[1]="[$(printf "%${bar_done}s"|tr ' ' '#')$(printf "%${bar_pad}s"|tr ' ' '-')]"
# Add line padding (if need)
(( line_pad=term_width-${#line_items} ))
[[ $line_pad -gt 0 ]] && line_items+=( "$(printf "%${line_pad}s")" )
# Compute & display line (strip the terminal width)
line="${line_items[*]}"
echo -en "\r${line:0:$term_width}"
# Update last progress bar update time
PBARS[${id}_LAST_UPDATE]=$now
}
# Increment the progress bar
# Arguments:
# - the step (default: 1)
# - the ID of the progress bar (default: PBAR)
# - extra message to display in the progress bar (before the ETA, optional)
# - all extra arguments will be use to compute the extra message using printf
function pbar_increment() {
local step=${1:-1} id=${2:-PBAR}
# Increment the progress bar state
((PBARS[${id}_CURRENT]+=step))
# Draw the progress bar
pbar_draw "${@:2}"
}
**Utilisation :**
pbar_create "Test" 20
for i in $( seq 1 20 ); do
pbar_increment
sleep 0.1
done
pbar_finish
**Ajout d'info avant l'ETA :**
pbar_create "Test" 20
for i in $( seq 1 20 ); do
pbar_increment "" "" "%d iteration(s) - %d found(s)" $i $(( i/2 ))
sleep 0.1
done
pbar_finish "" "" "%d iteration(s) - %d found(s)" $i $(( i/2 ))
La fonction [[#format_duration]] est nécessaire.