Exécuter un script shell lance un nouveau processus, un sous-shell.
Un sous-shell est une instance séparée du gestionnaire de commande -- le shell qui vous donne l'invite sur la console ou dans une fenêtre xterm. De la même façon que vos commandes sont interprétées sur l'invite de commandes, un script traite en flot une liste de commandes. Chaque script shelle en cours d'exécution est un sous-processus (processus enfant) du shell parent shell.
Un script shell peut lancer lui-même des sous-processus. Ces sous-shells permettent au script de faire de l'exécution en parallèle, donc d'exécuter différentes tâches simultanément.
#!/bin/bash # subshell-test.sh ( # Parenthèses à l'intérieur, donc un sous-shell... while [ 1 ] # Boucle sans fin. do echo "Sous-shell en cours d'exécution..." done ) # Le script s'exécutera sans jamais s'arrêter, #+ au moins tant que vous ne ferez pas Ctl-C. exit $? # Fin du script (mais l'exécution ne parviendra jamais jusqu'ici). Maintenant, exécutez le script : sh subshell-test.sh et tant que le script s'exécute, à partir d'un autre terminal X : ps -ef | grep subshell-test.sh UID PID PPID C STIME TTY TIME CMD 500 2698 2502 0 14:26 pts/4 00:00:00 sh subshell-test.sh 500 2699 2698 21 14:26 pts/4 00:00:24 sh subshell-test.sh ^^^^ Analyse : PID 2698, le script, a lancé le sous-shell de PID 2699. Note : la ligne "UID ..." peut être filtrée par la commande grep mais elle est laissée ici dans un but démonstratif.
En général, une commande externe dans un script lance un sous-processus [103] alors qu'une commande intégrée Bash ne le fait pas. Pour cette raison, les commandes intégrées s'exécutent plus rapidement et utilisent moins de ressources que leurs commandes externes équivalentes.
Liste de commandes entre parenthèses
Une liste de commandes placées entre parenthèses est exécutée sous forme de sous-shells
Les variables utilisées dans un sous shell ne sont pas visibles en dehors du code du sous-shell. Elles ne sont pas utilisables par le processus parent, le shell qui a lancé le sous-shell. En fait, ce sont des variables locales au processus enfant.
Exemple 21.1. Étendue des variables dans un sous-shell
#!/bin/bash # subshell.sh echo echo "Nous sommes à l'extérieur du sous-shell." echo "Niveau de sous-shell À L'EXTÉRIEUR DU sous-shell = $BASH_SUBSHELL" # Bash, version 3, ajoute la nouvelle variable $BASH_SUBSELL. echo; echo variable_externe=externe variable_globale= # Définit une variable globale pour le stockage #+ de la valeur d'une variable d'un sous-shell. ( echo "Nous sommes à l'intérieur du sous-shell." echo "Niveau de sous-shell À L'INTÉRIEUR DU sous-shell = $BASH_SUBSHELL" variable_interne=interne echo "À partir du sous-shell interne, \"variable_interne\" = $variable_interne" echo "À partir du sous-shell interne, \"externe\" = $variable_externe" variable_globale="$variable_interne" # Est-ce que ceci permet l'export #+ d'une variable d'un sous-shell ? ) echo; echo echo "Nous sommes à l'extérieur du sous-shell." echo echo "Niveau de sous-shell À L'EXTÉRIEUR DU sous-shell = $BASH_SUBSHELL" echo if [ -z "$variable_interne" ] then echo "variable_interne non défini dans le corps principal du shell" else echo "variable_interne défini dans le corps principal du shell" fi echo "À partir du code principal du shell, \"variable_interne\" = $variable_interne" # $variable_interne s'affichera comme non initialisée parce que les variables #+ définies dans un sous-shell sont des "variables locales". # Existe-t'il un remède pour ceci ? echo "variable_globale = "$variable_globale"" # Pourquoi ceci ne fonctionne pas ? echo # ======================================================================= # De plus... echo "-----------------"; echo var=41 # Variable globale. ( let "var+=1"; echo "\$var À L'INTÉRIEUR D'UN sous-shell = $var" ) # 42 echo "\$var EN DEHORS DU sous-hell = $var" # 41 # Les opérations sur des variabledans un sous-shell, même dans une variable #+ globale, n'affectent pas la valeur de la variable en dehors du sous-shell ! exit 0 # Question : # --------- # Une fois le sous-shell quitté, #+ existe-il un moyen d'entrer de nouveau dans le même sous-shell #+ pour modifier ou accéder aux variables du sous-shell ?
Voir aussi les $BASHPID et Exemple 34.2, « Problèmes des sous-shell ».
Alors que la variable interne the $BASH_SUBSHELL indique le niveau d'imbrication des sous-shells, la variable $SHLVL ne montre aucune modification dans un sous-shell.
echo " \$BASH_SUBSHELL en dehors du sous-shell = $BASH_SUBSHELL" # 0 ( echo " \$BASH_SUBSHELL dans le sous-shell = $BASH_SUBSHELL" ) # 1 ( ( echo " \$BASH_SUBSHELL à l'intérieur du sous-shell imbriqué = $BASH_SUBSHELL" ) ) # 2 # ^ ^ *** imbriquée *** ^ ^ echo echo " \$SHLVL en dehors du sous-shell = $SHLVL" # 3 ( echo " \$SHLVL à l'intérieur du sous-shell = $SHLVL" ) # 3 (aucun changement !)
Le changement de répertoire effectué dans un sous-shell n'a pas d'incidence sur le shell parent.
Exemple 21.2. Lister les profils utilisateurs
#!/bin/bash # allprofs.sh : affiche tous les profils utilisateur. # Ce script a été écrit par Heiner Steven et modifié par l'auteur du document. FICHIER=.bashrc # Fichier contenant le profil utilisateur, #+ était ".profile" dans le script original. for home in `awk -F: '{print $6}' /etc/passwd` do [ -d "$home" ] || continue # Si pas de répertoire personnel, passez au #+ suivant. [ -r "$home" ] || continue # Si non lisible, passez au suivant. (cd $home; [ -e $FICHIER ] && less $FICHIER) done # Quand le script se termine, il n'y a pas de besoin de retourner dans le #+ répertoire de départ parce que 'cd $home' prend place dans un sous-shell. exit 0
Un sous-shell peut être utilisé pour mettre en place un « environnement dédié » à un groupe de commandes.
COMMANDE1 COMMANDE2 COMMANDE3 ( IFS=: PATH=/bin unset TERMINFO set -C shift 5 COMMANDE4 COMMANDE5 exit 3 # Sortie du seul sous-shell ! ) # Le shell parent n'a pas été affecté et son environnement est préservé (ex : #+ pas de modification de $PATH). COMMANDE6 COMMANDE7
Comme vous le voyez, la commande exit termine seulement le sous-shell dans lequel il s'exécute, mais il ne termine pas le shell ou le script parent.
L'intérêt peut être par exemple de tester si une variable est définie ou pas.
if (set -u; : $variable) 2> /dev/null then echo "La variable est définie." fi # La variable a été initialisée dans le script en cours, #+ ou est une variable interne de Bash, #+ ou est présente dans l'environnement (a été exportée). # Peut également s'écrire [[ ${variable-x} != x || ${variable-y} != y ]] # ou [[ ${variable-x} != x$variable ]] # ou [[ ${variable+x} = x ]] # ou [[ ${variable-x} != x ]]
Une autre application est de vérifier si un fichier est marqué comme verrouillé :
if (set -C; : > fichier_verrou) 2> /dev/null then : # fichier_verrou n'existe pas : aucun utilisateur n'exécute ce script else echo "Un autre utilisateur exécute déjà ce script." exit 65 fi # Code de Stéphane Chazelas, #+ avec des modifications de Paulo Marcel Coelho Aragao.
+
Des processus peuvent être exécutés en parallèle dans différents sous-shells. Cela permet de séparer des tâches complexes en plusieurs sous-composants exécutés simultanément.
Exemple 21.3. Exécuter des processus en parallèle dans les sous-shells
(cat liste1 liste2 liste3 | sort | uniq > liste123) & (cat liste4 liste5 liste6 | sort | uniq > liste456) & # Concatène et trie les 2 groupes de listes simultanément. # Lancer en arrière-plan assure une exécution en parallèle. # # Peut également être écrit : # cat liste1 liste2 liste3 | sort | uniq > liste123 & # cat liste4 liste5 liste6 | sort | uniq > liste456 & wait # Ne pas exécuter la commande suivante tant que les sous-shells # n'ont pas terminé diff liste123 liste456
Redirection des entrées/sorties (I/O) dans un sous-shell en utilisant « | », l'opérateur tube (pipe en anglais), par exemple ls -al | (commande).
Un bloc de commandes entre accolades ne lance pas de sous-shell.
{ commande1; commande2; commande3; ... commandeN; }
var1=23 echo "$var1" # 23 { var1=76; } echo "$var1" # 76