Chapitre 20. Sous-shells

L'exécution d'un script shell lance une nouvelle instance de l'interpréteur de commande. De la même manière que sont interprétées les commandes tapées en ligne de commande, un script bash exécute une liste de commandes lues dans un fichier. Chaque script shell exécuté est en réalité un sous-processus du shell parent, celui qui vous donne une invite à la console ou dans une fenêtre xterm

Un script shell peut également lancer 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.

Liste de commandes entre parenthèses

( commande1; commande2; commande3; ... )

Une liste de commandes placées entre parenthèses est exécutée sous forme de sous-shells

Note

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. Elles sont en réalité des variables locales.

Exemple 20-1. Etendue des variables dans un sous-shell

#!/bin/bash
# subshell.sh

echo

variable_externe=Outer

(
variable_interne=Inner
echo "A partir du sous-shell, \"variable_interne\" = $variable_interne"
echo "A partir du sous-shell, \"externe\" = $variable_externe"
)

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 "A partir du code principal du shell, \"variable_interne\" = $variable_interne"
# $variable_interne s'affichera comme non initialisé parce que les variables
# définies dans un sous-shell sont des "variables locales".

echo

exit 0

Voir aussi Exemple 32-1.

+

Le changement de répertoire effectué dans un sous-shell n'a pas d'incidence sur le shell parent.

Exemple 20-2. Lister les profiles 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 sous-shell.
)
#  Le shell parent n'a pas été affecté, et son environnement est préservé (ex :
#+ pas de modification de $PATH).
COMMANDE6
COMMANDE7
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

# Peut également s'écrire [[ ${variable-x} != x || ${variable-y} != y ]]
# ou                      [[ ${variable-x} != x$variable ]]
# ou                      [[ ${variable+x} = x ]])
Une autre application est de vérifier si un fichier est marquée comme verrouillé :
if (set -C; : > lock_file) 2> /dev/null
then
  echo "Un autre utilisateur exécute déjà ce script."
  exit 65
fi

# Merci, S.C.

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 20-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).

Note

Un bloc de commandes entre accolades ne lance pas un sous-shell.

{ commande1; commande2; commande3; ... }

>

Extrait $souschaine à partir du début de $chaine, et où $souschaine est une expression régulière.

chaineZ=abcABC123ABCabc
#       =======	    

echo `expr match "$chaineZ" '\(.[b-c]*[A-Z]..[0-9]\)'`   # abcABC1
echo `expr "$chaineZ" : '\(.[b-c]*[A-Z]..[0-9]\)'`       # abcABC1
echo `expr "$chaineZ" : '\(.......\)'`                   # abcABC1
# Toutes les formes ci-dessus donnent un résultat identique.

expr match "$chaine" '.*\($souschaine\)'

Extrait $souschaine à la fin de $chaine, et où $souschaine est une expression régulière.

expr "$chaine" : '.*\($souschaine\)'

Extrait $souschaine à la fin de $chaine, et où $souschaine est une expression régulière.

chaineZ=abcABC123ABCabc
#                ======

echo `expr match "$chaineZ" '.*\([A-C][A-C][A-C][a-c]*\)'`    # ABCabc
echo `expr "$chaineZ" : '.*\(......\)'`                       # ABCabc

Suppression de sous-chaînes

