9.2. Manipuler les chaînes de caractères

Bash supporte un nombre surprenant d'opérations de manipulation de chaînes de caractères. Malheureusement, ces outils manquent d'unité. Certains sont un sous-ensemble de la substitution de paramètre et les autres font partie des fonctionnalités de la commande UNIX expr. Ceci produite une syntaxe de commande non unifié et des fonctionnalités qui se recoupent, sans parler de la confusion engendrée.

Longueur de chaînes de caractères

${#chaine}

expr length $chaine

expr "$chaine" : '.*'

chaineZ=abcABC123ABCabc

echo ${#chaineZ}                 # 15
echo `expr length $chaineZ`      # 15
echo `expr "$chaineZ" : '.*'`    # 15

Exemple 9-10. Insérer une ligne blanche entre les paragraphes d'un fichier texte

#!/bin/bash
# paragraph-space.sh

# Insère une ligne blanche entre les paragraphes d'un fichier texte.
# Usage: $0 <NOMFICHIER

LONGUEUR_MINI=45        # Il peut être nécessaire de changer cette valeur.
#  Suppose que les lignes plus petites que $LONGUEUR_MINI caractères
#+ terminent un paragraphe.

while read ligne  # Pour toutes les lignes du fichier...
do
  echo "$ligne"   # Afficher la ligne.

  longueur=${#ligne}
  if [ "$longueur" -lt "$LONGUEUR_MINI" ]
    then echo    # Ajoute une ligne blanche après chaque petite ligne.
  fi  
done

exit 0

Longueur de sous-chaînes au début d'une chaîne

expr match "$chaine" '$souschaine'

$souschaine est une expression régulière.

expr "$chaine" : '$souschaine'

$souschaine est une expression régulière.

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

echo `expr match "$chaineZ" 'abc[A-Z]*.2'`   # 8
echo `expr "$chaineZ" : 'abc[A-Z]*.2'`       # 8

Index

expr index $chaine $souschaine

Position numérique dans $chaine du premier caractère dans $souschaine qui correspond.

chaineZ=abcABC123ABCabc
echo `expr index "$chaineZ" C12`             # 6
                                             # C position.

echo `expr index "$chaineZ" 1c`              # 3
# 'c' (à la position #3) correspond avant '1'.

Ceci est l'équivalent le plus proche strchr() en C.

Extraction d'une sous-chaîne

${chaine:position}

Extrait une sous-chaîne de $chaine à la position $position.

Si le paramètre $chaine est << * >> ou << @ >>, alors cela extrait les paramètres de position, [1] commençant à $position.

${chaine:position:longueur}

Extrait $longueur caractères d'une sous-chaîne de $chaine à la position $position.

chaineZ=abcABC123ABCabc
#       0123456789.....
#       indexage base 0.

echo ${chaineZ:0}                            # abcABC123ABCabc
echo ${chaineZ:1}                            # bcABC123ABCabc
echo ${chaineZ:7}                            # 23ABCabc

echo ${chaineZ:7:3}                          # 23A
                                             # Trois caractères de la sous-chaîne.

Si le paramètre $chaine est << * >> ou << @ >>, alors ceci extrait un maximum de $longueur du paramètre de position, en commençant à $position.

echo ${*:2}          # Affiche le deuxième paramètre de position et les suivants.
echo ${@:2}          # Identique à ci-dessus.

echo ${*:2:3}        # Affiche trois paramètres de position, en commençant par le deuxième.

expr souchaine $chaine $position $longueur

Extrait $longueur caractères à partir de $chaine en commençant à $position.

chaineZ=abcABC123ABCabc
#       123456789......
#       indexage base 1.

echo `expr substr $chaineZ 1 2`              # ab
echo `expr substr $chaineZ 4 3`              # ABC

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

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

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

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.