Fiche 5 — Permissions, processus et scripts shell

Fiche 05

TP
Lire et modifier des permissions, gérer un processus en arrière-plan, écrire un premier script shell avec variables, arguments et garde d’arguments.
Auteur
Affiliations

Emmanuel Bruno

Université de Toulon

LIS UMR CNRS 7020

Date de publication

2026-11-04

Objectifs

À l’issue de cette fiche, vous serez capable de :

  • lire une ligne ls -l et 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 x signifie pour un fichier et pour un répertoire ;
  • lancer un processus en arrière-plan avec &, le suivre avec jobs, le rejoindre avec wait ;
  • écrire un script exécutable muni d’une ligne shebang, avec variables, guillemets et arguments positionnels ;
  • distinguer export (sous-processus) de source (shell courant) ;
  • faire vérifier ses arguments par un script et lui faire renvoyer un code de retour non nul.
AvertissementCe que cette fiche ne fait pas

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 755 et 644, jobs, wait, arguments positionnels, boucle for.
  • Avancé : garde d’arguments, code de retour non nul, export contre source, 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.

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; }

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.sh

Les 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, d répertoire, l lien 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. r autorise à lister les noms qu’il contient, x autorise à traverser le chemin, c’est-à-dire cd dedans et y atteindre un fichier par son nom. Un répertoire r-- se lit mais est inutilisable ; un --x permet 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 : ajouter x pour le user (propriétaire) ;
  • g-w : retirer w pour le group ;
  • o-r : retirer r pour others ;
  • a+r : ajouter r pour 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.

Astuce

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 obtenu
Solution
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 (le aux est 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 nos sleep plutôt que de les tuer.
Avertissement

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.log
Solution
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 dupont devient 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.sh

export 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 RC2
Solution
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 blocs rwx pour user, group, others.
  • r lire, w écrire, x exécuter un fichier ou traverser un répertoire.
  • Pour un répertoire, r (lister) et x (traverser) sont deux droits différents.
  • Notation symbolique : chmod u+x, g-w, o-r, a+r, combos go+r.
  • Notation numérique : r=4, w=2, x=1 ; 755 pour un exécutable, 644 pour une donnée.
  • bash script.sh lance un script sans avoir besoin du bit x : le fichier est alors un argument.
  • & met en arrière-plan, jobs liste, wait attend, $! 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=valeur sans 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 "$@".
  • export descend vers les sous-processus, source exé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.

Réutilisation