Aide mémoire pour les scripts bash
Auteur : Philippe Le Van - @plv
Date : 3 mars 2022
Introduction
Cette page recense quelques syntaxes utilisables dans des scripts bash.
Tableau associatifs (ou dictionnaires, hashtable)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 | #!/bin/bash
# déclarer la variable comme un tableau associatif
declare -A CONFIG_LIST
# on définit le tableau globalement
CONFIG_LIST=(\
["foo"]="bar" \
["mr blue"]="mr red" \
)
# on ajoute une valeur
CONFIG_LIST['gatekeeper']='zul'
# on parcourt les valeurs et on les affiche
for val in "${CONFIG_LIST[@]}"; do
echo "${val}"
done
# on parcourt les clés et on affiche les clés et les valeurs
# (notez le "!" pour parcourir les clés)
# pensez au " autour du "${!CONFIG_LIST[@]}" sinon ça va pas marcher avec des espaces dans les clés
for key in "${!CONFIG_LIST[@]}"; do
echo "${key}"
echo "${CONFIG_LIST[${key}]}"
done
|
Note
- Uniquement en bash 4, vous devez avoir #!/bin/bash en haut de votre fichier.
- notez les "" autour des noms de variables, sinon il y aura des problèmes avec les clés
qui ont des espaces
Inspiré de l'article stack overflow : How to define hash tables in Bash
Poser une question à l'internaute (pour une confirmation par exemple)
Parfois on veut poser une question du genre "êtes-vous certains de xxx (yes|no)".
Pour ça on utilise dans nos scripts un function question :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | #!/bin/bash
question() {
local question=$1
local choices=$2
local default=$3
echo -e "== $question (${choices}) [${default}] "
read RESPONSE
if [ "x$RESPONSE" = "x" ]; then
RESPONSE=$default
fi
}
question "Etes vous certain de vouloir faire cette action ?" "yes|no" "no"
if [ "$RESPONSE" = "yes" ]; then
echo "vous avez bien confirmé l'opération"
else
echo "Vous avez abandonné l'opération"
fi
|
Parser une ligne de commande bash
Là je donne juste un exemple de script (un script de deploy)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107 | #!/bin/bash
# liste des stacks qu'on peut déployer avec cette commande
STACK_NAME_LIST=(taiga penpot)
# liste des hosts où on peut déployer
HOST_LIST=("srv1.example.com" "srv2.example.com")
# fichier d'environnement
ENV_FILE=".env"
# valeurs par défaut des paramètres
DEPLOY_STACK="./docker-compose.yml"
DEPLOY_HOST="undefined"
STACK_NAME="undefined"
DELETE_STACK="NO"
# help message
function display_help() {
SCRIPT_NAME=$(basename "$0")
echo "${0} [--user xxx] [--help] <stack_name>"
echo " <stack_name> name of the stack to deploy : ($(IFS=\| ; echo "${STACK_NAME_LIST[*]}")) "
echo " --deploy_host xx host where we want to deploy : ($(IFS=\| ; echo "${HOST_LIST[*]}")) "
echo " --stack xx fichier de stack à déployer (relatif au répertoire de ce script)"
echo " --help display this help message"
echo " --user xx deploy with specific user"
echo " --delete-stack delete the entire stack"
echo
echo " Ex:"
echo " ${0} --user philippe --stack ./docker-compose.yml --deploy_host srv1.example.com taiga"
}
# on parse les arguments
while test $# -gt 0; do
_key="$1"
case "$_key" in
--help)
display_help
exit 0
;;
--user)
DEPLOY_USER=$2
shift
;;
--stack)
DEPLOY_STACK=$2
shift
;;
--deploy_host)
DEPLOY_HOST=$2
shift
;;
--delete-stack)
DELETE_STACK=YES
;;
*)
STACK_NAME=$1
;;
esac
shift
done
# on va dans le répertoire du script que veut lancer
ROOT_DIR=$(dirname "${0}")
cd ${ROOT_DIR}
DEPLOY_USER=${DEPLOY_USER:-$USER}
# check if env file exist
if [ ! -f ${ENV_FILE} ]; then
echo "I can't find the .env file. You should copy the env_sample to .env and adapt vars"
exit 1
fi
# import env vars
set -o allexport; source ${ENV_FILE}; set +o allexport
# validate param STACK_NAME
if ! grep -w ${STACK_NAME} <<< "${STACK_NAME_LIST[@]}" > /dev/null; then
echo "ERROR : the stack_name to deploy can be only ($(IFS=\| ; echo "${STACK_NAME_LIST[*]}"))"
display_help;
exit 1
fi
# validate param DEPLOY_HOST
if ! grep -w "${DEPLOY_HOST}" <<< "${HOST_LIST[@]}" > /dev/null; then
echo "ERROR : the host to deploy can be only ($(IFS=\| ; echo "${HOST_LIST[*]}"))"
display_help;
exit 1
fi
export DOCKER_HOST="ssh://${DEPLOY_USER}@${DEPLOY_HOST}"
echo "set DOCKER_HOST to ${DOCKER_HOST}"
if [ ${DELETE_STACK} = "YES" ]; then
question "delete stack ${STACK_NAME}?" "delete ${STACK_NAME}|no" "no"
if [ "$RESPONSE" = "delete ${STACK_NAME}" ]; then
DEPLOY_CMD="docker stack rm ${STACK_NAME}"
echo "DEPLOY_CMD=${DEPLOY_CMD}"
# commande à lancer : ${DEPLOY_CMD}
exit 0
else
echo "delete aborted"
exit 0
fi
fi
DEPLOY_CMD="docker stack deploy --with-registry-auth --prune -c $DEPLOY_STACK ${STACK_NAME}"
echo "DEPLOY_CMD=${DEPLOY_CMD}"
# commande à lancer ${DEPLOY_CMD}
|
Construire une commande à lancer
Parfois on a besoin de construire une commande morceau par morceau, par exemple en fonction de paramètres fournis en ligne de commande.
ici par exemple, on cherche à créer une commande curl qui envoie en POST un fichier binaire sur une URL donnée.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | # on initialiser un tableau qu'on va faire grandir ensuite.
CMD=(curl)
# on ajoute un flag -v à curl si l'utilisateur a demandé le
# mode verbose.
if [ ${VERBOSE} = "yes" ]; then
CMD+=("-v")
fi
CMD+=(--header "X-token: ${TOKEN}" --header "Content-Type: application/octet-stream" -k -X POST)
CMD+=(--data-binary @${PUB_FILE} https://${URL_PARAM}/data)
# pour que la commande soit copiable, j'ajoute des " autour de
# chaque élément du tableau dans la sortie du script.
echo "=> run command : " $(printf "\"%s\" " "${CMD[@]}")
# on lance la commande réellement ici
"${CMD[@]}"
|
Lancer une commande avec un timeout
le principe : timeout <temps en s> <ma commande bash>
| # lis en direct ce qui arrive dans /var/log/syslog pendant
# 5 secondes puis rend la main
timeout 5 tail -f /var/log/syslog
|
Lire une commande temps réel qui ne rend pas la main et la tuer sur une condition
Exemple : docker events
ne rend pas la main. On veut attendre un event en particulier
et récupérer la main après.
| (docker events &) | while read event; do echo ${event}; pkill -f "docker events"; done
|
Points à améliorer :
- je ne suis pas fan du pkill, il faudrait récupérer le PID du docker events
- idée 1 : utiliser $! mais à relier au while : (docker events &) && DOCKER_EVENTS_PID=$!
- il faudrait un timeout : cf paragraphe sur les timeout dans cette page.
Pour aller plus loin
Quelques liens utiles :
Versions
- 3 mars 2022 : construire une commande pas à pas
- 8 fevrier 2022 : commande avec timeout
- 12 octobre 2021 : création