WORK=~/work/assignments/shell_tp/fiche05
rm -rf "${WORK:?}" && mkdir -p "${WORK}" && cd "${WORK}" || { echo "ERREUR : création du répertoire de travail impossible"; exit 1; }Fiche 5 — Permissions, processus et scripts shell
Fiche 05
Objectifs
À l’issue de cette fiche, vous serez capable de :
- lire une ligne
ls -let identifier les trois catégories (user, group, others) ; - modifier des droits en notation symbolique (
u+x,g-w,o-r,a+r) et en notation numérique (755,644) ; - expliquer ce que
xsignifie pour un fichier et pour un répertoire ; - lancer un processus en arrière-plan avec
&, le suivre avecjobs, le rejoindre avecwait; - écrire un script exécutable muni d’une ligne shebang, avec variables, guillemets et arguments positionnels ;
- distinguer
export(sous-processus) desource(shell courant) ; - faire vérifier ses arguments par un script et lui faire renvoyer un code de retour non nul.
nohup, disown, screen et tmux sont mentionnés pour situer le détachement d’un processus : ils ne sont jamais exécutés ici, pas plus que top ou htop, qui sont des interfaces interactives. Un cours dédié traite l’ordonnancement et les signaux.
Parcours
- Minimal : lire
ls -l,chmod u+x, exécuter./script.sh, variables simples. Suivre ce seul parcours donne un score partiel au bilan final, qui cumule les quatorze vérifications. - Approfondi : notation numérique
755et644,jobs,wait, arguments positionnels, bouclefor. - Avancé : garde d’arguments, code de retour non nul,
exportcontresource, exécution en arrière-plan.
Préparation de l’environnement de travail
Le rm -rf est borné à ce sous-répertoire ; "${WORK:?}" interdit toute suppression si la variable était vide. Cette fiche ne modifie les permissions d’aucun fichier situé hors de ${WORK}, et ne lance jamais chmod -R sur une arborescence personnelle.
Si une cellule plus bas affiche un message commençant par ERREUR : — dans cette fiche surtout ERREUR : dossier absent, et ERREUR : création du répertoire de travail impossible pour la préparation —, la garde fait exit 1, ce qui termine le shell de la cellule : le moteur de calcul — le programme qui exécute les cellules, pas le noyau du système — en redémarre un neuf, sans variable ni répertoire courant, et la cellule en échec est marquée comme exécutée, pas comme fausse. Si c’est bash: WORK: parameter null or not set qui s’affiche, la variable était déjà perdue : la garde ne s’exécute pas et le reste de la cellule tourne dans un répertoire quelconque. Dans les deux cas, relancer la préparation puis toutes les cellules qui suivent, dans l’ordre, est la seule reprise sûre ; la préparation rm -rf le répertoire au passage.
Point de contrôle
pwd doit afficher un chemin terminé par shell_tp/fiche05. Sinon, réexécutez la cellule de préparation puis toutes les cellules qui suivent, dans l’ordre : relancer la seule préparation laisserait la fiche dans un état half-fait.
cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
pwd
echo "FICHE : 05_shell_P_permissions-scripting"La sonde de permissions
ls -l montre rw-r--r-- : trois blocs de trois caractères (user, group, other). Le script qui suit reconstruit cette chaîne à partir du mode octal lu par stat, traduit chiffre par chiffre, car les colonnes de ls -l ne se ressemblent pas entre macOS et Linux ; le mode octal, si. C’est portable, et c’est ce que nous affirmons ici.
Le motif employé par la cellule, cat > fichier <<'EOF', s’appelle un ici-document (heredoc) : tout ce qui suit jusqu’à une ligne EOF seule part dans l’entrée standard de cat, qui l’écrit dans fichier. C’est ainsi que la fiche fabrique ses scripts sans éditeur. Les guillemets autour de 'EOF' sont importants : sans eux, le shell développerait $1, $# et $(…) pendant l’écriture au lieu de les laisser dans le fichier. Toutes les cellules qui suivent utilisent ce même motif ; le relire une fois suffit.
cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
cat > perms.sh <<'EOF'
#!/bin/bash
# Affiche les droits d'un fichier sous la forme rwxr-xr-x (9 caracteres).
# Le mode est lu en OCTAL via stat, puis chaque chiffre est traduit par une
# table. On ne compare jamais une sortie de ls -l : ses colonnes changent d'un
# systeme a l'autre (groupes, taille, date, ACL), le mode octal non.
f=$1
oct=$(stat -c '%a' "$f" 2>/dev/null || stat -f '%Lp' "$f" 2>/dev/null || stat -f '%p' "$f" 2>/dev/null)
if [ -z "$oct" ]; then
echo "inconnu"
exit 1
fi
case ${#oct} in
4) oct=${oct#?} ;;
3) : ;;
2) oct=0$oct ;;
1) oct=00$oct ;;
esac
BITS="--- --x -w- -wx r-- r-x rw- rwx"
out=""
for i in 0 1 2; do
d=${oct:i:1}
out="$out$(echo $BITS | cut -d' ' -f$((d + 1)))"
done
printf '%s\n' "$out"
EOF
chmod u+x perms.sh
./perms.sh perms.shLes droits d’un fichier
ls -l ouvre la ligne par dix caractères : le type, puis trois blocs de trois droits, du propriétaire vers les autres.
-rwxr-xr-- 1 user group 1234 Jan 31 12:00 script.sh
drwxr-xr-- 1 user group 4096 Jan 31 12:00 mon_dossier
- le premier caractère est le type :
-fichier ordinaire,drépertoire,llien symbolique ; - les trois suivants : user, le propriétaire ;
- les trois suivants : group, le groupe du fichier ;
- les trois derniers : others, tout le reste du monde.
Trois catégories, trois permissions : r lecture, w écriture, x exécution.
Le x ne veut pas dire la même chose selon la nature de l’entrée.
- Fichier : on peut le lancer comme un programme (shell, binaire). C’est un droit, pas une capacité : sans interpréteur ni format exécutable, le noyau refuse, droits ou non.
- Répertoire : deux droits distincts.
rautorise à lister les noms qu’il contient,xautorise à traverser le chemin, c’est-à-direcddedans et y atteindre un fichier par son nom. Un répertoirer--se lit mais est inutilisable ; un--xpermet l’accès aux fichiers sans pouvoir en dresser la liste.
cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
echo "sans x :"
mkdir -p -m 700 sousdossier && chmod 700 sousdossier
(cd sousdossier && echo "on est entre") || echo "refuse"
echo "avec x :"
chmod 711 sousdossier
(cd sousdossier && echo "on est entre") || echo "refuse"cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
echo "couche fichier" > demo.txt
chmod 644 demo.txt
echo "--- demo.txt en 644, bits d'execution : ---"
[ -x demo.txt ] && echo "x present" || echo "x absent"
echo "--- tentative d'execution : ---"
./demo.txt 2>/dev/null
RC_FICHIER_SANS_X=$?
echo "Code de retour de ./demo.txt : ${RC_FICHIER_SANS_X}"
echo "--- meme fichier lance par son interpreteur : ---"
bash demo.txt 2>/dev/null
RC_AVEC_BASH=$?
echo "Code de retour de bash demo.txt : ${RC_AVEC_BASH}"Deux leçons : x est un garde-fou que le noyau consulte avant de lancer (Permission denied), et bash demo.txt contourne ce garde-fou parce que le fichier cesse d’être le programme exécuté pour devenir l’argument d’un autre programme. C’est la définition exacte du x : autoriser le noyau à charger le fichier comme programme.
chmod : notation symbolique
chmod cible une catégorie, puis ajoute (+), retire (-) ou impose (=) une permission.
u+x: ajouterxpour le user (propriétaire) ;g-w: retirerwpour le group ;o-r: retirerrpour others ;a+r: ajouterrpour all, les trois catégories à la fois.
cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
chmod u+x demo.txt
echo "u+x : $(./perms.sh demo.txt)"
chmod g-w demo.txt
echo "g-w : $(./perms.sh demo.txt)"
chmod o-r demo.txt
echo "o-r : $(./perms.sh demo.txt)"
chmod a+r demo.txt
echo "a+r : $(./perms.sh demo.txt)"
MODE_SYM=$(./perms.sh demo.txt)Les quatre chaînes obtenues sont déterministes : elles ne dépendent ni de la machine, ni de l’heure, ni du contenu du fichier.
chmod : notation numérique
Chaque bloc rwx est un nombre : r=4, w=2, x=1, et l’on additionne les bits présents.
7=rwx(4+2+1) ;5=r-x(4+0+1) ;4=r--(4+0+0) ;6=rw-(4+2+0).
cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
chmod 755 demo.txt
MODE_755=$(./perms.sh demo.txt)
echo "755 : $MODE_755"
chmod 644 demo.txt
MODE_644=$(./perms.sh demo.txt)
echo "644 : $MODE_644"755 script.sh et 644 fichier.txt sont les deux modes usuels : un exécutable que tout le monde peut lancer, un fichier de données que tout le monde peut lire mais que seul le propriétaire modifie.
chmod -R applique le mode à toute l’arborescence. Jamais sur un répertoire personnel réel, et jamais ici hors de ${WORK} : un -R mal ciblé est la façon la plus rapide de casser un projet.
Exercice 1 : les deux notations, le même résultat
Sur une copie de perms.sh, obtenez rwxr--r-- en une commande symbolique à partir de 700, puis vérifiez avec la sonde que 644 et u+rwx donnent bien la chaîne attendue.
cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
# A vous d'ecrire :
# 1. copier perms.sh dans ex1.sh
# 2. partir de 700, ajouter la lecture pour le groupe et pour les autres
# 3. afficher le mode obtenuSolution
cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
cp perms.sh ex1.sh
chmod 700 ex1.sh
chmod go+r ex1.sh
EX1_MODE=$(./perms.sh ex1.sh)
echo "ex1.sh : $EX1_MODE"ex1.sh : rwxr--r--
go+r est un combo : il touche g et o en une seule fois, sans passer par a.
Un processus
Un processus est un programme en cours d’exécution. Chacun porte un PID (Process IDentifier) unique, un utilisateur, un état et une consommation de CPU et de mémoire.
- En avant-plan (foreground) : le processus occupe le terminal ; la invite est rendue inutilisable jusqu’à sa fin.
- En arrière-plan (background) : le terminal reste libre, le processus tourne à côté.
Quand le terminal se ferme, les processus qu’il a lancés sont tous terminés, avant-plan comme arrière-plan. Un processus en arrière-plan ne survit à la fermeture que s’il est détaché : nohup, disown, ou un gestionnaire de sessions comme screen ou tmux. Rien de tout cela n’est exécuté dans cette fiche : un terminal qui rend, à la seconde près, n’est pas reproductible.
ps aux: liste les processus de la machine (leauxest un bloc d’options, pas un pipeline).&: lance la commande en arrière-plan, par exemple./script.sh &.jobs: liste les tâches d’arrière-plan du shell courant.wait: bloque jusqu’à la fin des tâches arrière-plan.kill 1234: envoie un signal à un PID. Cette fiche ne l’emploie pas : nous attendons nossleepplutôt que de les tuer.
Une liste de processus n’est pas une sortie d’exercice : elle dépend de la machine, de l’instant et de tout ce qui tourne à côté. ps s’affiche pour comprendre, jamais pour être comparé à une valeur attendue.
cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
sleep 2 &
BG_JOBS=$(jobs -p | wc -l | tr -d ' ')
echo "taches en arriere-plan : $BG_JOBS"
BG_PID=$!
echo "PID de la tache de fond : $BG_PID"
wait
echo "plus aucune tache en attente : $(jobs -p | wc -l | tr -d ' ')"Exercice 2 : script en arrière-plan
Créez un script qui écrit une ligne, lancez-le en arrière-plan avec sa sortie redirigée vers un fichier, attendez-le avec wait, puis lisez le fichier.
cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
# A vous d'ecrire :
# 1. creer trace.sh qui echo "couche en arriere-plan"
# 2. chmod u+x
# 3. le lancer en arriere-plan, stdout vers trace.log
# 4. wait, puis cat trace.logSolution
cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
cat > trace.sh <<'EOF'
#!/bin/bash
echo "couche en arriere-plan"
EOF
chmod u+x trace.sh
./trace.sh > trace.log 2>&1 &
BG_ATTENDU=$!
wait
TRACE_LIGNE=$(cat trace.log)
echo "trace.log : $TRACE_LIGNE"[1] 823
[1]+ Done ./trace.sh > trace.log 2>&1
trace.log : couche en arriere-plan
$! est le PID de la dernière tâche lancée : ici, notre propre sleep ou notre propre script, jamais un processus emprunté à quelqu’un d’autre.
Variables et guillemets
Une variable shell se crée sans déclaration, s’affecte avec = sans espaces, se lit avec $. Les accolades ${} délimitent le nom quand il est collé à du texte.
cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
NOM=alice
echo "val=$NOM"
echo "avec accolades : ${NOM} Dupont"
echo 'simple : $NOM'
echo "double : $NOM"- guillemets simples : littéral, aucune expansion ;
- guillemets doubles : expansion des variables et des commandes ;
- sans guillemets : les espaces découpent le mot, un nom comme
alice dupontdevient deux arguments.
Les arguments d’un script
$0: le nom sous lequel le script a été appelé ;$1,$2, … : les arguments positionnels, dans l’ordre ;$#: le nombre d’arguments ;$@: tous les arguments, chacun préservé entre guillemets si on écrit"$@".
cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
cat > argres.sh <<'EOF'
#!/bin/bash
echo "nom : $0"
echo "nombre : $#"
echo "tous : $@"
echo "premier: $1"
echo "second : $2"
EOF
chmod u+x argres.sh
./argres.sh un deux trois
ARGS_SANS_APPEL=$#
./argres.shexport contre source
Deux façons de faire passer de l’information entre un script et son shell, incompatibles :
- un script lancé (
./max.sh) s’exécute dans un sous-processus : ce qu’il modifie meurt avec lui ; une variable n’est visible là-dedans que si elle a étéexportée par le parent ; - un script exécuté par
source(.inclus) s’exécute dans le shell courant : ses affectations survivent à l’appel.
cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
cat > max.sh <<'EOF'
#!/bin/bash
# max.sh : affiche le maximum des arguments recus.
if [ $# -eq 0 ]; then
echo "Usage: $0 nombre1 nombre2 ..."
exit 1
fi
FICHE_MAX=$1
for NUM in "$@"; do
if [ "$NUM" -gt "$FICHE_MAX" ]; then
FICHE_MAX=$NUM
fi
done
echo "Le maximum est : $FICHE_MAX"
export FICHE_MAX
EOF
chmod u+x max.sh
FICHE_MAX=0
echo "avant source : $FICHE_MAX"
./max.sh 10 25 7 42 3
echo "lance (sous-processus) : $FICHE_MAX"
source max.sh 10 25 7 42 3
APRES_SOURCE=$FICHE_MAX
echo "source (shell courant) : $APRES_SOURCE"Le nom FICHE_MAX est volontairement spécifique à cette fiche, et la cellule suivante remet la variable à zéro : source laisse une trace dans la session, une fiche doit savoir l’effacer.
cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
cat > showvar.sh <<'EOF'
#!/bin/bash
echo "enfant : FICHE_VAR=[${FICHE_VAR-non definie}]"
EOF
chmod u+x showvar.sh
FICHE_VAR=42
./showvar.sh
export FICHE_VAR
./showvar.sh
FICHE_VISITE_ENFANT=$?
echo "et dans l'autre sens, l'enfant ne remonte jamais rien :"
cat > setvar.sh <<'EOF'
#!/bin/bash
FICHE_VAR=99
EOF
chmod u+x setvar.sh
./setvar.sh
echo "parent apres ./setvar.sh : $FICHE_VAR"cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
unset FICHE_VAR
FICHE_MAX=0
echo "session propre : FICHE_VAR=${FICHE_VAR-absente}, FICHE_MAX=$FICHE_MAX"Un script qui vérifie ses arguments
Un script utile regarde ce qu’on lui passe, et le dit clairement quand ça manque. Le code de retour est son canal de communication vers le shell : 0 pour un succès, toute autre valeur pour un échec. $? contient le code de la dernière commande exécutée.
cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
cat > garde.sh <<'EOF'
#!/bin/bash
# garde.sh : exige un argument, renvoie 1 quand il manque.
if [ $# -eq 0 ]; then
echo "Usage: $0 NOM"
exit 1
fi
echo "Bonjour, $1 !"
EOF
chmod u+x garde.sh
./garde.sh
STATUS_SANS_ARG=$?
./garde.sh alice > /dev/null
STATUS_AVEC_ARG=$?Le exit 1 est dans le script, donc dans un processus enfant : il ferme garde.sh, jamais la session du notebook. Un exit écrit dans la cellule elle-même a un autre effet : il termine le shell de cette cellule, et le moteur de calcul en redémarre un neuf. La session survit, pas son état — et toutes les cellules qui suivent repartent donc d’un shell vide.
Exercice 3 : votre premier script robuste
Écrivez fiche.sh : sans argument, il affiche un message d’usage et renvoie 1 ; avec des arguments, il affiche leur nombre et le premier.
cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
# A vous d'ecrire :
# 1. heredoc citee : cat > fiche.sh << 'EOF' ... EOF
# 2. garde [ $# -eq 0 ] avec exit 1 a l'interieur du script
# 3. chmod u+x, puis ./fiche.sh et ./fiche.sh alpha beta
# 4. capturer les deux codes de retour dans RC1 et RC2Solution
cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
cat > fiche.sh <<'EOF'
#!/bin/bash
# fiche.sh : signale les arguments manquants par un code de retour non nul.
if [ $# -eq 0 ]; then
echo "Usage: $0 argument1 [argument2 ...]"
exit 1
fi
echo "arguments recus : $#"
echo "premier argument : $1"
EOF
chmod u+x fiche.sh
./fiche.sh > /dev/null
RC1=$?
./fiche.sh alpha beta
RC2=$?
echo "RC1=$RC1 RC2=$RC2"arguments recus : 2
premier argument : alpha
RC1=1 RC2=0
Auto-vérification
Quatorze vérifications, réparties sur les trois cellules ci-dessous. Chacune correspond à un fait démontré par une cellule exécutée de cette fiche — mais plusieurs comparent des variables capturées dans le shell ($MODE_SYM, $APRES_SOURCE, $STATUS_SANS_ARG, $TRACE_LIGNE) plutôt que l’état du disque. Ce bilan certifie donc que les cellules se sont exécutées dans ce shell, dans l’ordre : il ne prouve pas que les fichiers existent encore, et il ne remplace pas un ls de contrôle.
cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
TOTAL_OK=0
check() {
if [ "$2" = "$3" ]; then
echo "OK $1 : $2"
TOTAL_OK=$((TOTAL_OK + 1))
else
echo "KO $1 : obtenu [$2], attendu [$3]"
fi
}
check "chaine symbolique u+x,g-w,o-r,a+r" "$MODE_SYM" "rwxr--r--"
check "mode 755" "$MODE_755" "rwxr-xr-x"
check "mode 644" "$MODE_644" "rw-r--r--"
check "exercice 1 : go+r" "$EX1_MODE" "rwxr--r--"
check "traverser un repertoire avec x" "$(cd sousdossier && echo entre)" "entre"
check "x absent sur un fichier ordinaire" "$( [ -x demo.txt ] && echo present || echo absent )" "absent"cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
check "execution refusee sans x" "$RC_FICHIER_SANS_X" "126"
check "variable : affectation sans espaces" "$(NOM=alice; echo val=$NOM)" "val=alice"
check "guillemets simples : aucun $ expandé" "$(echo 'simple : $NOM')" 'simple : $NOM'
check "arguments : \$# hors appel" "$ARGS_SANS_APPEL" "0"
check "max.sh : source modifie le shell courant" "$APRES_SOURCE" "42"
check "max.sh : le parent repart a 0 apres nettoyage" "$FICHE_MAX" "0"
check "garde.sh : code de retour sans argument" "$STATUS_SANS_ARG" "1"cd "${WORK:?}" || { echo "ERREUR : dossier absent ${WORK}"; exit 1; }
check "script en arriere-plan : trace ecrite" "$TRACE_LIGNE" "couche en arriere-plan"
echo "TOTAL : $TOTAL_OK / 14"Les deux modes reconstruits par la sonde (755 et 644) et la ligne 126 sont les trois vérifications qui distinguent une lecture attentive d’une lecture rapide : un mode n’est pas une chaîne ls -l, 126 est le code que le shell renvoie quand le noyau refuse le lancement, et un exit 1 de script n’est jamais un exit de cellule.
À retenir
ls -l: un caractère de type, puis trois blocsrwxpour user, group, others.rlire,wécrire,xexécuter un fichier ou traverser un répertoire.- Pour un répertoire,
r(lister) etx(traverser) sont deux droits différents. - Notation symbolique :
chmod u+x,g-w,o-r,a+r, combosgo+r. - Notation numérique :
r=4,w=2,x=1;755pour un exécutable,644pour une donnée. bash script.shlance un script sans avoir besoin du bitx: le fichier est alors un argument.&met en arrière-plan,jobsliste,waitattend,$!donne le dernier PID.- À la fermeture du terminal, l’arrière-plan meurt aussi, sauf détachement (
nohup,screen,tmux). - Script : fichier texte, ligne shebang
#!/bin/bash,chmod u+x, lancé par./script.sh. - Variable :
NOM=valeursans espaces, lue par$NOM, délimitée par${NOM}si collée à du texte. '...'littéral,"..."avec expansion.- Arguments :
$0,$1,$#,$@; garde :if [ $# -eq 0 ], boucle :for A in "$@". exportdescend vers les sous-processus,sourceexécute dans le shell courant.- Un script robuste vérifie ses arguments et renvoie un code non nul : il se branche ainsi sur
&&et||.
Trois réflexes : ne jamais poser chmod -R hors d’un répertoire de travail borné, tester les droits par leurs bits plutôt qu’en comparant une sortie ls -l, et faire en sorte qu’un script parle à son appelant par son code de retour, pas seulement par ce qu’il affiche.