${chaine#souschaine}

Supprime la correspondance la plus petite de $souschaine à partir du début de $chaine.

${chaine##souschaine}

Supprime la correspondance la plus grande de $souschaine à partir du début de $chaine.

chaineZ=abcABC123ABCabc
#       |----|
#       |----------|

echo ${chaineZ#a*C}      # 123ABCabc
# Supprime la plus petite correspondance entre 'a' et 'C'.

echo ${chaineZ##a*C}     # abc
# Supprime la plus grande correspondance entre 'a' et 'C'.

${chaine%souschaine}

Supprime la plus petite correspondance de $souschaine à partir de la fin de $chaine.

${chaine%%souschaine}

Supprime la plus grande correspondance de $souschaine à partir de la fin de $chaine.

chaineZ=abcABC123ABCabc
#                    ||
#        |------------|

echo ${chaineZ%b*c}      # abcABC123ABCa
#  Supprime la plus petite correspondance entre 'b' et 'c', à partir de la fin
#+ de $chaineZ.

echo ${chaineZ%%b*c}     # a
#  Supprime la plus petite correspondance entre 'b' et 'c', à partir de la fin
#+ de $chaineZ.

Exemple 9-11. Convertir des formats de fichiers graphiques avec une modification du nom du fichier

#!/bin/bash
#  cvt.sh:
#  Convertit les fichiers image MacPaint contenues dans un répertoire dans un
#+ format "pbm".

#  Utilise le binaire "macptopbm" provenant du paquetage "netpbm",
#+ qui est maintenu par Brian Henderson (bryanh@giraffe-data.com).
#  Netpbm est un standard sur la plupart des distributions Linux.

OPERATION=macptopbm
SUFFIXE=pbm         # Suffixe pour les nouveaux noms de fichiers.

if [ -n "$1" ]
then
  repertoire=$1      # Si le nom du répertoire donné en argument au script...
else
  repertoire=$PWD    # Sinon, utilise le répertoire courant.
fi  
  
#  Assume que tous les fichiers du répertoire cible sont des fichiers image
# + MacPaint avec un suffixe ".mac".

for fichier in $repertoire/*  # Filename globbing.
do
  nomfichier=${fichier%.*c} #  Supprime le suffixe ".mac" du nom du fichier
                            #+ ('.*c' correspond à tout ce qui se trouve
			    #+ entre '.' et 'c', inclus).
  $OPERATION $fichier > $nomfichier.$SUFFIXE
    # Redirige la conversion vers le nouveau nom du fichier.
    rm -f $fichier          # Supprime le fichier original après sa convertion.
  echo "$nomfichier.$SUFFIXE"  # Trace ce qui se passe sur stdout.
done

exit 0

Remplacement de sous-chaîne

${chaine/souschaine/remplacement}

Remplace la première correspondance de $souschaine par $remplacement.

${chaine//souschaine/remplacement}

Remplace toutes les correspondances de $souschaine avec $remplacement.

chaineZ=abcABC123ABCabc

echo ${chaineZ/abc/xyz}           # xyzABC123ABCabc
                                  #  Remplace la première correspondance de
				  #+ 'abc' avec 'xyz'.

echo ${chaineZ//abc/xyz}          # xyzABC123ABCxyz
                                  #  Remplace toutes les correspondances de
				  #+ 'abc' avec 'xyz'.

${chaine/#souschaine/remplacement}

Si $souschaine correspond au début de $chaine, substitue $remplacement à $souschaine.

${chaine/%souchaine/remplacement}

Si $souschaine correspond à la fin de $chaine, substitue $remplacement à $souschaine.

chaineZ=abcABC123ABCabc

echo ${chaineZ/#abc/XYZ}          # XYZABC123ABCabc
                                  #  Remplace la correspondance de fin de
				  #+ 'abc' avec 'xyz'.

echo ${chaineZ/%abc/XYZ}          # abcABC123ABCXYZ
                                  #  Remplace la correspondance de fin de
				  #+ 'abc' avec 'xyz'.

9.2.1. Manipuler des chaînes de caractères avec awk

Un script Bash peut utiliser des fonctionnalités de manipulation de chaînes de caractères de awk comme alternative à ses propres fonctions intégrées.

Exemple 9-12. Autres moyens d'extraire des sous-chaînes

#!/bin/bash
# substring-extraction.sh

Chaine=23skidoo1
#      012345678    Bash
#      123456789    awk
# Notez les différents systèmes d'indexage de chaînes:
# Bash compte le premier caractère d'une chaîne avec '0'.
# Awk  compte le premier caractère d'une chaîne avec '1'.

echo ${Chaine:2:4} # position 3 (0-1-2), longueur de quatre caractères
                                         # skid

# L'équivalent awk de ${string:pos:length} est substr(string,pos,length).
echo | awk '
{ print substr("'"${Chaine}"'",3,4)      # skid
}
'
#  Envoyé un "echo" vide à awk donne une entrée inutile, et donc permet d'éviter
#+ d'apporter un nom de fichier.

exit 0

9.2.2. Discussion plus avancée

Pour plus d'informations sur la manipulation des chaînes de caractères dans les scripts, référez-vous à la Section 9.3 et à la section consacrée à la commande expr. Pour des exemples de scripts, jetez un oeil sur :

  1. Exemple 12-6

  2. Exemple 9-15

  3. Exemple 9-16

  4. Exemple 9-17

  5. Exemple 9-19

Notes

[1]

Ceci s'applique soit aux arguments en ligne de commande soit aux paramètres passés à une fonction.

>

!

Inverse le sens d'un test ou d'un état de sortie. L'opérateur ! inverse l'état de sortie de la commande à laquelle il est appliqué (voir Exemple 6-2). Il inverse aussi la conclusion d'un opérateur de test. Cela peut, par exemple, changer le sens d'un << égal >> ( = ) en un << différent >> ( != ). L'opérateur ! est un mot-clé Bash.

Dans un autre contexte, le ! apparaît aussi dans les références indirectes de variable.

Dans un contexte encore différent, à partir de la ligne de commande, le ! appelle le mécanisme d'historique de Bash (voir Annexe F). Notez que dans un script, ce mécanisme est désactivé.

*

Joker. [astérisque] Le caractère * sert de << joker >> pour l'expansion des noms de fichiers dans le remplacement. Utilisé seul, il correspond à tous les noms de fichiers d'un répertoire donné.

bash$ echo *
abs-book.sgml add-drive.sh agram.sh alias.sh
	      

L'astérisque * représente aussi tout un ensemble (parfois vide) de caractères dans une expression régulière.

*

Opérateur arithmétique. Dans le contexte des opérations arithmétiques, * est une multiplication.

La double astérisque ** est l'opérateur exponentiel.

?

Opérateur de test. A l'intérieur de certaines expressions, le ? indique un test pour une condition.

Dans une construction entre parenthèses doubles, ? sert d'opérateur à trois arguments dans le style du C. Voir Exemple 9-28.

Dans une expression de substitution de paramètres, le ? teste si une variable a été initialisée.

?

Joker. Le caractère ? sert de joker pour un seul caractère pour l'expansion d'un nom de fichier dans un remplacement, et également représente un caractère dans une expression régulière étendue.

$

Substitution de variable.
var1=5
var2=23skidoo

echo $var1     # 5
echo $var2     # 23skidoo

Un $ préfixant un nom de variable donne la valeur que contient cette variable.

$

Fin de ligne. Dans une expression régulière, un $ signifie la fin d'une ligne de texte.

${}

Substitution de paramètres.

$*, $@

Paramètres de position.

$?

Variable contenant l'état de sortie. La variable $? contient l'état de sortie d'une commande, d'une fonction ou d'un script.

$$

Variable contenant l'identifiant du processus. La variable $$ contient le numéro de processus du script dans lequel elle apparaît.

()

Groupe de commandes.
(a=bonjour; echo $a)

Important

Une liste de commandes entre parenthèses lance un sous-shell.

Les variables comprises dans ces parenthèses, à l'intérieur du sous-shell, ne sont pas visibles par le reste du script. Le processus parent, le script, ne peut pas lire les variables créées dans le processus fils, le sous-shell.
a=123
( a=321; )	      

echo "a = $a"   # a = 123
# "a" à l'intérieur des parenthèses agit comme une variable locale.

Initialisation de tableaux.
Array=(element1 element2 element3)

{xxx, yyy, zzz, ...}

Expansion d'accolades.
grep Linux fichier*.{txt,htm*}
# Trouve toutes les instances du mot "Linux"
# dans les fichiers "fichierA.txt", "fichier2.txt", "fichierR.html", "fichier-87.htm", etc.

Une commande peut agir sur un liste de fichiers séparés par des virgules à l'intérieur d'accolades [1]. L'expansion de noms de fichiers (remplacement) s'applique aux fichiers contenus dans les accolades.

Attention

Aucune espace n'est autorisée à l'intérieur des accolades sauf si les espaces sont comprises dans des guillemets ou échappées.

echo {fichier1,fichier2}\ :{\ A," B",' C'}

fichier1 : A fichier1 : B fichier1 : C fichier2 : A fichier2 : B fichier2 : C

{}

Bloc de code. [accolade] Aussi connu sous le nom de << groupe en ligne >>, cette construction crée une fonction anonyme. Néanmoins, contrairement à une fonction, les variables d'un bloc de code restent visibles par le reste du script.

bash$ { local a; a=123; }
bash: local: can only be used in a function
	      

a=123
{ a=321; }
echo "a = $a"   # a = 321   (valeur à l'intérieur du bloc de code)

# Merci, S.C.

Le bloc de code entouré par des accolades peut utiliser la redirection d'E/S (entrées/sorties).

Exemple 3-1. Blocs de code et redirection d'E/S

#!/bin/bash
# Lit les lignes dans /etc/fstab.

Fichier=/etc/fstab

{
read ligne1
read ligne2
} < $Fichier

echo "La première ligne dans $Fichier est :"
echo "$ligne1"
echo
echo "La deuxième ligne dans $Fichier est :"
echo "$ligne2"

exit 0

Exemple 3-2. Sauver le résultat d'un bloc de code dans un fichier

#!/bin/bash
# rpm-check.sh

#  Recherche une description à partir d'un fichier rpm, et s'il peut être
#+ installé.
#+ Sauvegarde la sortie dans un fichier.
# 
#+ Ce script illustre l'utilisation d'un bloc de code.

SUCCES=0
E_SANSARGS=65

if [ -z "$1" ]
then
	echo "Usage: `basename $0` fichier-rpm"
  exit $E_SANSARGS
fi  

{ 
  echo
  echo "Description de l'archive :"
  rpm -qpi $1       # Requête pour la description.
  echo
  echo "Liste de l'archive :"
  rpm -qpl $1       # Requête pour la liste.
  echo
  rpm -i --test $1  # Requête pour savoir si le fichier rpm est installable.
  if [ "$?" -eq $SUCCES ]
  then
    echo "$1 est installable."
  else
    echo "$1 n'est pas installable."
  fi  
  echo
} > "$1.test"       # Redirige la sortie de tout le bloc vers un fichier.

echo "Les résultats du test rpm sont dans le fichier $1.test"

# Voir la page de manuel de rpm pour des explications sur les options.

exit 0

Note

Contrairement à un groupe de commandes entre parenthèses, comme ci-dessus, un bloc de code entouré par des accolades ne sera pas lancé dans un sous-shell. [2]

{} \;

Chemin. Principalement utilisé dans les constructions find. Ce n'est pas une commande intégrée du shell.

Note

Le << ; >> termine l'option -exec d'une séquence de commandes find. Il a besoin d'être échappé pour que le shell ne l'interprète pas.

[ ]

Test.

Teste l'expression entre [ ]. Notez que [ fait partie de la commande intégrée test (et en est un synonyme), ce n'est pas un raccourci vers la commande externe /usr/bin/test.

[[ ]]

Test.

Teste l'expression entre [[ ]] (mot-clé du shell).

Voir les explications sur la structure [[ ... ]].

[ ]

Élément d'un tableau.

Accolés au nom d'un tableau, les crochets indiquent l'indice d'un élément.
Tableau[1]=slot_1
echo ${Tableau[1]}

[ ]

suite de caractères.

Dans une expression régulière, les crochets désignent une suite continue de caractères devant servir de motif.

(( ))

Expansion d'entiers.

Développe et évalue une expression entière entre (( )).

Voir les explications sur la structure (( ... )).

> &> >& >> <

Redirection.

nom_script >nom_fichier redirige la sortie de nom_script vers le fichier nom_fichier et écrase nom_fichier s'il existe déjà.

commande &>nom_fichier redirige à la fois stdout et stderr de commande vers nom_fichier.

commande >&2 redirige stdout de commande vers stderr.

nom_script >>nom_fichier ajoute la sortie de nom_script à la fin du fichier nom_fichier. Si le fichier n'existe pas déjà, il sera créé.

Substitution de processus.

(commande)>

<(commande)

Dans un autre contexte, les caractères < et > agissent comme des opérateurs de comparaison de chaînes de caractères.

Dans un contexte encore différent, les caractères < et > agissent comme des opérateurs de comparaison d'entiers. Voir aussi Exemple 12-6.

<<

Redirection utilisée dans un document en ligne.

<, >

Comparaison ASCII.
leg1=carottes
leg2=tomates

if [[ "$leg1" < "$leg2" ]]
then
  echo "Le fait que $leg1 précède $leg2 dans le dictionnaire"
  echo "n'a rien à voir avec mes préférences culinaires."
else
  echo "Mais quel type de dictionnaire utilisez-vous?"
fi

\<, \>

Délimitation d'un mot dans une expression régulière.

bash$ grep '\<mot\>' fichier_texte

|

Tube. Passe la sortie de la commande précédente à l'entrée de la suivante. Cette méthode permet de chaîner les commandes ensemble.

echo ls -l | sh
#  Passe la sortie de "echo ls -l" au shell
#+ avec le même résultat qu'un simple "ls -l".


cat *.lst | sort | uniq
#  Assemble et trie tous les fichiers ".lst", puis supprime les lignes
#+ dupliquées.

La sortie d'une commande ou de plusieurs commandes doit être envoyée à un script via un tube.
#!/bin/bash
# uppercase.sh : Change l'entrée en majuscules.

tr 'a-z' 'A-Z'
#  La plage de lettres doit être entre guillemets pour empêcher que la
#+ génération des noms de fichiers ne se fasse que sur les fichiers à un
#+ caractère.

exit 0
Maintenant, envoyons par le tube la sortie de ls -l à ce script.
bash$ ls -l | ./uppercase.sh
-RW-RW-R--    1 BOZO  BOZO       109 APR  7 19:49 1.TXT
 -RW-RW-R--    1 BOZO  BOZO       109 APR 14 16:48 2.TXT
 -RW-R--R--    1 BOZO  BOZO       725 APR 20 20:56 FICHIER-DONNEES
	      

Note

Le canal stdout de chaque processus dans un tube doit être lu comme canal stdin par le suivant. Si ce n'est pas le cas, le flux de données va se bloquer et le tube ne se comportera pas comme il devrait.
cat fichier1 fichier2 | ls -l | sort
# La sortie à partir de "cat fichier1 fichier2" disparaît.

Un tube tourne en tant que processus fils, et ne peut donc modifier les variables du script.
variable="valeur_initiale"
echo "nouvelle_valeur" | read variable
echo "variable = $variable"     # variable = valeur_initiale

Si une des commandes du tube échoue, l'exécution du tube se termine prématurément. Dans ces conditions, on a un tube cassé et on envoie un signal SIGPIPE.

>|

Force une redirection (même si l' option noclobber est mise en place). Ceci va forcer l'écrasement d'un fichier déjà existant.

||

Opérateur OU logique. Dans une structure de test , l'opérateur || a comme valeur de retour 0 (succès) si l'un des deux est vrai.

&

Faire tourner la tâche en arrière-plan. Une commande suivie par un & fonctionnera en tâche de fond.

bash$ sleep 10 &
[1] 850
[1]+  Done                    sleep 10
	      

A l'intérieur d'un script, les commandes et même les boucles peuvent tourner en tâche de fond.

Exemple 3-3. Lancer une boucle en tâche de fond

#!/bin/bash
# background-loop.sh

for i in 1 2 3 4 5 6 7 8 9 10            # Première boucle.
do
  echo -n "$i "
done & # Lance cette boucle en tâche de fond.
       # S'exécutera quelques fois après la deuxième boucle.

echo   # Ce 'echo' ne s'affichera pas toujours.

for i in 11 12 13 14 15 16 17 18 19 20   # Deuxième boucle.
do
  echo -n "$i "
done  

echo   # Ce 'echo' ne s'affichera pas toujours.

# ======================================================

# La sortie attendue de ce script:
# 1 2 3 4 5 6 7 8 9 10 
# 11 12 13 14 15 16 17 18 19 20 

# Mais, quelque fois, vous obtenez:
# 11 12 13 14 15 16 17 18 19 20 
# 1 2 3 4 5 6 7 8 9 10 bozo $
# (Le deuxième 'echo' ne s'exécute pas. Pourquoi?)

# Occasionnellement aussi:
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
# (Le premier 'echo' ne s'exécute pas. Pourquoi?)

# Et très rarement:
# 11 12 13 1 2 3 4 5 6 7 8 9 10 14 15 16 17 18 19 20 
# La boucle en avant plan s'exécute avant celle en tâche de fond.

exit 0

Attention

Une commande tournant en tâche de fond à l'intérieur d'un script peut faire s'arrêter le script, attendant un appui sur une touche. Heureusement, il est possible d'y remède.

&&

Opérateur ET logique. Dans une structure de test, l'opérateur && renvoie 0 (succès) si et seulement si les deux conditions sont vraies.

-

Option, préfixe. Introduit les options pour les commandes ou les filtres. Sert aussi de préfixe pour un opérateur.

COMMANDE -[Option1][Option2][...]

ls -al

sort -dfu $nom_fichier

set -- $variable

if [ $fichier1 -ot $fichier2 ]
then
  echo "Le fichier $fichier1 est plus ancien que le $fichier2."
fi

if [ "$a" -eq "$b" ]
then
  echo "$a est égal à $b."
fi

if [ "$c" -eq 24 -a "$d" -eq 47 ]
then
  echo "$c vaut 24 et $d vaut 47."
fi

-

Redirection à partir de ou vers stdin ou stdout. [tiret]

(cd /source/repertoire && tar cf - . ) | (cd /dest/repertoire && tar xpvf -)
# Déplace l'ensemble des fichiers d'un répertoire vers un autre
# [courtoisie d'Alan Cox <a.cox@swansea.ac.uk>, avec une modification mineure]

# 1) cd /source/repertoire   Répertoire source, où se trouvent les fichiers à
#                            déplacer.
# 2) &&                      "ET de liste": si l'opération 'cd' a fonctionné,
#							 alors il exécute la commande suivante.
# 3) tar cf - .              L'option 'c' de la commande d'archivage 'tar' crée
#                            une nouvelle archive,
#                            l'option 'f' (fichier), suivie par '-' désigne
#                            stdout comme fichier cible.
#                            et place l'archive dans le répertoire courant ('.').
# 4) |                       Tube...
# 5) ( ... )                 Un sous-shell.
# 6) cd /dest/repertoire     Se déplace dans le répertoire de destination.
# 7) &&                      "ET de liste", comme ci-dessus.
# 8) tar xpvf -              Déballe l'archive ('x'), préserve l'appartenance
#                            et les droits des fichiers ('p'),
#                            puis envoie de nombreux messages vers stdout ('v'),
#                            en lisant les données provenant de stdin
#                            ('f' suivi par un '-').
#
#                            Notez que 'x' est une commande, et 'p', 'v', 'f'
#                            sont des options.
# Ouf !



# Plus élégant, mais équivalent à:
#   cd source-repertoire
#   tar cf - . | (cd ../repertoire-source; tar xzf -)
#
# cp -a /source/repertoire /dest     a aussi le même effet.

bunzip2 linux-2.4.3.tar.bz2 | tar xvf -
# --décompresse l'archive--  | --puis la passe à "tar"--
# Si "tar" n'a pas intégré le correctif de support de "bunzip2",
# il faut procéder en deux étapes distinctes avec un tube.
# Le but de cet exercice est de désarchiver les sources du noyau compressées
# avec bzip2.

Notez que dans ce contexte le signe << - >> n'est pas en lui-même un opérateur Bash, mais plutôt une option reconnue par certains utilitaires UNIX qui écrivent dans stdout ou lisent dans stdin, tels que tar, cat, etc.

bash$ echo "quoiquecesoit" | cat -
quoiquecesoit 

Lorsqu'un nom de fichier serait attendu, un - redirige la sortie vers stdout (vous pouvez le rencontrer avec tar cf), ou accepte une entrée de stdin, plutôt que d'un fichier. C'est une méthode pour utiliser un outil principalement destiné à manipuler des fichiers comme filtre dans un tube.

bash$ file
Usage: file [-bciknvzL] [-f namefile] [-m magicfiles] file...
	      
Tout seul sur la ligne de commande, file échoue avec un message d'erreur.

Ajoutez un << - >> pour pouvoir vous en servir. Le shell attend alors une entrée de l'utilisateur.
bash$ file -
abc
standard input:              ASCII text



bash$ file -
#!/bin/bash
standard input:              Bourne-Again shell script text executable
	      
Maintenant la commande accepte une entrée de stdin et l'analyse.

Le << - >> peut être utilisé pour envoyer stdout à d'autres commandes via un tube, ce qui permet quelques astuces comme l'ajout de lignes au début d'un fichier.

Par exemple vous pouvez utiliser diff pour comparer un fichier avec une partie d'un autre fichier :

grep Linux fichier1 | diff fichier2 -

Finalement, un exemple réel utilisant - avec tar.

Exemple 3-4. Sauvegarde de tous les fichiers modifiés dans les dernières 24 heures

#!/bin/bash

#  Sauvegarde dans une archive tar compressée tous les fichiers
#+ du répertoire courant modifiés dans les dernières 24 heures.

FICHIERSAUVE=backup
archive=${1:-$FICHIERSAUVE}
#  Si aucun nom de fichier n'est spécifié sur la ligne de commande,
#+ nous utiliserons par défaut "backup.tar.gz."

tar cvf - `find . -mtime -1 -type f -print` > $archive.tar
gzip $archive.tar
echo "Répertoire $PWD sauvegardé dans un fichier archive \"$archive.tar.gz\"."


#  Stephane Chazelas indique que le code ci-dessus échouera si il existe trop
#+ de fichiers ou si un nom de fichier contient des espaces blancs.

# Il suggère les alternatives suivantes:
# -------------------------------------------------------------------
#   find . -mtime -1 -type f -print0 | xargs -0 tar rvf "$archive.tar"
#         avec la version GNU de "find".


#   find . -mtime -1 -type f -exec tar rvf "$archive.tar" '{}' \;
#         portable aux autres UNIX, mais plus lent.
# -------------------------------------------------------------------


exit 0

Attention

Les noms de fichiers commençant avec un << - >> peuvent poser problème lorsqu'ils sont couplés avec le << - >> opérateur de redirection. Votre script doit détecter de tels fichiers et leur ajouter un préfixe approprié, par exemple ./-NOMFICHIER, $PWD/-NOMFICHIER, ou $NOMCHEMIN/-NOMFICHIER.

Il y aura probablement un problème si la valeurx d'une variable commence avec un -.
var="-n"
echo $var		
# A le même effet qu'un "echo -n" et donc n'affiche rien.

-

Répertoire courant précédent. [tiret] cd - revient au répertoire précédent, en utilisant la variable d'environnement $OLDPWD.

Attention

Ne confondez pas << - >> utilisé dans ce sens avec l'opérateur de redirection << - >> vu précédemment. L'interprétation du << - >> dépend du contexte dans lequel il apparaît.

-

Moins. Le signe moins est une opération arithmétique.

=

Égal. Opérateur d'affectation.
a=28
echo $a   # 28

Dans un autre contexte, le signe = est un opérateur de comparaison de chaînes de caractères.

+

Plus. Opérateur arithmétique d'addition.

Dans un autre contexte, le + est un opérateur d'expression régulière.

+

Option. Option pour une commande ou un filtre.

Certaines commandes, intégrées ou non, utilisent le + pour activer certaines options et le - pour les désactiver.

%

Modulo. Opérateur arithmétique modulo (reste d'une division entière).

Dans un autre contexte, le % est un opérateur de reconnaissance de modèles.

~

Répertoire de l'utilisateur. [tilde] Le ~ correspond à la variable interne $HOME. ~bozo est le répertoire de l'utilisateur bozo, et ls ~bozo liste son contenu. ~/ est le répertoire de l'utilisateur courant et ls ~/ liste son contenu.
bash$ echo ~bozo
/home/bozo

bash$ echo ~
/home/bozo

bash$ echo ~/
/home/bozo/

bash$ echo ~:
/home/bozo:

bash$ echo ~utilisateur-inexistant
~utilisateur-inexistant
	      

~+

Répertoire courant. Correspond à la variable interne $PWD.

~-

Répertoire courant précédent. Correspond à la variable interne $OLDPWD.

^

Début de ligne. Dans une expression régulière, un << ^ >> correspond au début d'une ligne de texte.

Caractères de contrôle

Modifient le comportement d'un terminal ou de l'affichage d'un texte. Un caractère de contrôle est une combinaison CONTROL + touche.

Espace blanc

Fonctionne comme un séparateur, séparant les commandes ou les variables. Les espaces blancs sont constitués d'espaces, de tabulations, de lignes blanches ou d'une combinaison de ceux-ci. Dans certains contextes, tels que les affectations de variable, les espaces blancs ne sont pas permis, et sont considérés comme une erreur de syntaxe.

Les lignes blanches n'ont aucun effet sur l'action d'un script, et sont plutôt utiles pour séparer visuellement les différentes parties.

La variable $IFS est une variable spéciale définissant pour certaines commandes le séparateur des champs en entrée. Elle a pour valeur par défaut un espace blanc.

Notes

[1]

Le shell fait l'expansion des accolades. La commande elle-même agit sur le résultat de cette expansion.

[2]

Exception : un bloc de code entre accolades dans un tube peut être lancé comme sous-shell.
ls | { read ligne1; read ligne2; }
# Erreur. Le bloc de code entre accolades tourne comme un sous-shell,
# donc la sortie de "ls" ne peut être passée aux variables de ce bloc.
echo "La première ligne est $ligne1; la seconde ligne est $ligne2"  # Ne fonctionnera pas.

# Merci, S.C.