================= Bash Basics HOWTO (c) Nicolas Kovacs ================= Dernière révision : 20 décembre 2015 Ce HOWTO présente et explique de manière détaillée les fonctionnalités de base du shell Bash. * Introduction * Commandes externes et internes * Affichage à l'écran * La commande interne 'cd' * Substitution de noms de fichiers * Expressions complexes * Séparateur de commandes * Redirections * Tubes de communication * Regroupement de commandes * Processus en arrière-plan * Exercices * Corrigés ============ Introduction ============ Définition du shell ------------------- Le shell est un programme ayant pour fonction d'assurer l'interface entre l'utilisateur et le système Linux. C'est un interpréteur de commandes. Plusieurs shells sont disponibles sur les plates-formes Linux. Caractéristiques du shell ------------------------- Les interpréteurs de commandes disponibles en environnement Unix et Linux ont en commun les fonctionnalités suivantes : - Ils proposent un jeu de caractères spéciaux permettant de déclencher des actions particulières. - Ils possèdent des commandes internes et des mots clés parmi lesquels certains sont utilisés pour faire de la programmation. - Ils utilisent des fichiers d'initialisation permettant à un utilisateur de paramétrer son environnement de travail. Chaque shell propose ses propres caractères spéciaux, commandes internes, mots clés et fichiers de paramétrage. Heureusement, les interpréteurs les plus utilisés actuellement dérivent tous du shell Bourne et ont, par conséquent, un certain nombre de fonctionnalités en commun. Historique du shell ------------------- Le shell qui est considéré comme le plus ancien est le Bourne Shell (sh). Il a été écrit dans les années 1970 par Steve Bourne aux laboratoires AT & T. Outre sa capacité à lancer des commandes, il offre des fonctionnalités de programmation. Le Bourne Shell est un shell ancien qui est de moins en moins utilisé sur les plates-formes Unix. Durant la même période, Bill Joy invente le C-Shell (csh), incompatible avec le Bourne Shell, mais qui offre des fonctionnalités supplémentaires telles que l'historique des commandes, le contrôle des tâches ou la possibilité de créer des alias de commandes. Ces trois aspects seront repris plus tard dans le Korn Shell. Le C-Shell est peu utilisé dans le monde Unix. En 1983, David Korn reprend le Bourne Shell et l'enrichit. Ce nouvel interpréteur prendra le nom de Korn Shell (ksh). Il sera de plus en plus employé et deviendra un standard de fait. Le ksh88 (version datant de 1988) est, avec le Bourne Again Shell, le shell le plus utilisé actuellement. Il a servi de base à la normalisation POSIX du shell. En 1993, une nouvelle version du Korn Shell voit le jour (ksh93). Celle-ci présente une compatibilité arrière avec le ksh88, à quelques exceptions près. Le ksh93 est disponible sur certaines versions Unix récentes : Solaris et AIX. La Free Software Foundation propose le Bourne Again Shell (bash). Il est conforme à la norme POSIX à laquelle il a ajouté quelques extensions. Ce shell est l'interpréteur fourni en standard sur les systèmes Linux. Il est également disponible en standard ou en téléchargement sur les systèmes Unix. Scripts de démarrage -------------------- Sur certaines plates-formes, les scripts de démarrage sont interprétés par un Bourne Shell. Si l'on souhaite modifier ces scripts ou créer de nouveaux scripts de démarrage, il faut dans ce cas se restreindre à la syntaxe Bourne Shell. De manière plus générale, il faut utiliser la syntaxe qui correspond au shell qui interprète les scripts de démarrage. Autres scripts -------------- Dans les cas les plus fréquents - scripts de traitement exécutés en mode de fonctionnement Unix normal - le développeur choisira soit le shell Bash, soit le Korn Shell 88 ou 93, selon le shell disponible sur sa plate-forme. En ce qui nous concerne, nous nous concentrerons sur le shell Bash. ============================== Commandes externes et internes ============================== Une commande Linux appartient à l'une des deux catégories suivantes : - commandes externes - commandes internes Les commandes externes ---------------------- Une commande externe est un fichier localisé dans l'arborescence. Par exemple, lorsqu'un utilisateur lance la commande 'ls', le shell demande au noyau Linux de charger en mémoire le fichier '/bin/ls'. Sont considérées comme commandes externes les fichiers possédant l'un des formats suivants : - fichiers au format binaire exécutable - fichiers au format texte représentant un script de commandes La commande 'file' donne une indication sur le type de données contenues dans un fichier. L'argument de la commande 'file' est un nom de fichier exprimé en relatif ou en absolu. Voici deux exemples. La commande 'ls' est un fichier au format binaire exécutable : $ file /bin/ls /bin/ls: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), stripped La commande 'gettext.sh' est un script shell : $ file /usr/bin/gettext.sh /usr/bin/gettext.sh: POSIX shell script, ASCII text executable Les commandes internes ---------------------- Une commande interne est intégrée au processus shell. Elle ne correspond donc en aucun cas à un fichier sur le disque. La commande 'type' indique si une commande possède une implémentation interne. Elle prend en argument le nom d'une commande. Si cette dernière n'est pas interne, elle est recherchée dans les répertoires cités dans PATH. Voici deux exemples. La commande 'cd' est une commande interne : $ type cd cd est une primitive du shell La commande 'ls' est une commande externe : $ type -a ls ls est un alias vers « ls --color=auto » ls est /usr/bin/ls ls est /bin/ls /!\ Ici on invoque l'option '-a' pour afficher les infos sur l'ensemble des exécutables nommés 'ls'. Pour plus d'infos, voir 'man bash'. Implémentation interne et externe --------------------------------- Certaines commandes ont une implémentation interne et une implémentation externe. Dans ce cas, la commande interne est lancée en priorité. L'exécution d'une commande interne est plus rapide que l'exécution d'une commande externe. La commande 'pwd' est une commande interne au shell : $ type pwd pwd est une primitive du shell Elle possède également une implémentation externe : $ type -a pwd pwd est une primitive du shell pwd est /usr/bin/pwd pwd est /bin/pwd $ ls -l /usr/bin/pwd lrwxrwxrwx 1 root root 13 avril 26 08:07 /usr/bin/pwd -> ../../bin/pwd C'est la commande interne qui est exécutée en priorité : $ pwd /home/kikinovak Pour forcer l'exécution de la commande externe, il faut indiquer explicitement l'emplacement de la commande, en absolu ou en relatif : $ /usr/bin/pwd /home/kikinovak $ cd /usr/bin/ $ ./pwd /usr/bin =================== Affichage à l'écran =================== La commande 'echo' ------------------ La commande interne 'echo' permet de réaliser des affichages à l'écran. $ echo Yatahongaga ! Yatahongaga ! Certains caractères ont une signification spéciale lorsqu'ils sont placés entre simples ou doubles quotes (apostrophes ou guillemets). Ce sont des caractères d'échappement. /!\ La commande 'echo' du shell Bash doit être utilisée avec l'option '-e' pour que l'interprétation des caractères d'échappement ait lieu. Pour plus de détails, voir 'man echo'. Le caractère '\n' ----------------- Le caractère '\n' sert à provoquer un saut de ligne : $ echo -e "Première ligne\nDeuxième ligne\nTroisième ligne" Première ligne Deuxième ligne Troisième ligne On peut également utiliser des apostrophes au lieu des guillemets : $ echo -e 'Première ligne\nDeuxième ligne\nTroisième ligne' Première ligne Deuxième ligne Troisième ligne Le caractère '\c' ----------------- Le caractère '\c' sert à éliminer le saut de ligne naturel de la commande 'echo'. Il doit se situer impérativement en dernière position de l'argument de 'echo', juste avant le guillemet fermant. L'option '-e' est indispensable pour l'interprétation du caractère d'échappement '\c'. $ echo "Première ligne" ; echo "Deuxième ligne" Première ligne Deuxième ligne $ echo -e "Première ligne\c" ; echo -e "Deuxième ligne\c" Première ligneDeuxième ligne $ Alternativement, l'option '-n' peut remplacer le caractère '\c' : $ echo -n "Première ligne" ; echo -n "Deuxième ligne" Première ligneDeuxième ligne $ Le caractère '\t' ----------------- Le caractère '\t' permet d'afficher une tabulation : $ echo -e "|\tTab 1\tTab 2\tTab 3\tTab 4\t|" | Tab 1 Tab 2 Tab 3 Tab 4 | ======================== La commande interne 'cd' ======================== La commande 'cd' sans argument permet à l'utilisateur de revenir dans son répertoire d'accueil : $ cd $ pwd /home/kikinovak Même chose en utilisant le caractère '~', qui représente le répertoire d'accueil de l'utilisateur courant : $ cd ~ $ pwd /home/kikinovak Se déplacer dans le répertoire d'accueil de l'utilisateur 'ftp' : $ cd ~ftp $ pwd /home/ftp Revenir dans le répertoire précédent : $ cd - ================================ Substitution de noms de fichiers ================================ De nombreuses commandes prennent des noms de fichiers en argument. Ces derniers peuvent être cités littéralement ou être spécifiés de manière plus générique. Le shell propose un certain nombre de caractères spéciaux qui permettent de fabriquer des expressions utilisées comme modèles de noms de fichiers. Le caractère '*' ---------------- Le caractère '*' représente une suite de caractères quelconques. Voici quelques exemples. $ ls f12 f1.i FICa fic.c fic.s monscript.pl MONSCRIPT.pl ours.c /!\ Pour créer tous ces fichiers, on pourra utiliser la commande 'touch'. Afficher tous les noms de fichier se terminant par '.c' : $ ls *.c fic.c ours.c Afficher tous les noms de fichier commençant par la lettre 'f' : $ ls f* f12 f1.i fic.c fic.s Le caractère '?' ---------------- Le caractère "?" représente un caractère quelconque. Voici quelques exemples. Afficher tous les noms de fichier ayant une extension composée d'un seul caractère : $ ls *.? f1.i fic.c fic.s ours.c Afficher tous les noms de fichier composés de quatre caractères : $ ls ???? f1.i FICa Les crochets '[ ]' -------------------- Les crochets '[ ]' permettent de spécifier la liste des caractères que l'on attend à une position bien précise dans le nom du fichier. Il est également possible d'utiliser les notions d'intervalle et de négation. /!\ Les exemples cités ci-dessous peuvent produire des résultats surprenants en fonction de certaines variables d'environnement. Dans le doute, on fera ceci : $ LANG=C $ LC_COLLATE=C Pour chacun des exemples, une alternative est donnée qui fonctionne parfaitement pour des variables d'environnement LANG et LC_COLLATE différentes, par exemple 'fr_FR.utf8'. Fichiers dont le nom commence par 'f' ou 'o' et se termine par le caractère '.' suivi d'une minuscule : $ ls [fo]*.[a-z] f1.i fic.c fic.s ours.c Alternativement : $ ls [fo]*.[[:lower:]] f1.i fic.c fic.s ours.c Fichiers dont le nom comporte en deuxième caractère une majuscule ou un chiffre ou la lettre 'i'. Les deux premiers caractères seront suivis d'une chaîne quelconque : $ ls ?[A-Z0-9i]* FICa MONSCRIPT.pl f1.i f12 fic.c fic.s Alternativement : $ ls ?[[:upper:]0-9i]* f12 f1.i FICa fic.c fic.s MONSCRIPT.pl Il est également possible d'exprimer la négation de tous les caractères spécifiés à l'intérieur d'une paire de crochets. Ceci se fait en plaçant un '!' en première position à l'intérieur de celle-ci. Noms de fichier ne commençant pas par une minuscule : $ ls [!a-z]* FICa MONSCRIPT.pl Alternativement : $ ls [![:lower:]]* FICa MONSCRIPT.pl Noms de fichier ne commençant pas par une majuscule : $ ls [!A-Z]* f1.i f12 fic.c fic.s monscript.pl ours.c Alternativement : $ ls [![:upper:]]* f12 f1.i fic.c fic.s monscript.pl ours.c Supprimer tous les fichiers dont le nom se termine par '.c' ou par '.s' : $ rm -i *.c *.s rm : supprimer fichier « fic.c » ? o rm : supprimer fichier « ours.c » ? o rm : supprimer fichier « fic.s » ? o ===================== Expressions complexes ===================== Pour utiliser les expressions complexes en Bash, il faut préalablement activer l'option 'extglob' avec la commande suivante : $ shopt -s extglob Voici le contenu du répertoire qui sera utilisé dans les exemples suivants : $ ls fic fic866866.log fic866.log fic868.log readme.txt fic866866866.log fic866868.log fic867.log fic.log typescript ?(expression) ------------- L'expression '?(expression)' sera présente 0 ou 1 fois. Fichiers dont le nom commence par 'fic' suivi de 0 ou 1 occurrence de '866', suivi de '.log' : $ ls fic?(866).log fic866.log fic.log *(expression) ------------- L'expression '*(expression)' sera présente entre 0 et n fois. Fichiers dont le nom commence par 'fic', suivi de 0 à n occurrence(s) de '866', suivi de '.log' : $ ls fic*(866).log fic866866866.log fic866866.log fic866.log fic.log +(expression) ------------- L'expression '+(expression)' sera présente entre 1 et n fois. Fichiers dont le nom commence par 'fic', suivi d'au moins une occurrence de '866', suivi de '.log' : $ ls fic+(866).log fic866866866.log fic866866.log fic866.log @(expression) ------------- L'expression '@(expression)' sera présente exactement 1 fois. Fichiers dont le nom commence par 'fic', suivi exactement d'une occurrence de '866', suivi de '.log' : $ ls fic@(866).log fic866.log !(expression) ------------- L'expression '!(expression)' ne sera pas présente. Fichiers dont le nom commence par 'fic', suivi d'une expression qui n'est pas la chaîne '866', suivi de '.log' : $ ls fic!(866).log fic866866866.log fic866866.log fic866868.log fic867.log fic868.log fic.log Fichiers dont le nom ne commence pas par 'fic' : $ ls !(fic*) readme.txt typescript Alternatives ------------ Une barre verticale '|' à l'intérieur d'une expression complexe prend le sens de "ou bien". Fichiers dont le nom commence par 'fic', suivi de '866' ou '867', suivi de '.log' : $ ls fic@(866|867).log fic866.log fic867.log Fichiers dont le nom commence par 'fic', suivi de 1 à n occurrence(s) de '866' ou '868', suivi de '.log' : $ ls fic+(866|868).log fic866866866.log fic866866.log fic866868.log fic866.log fic868.log ======================= Séparateur de commandes ======================= Le caractère spécial ";" du shell permet d'écrire plusieurs commandes sur une même ligne. Les commandes sont exécutées séquentiellement. $ mkdir -v test ; cd test ; pwd mkdir: création du répertoire « test » /home/kikinovak/test ============ Redirections ============ Les redirections sont couramment utilisées dans les commandes Unix. Elles permettent : - de récupérer le résultat d'une ou plusieurs commandes dans un fichier ; - de faire lire un fichier à une commande. Entrée et sorties standard des processus ---------------------------------------- Les processus Unix ont, par défaut, leur fichier terminal ouvert trois fois, sous trois descripteurs de fichiers différents : 0, 1 et 2. Entrée standard --------------- Le descripteur de fichier 0 est également nommé "entrée standard du processus". Les processus qui attendent des informations de la part de l'utilisateur déclenchent une requête de lecture sur le descripteur 0. Si ce dernier est associé au terminal, cela se matérialise pour l'utilisateur par une demande de saisie au clavier. Sortie standard --------------- Le descripteur de fichier 1 est également nommé "sortie standard du processus". Par convention, un processus qui souhaite envoyer un message résultat à l'utilisateur doit le faire transiter via le descripteur 1. Si ce dernier est associé au terminal, ce qui est le cas par défaut, cela se matérialise pour l'utilisateur par un affichage à l'écran. Sortie d'erreur standard ------------------------ Le descripteur de fichier 2 est également nommé "sortie d'erreur standard du processus". Par convention, un processus qui souhaite envoyer un message d'erreur à l'utilisateur doit le faire transiter via le descripteur 2. Si ce dernier est associé au terminal, ce qui est le cas par défaut, cela se matérialise pour l'utilisateur par un affichage à l'écran. Redirection des sorties en écriture ----------------------------------- La redirection des sorties en écriture permet d'envoyer les affichages liés à un descripteur particulier non plus sur le terminal, mais dans un fichier. Syntaxe pour la simple redirection : $ commande 1> fichier Alternativement : $ commande > fichier Exemple : $ touch fichier{1,2,3} $ ls fichier1 fichier2 fichier3 $ ls > liste $ cat liste fichier1 fichier2 fichier3 liste Si le fichier n'existe pas, il est créé. S'il existe déjà, il est écrasé. La double redirection permet de concaténer les messages résultant d'une commande au contenu d'un fichier déjà existant : $ commande 1>> fichier Ou bien : $ commande >> fichier Si le fichier n'existe pas, il est créé. S'il existe déjà, il est ouvert en mode ajout. Dans l'exemple suivant, on va ajouter la date à la fin du fichier 'liste' créé précédemment : $ date mar. oct. 13 08:51:28 CEST 2015 $ date >> liste $ cat liste fichier1 fichier2 fichier3 liste mar. oct. 13 08:51:32 CEST 2015 La redirection de la sortie d'erreur standard permet de récupérer les messages d'erreur dans un fichier. Les résultats restent à l'écran. $ commande 2> fichier Exemple : $ find / -name passwd 2> erreur /usr/bin/passwd /boot/initrd-tree/bin/passwd /boot/initrd-tree/etc/passwd /etc/passwd $ cat erreur find: "/var/db/sudo": Permission non accordée find: "/var/cache/ldconfig": Permission non accordée find: "/var/spool/cron": Permission non accordée ... Là encore, la double redirection de la sortie d'erreur standard permet de concaténer les messages d'erreur d'une commande au contenu d'un fichier existant. $ commande 2>> fichier Dans l'exemple suivant, on va concaténer les messages d'erreur de la commande invalide 'ls -z' à la fin du fichier 'erreur' : $ ls -z ls : option invalide -- 'z' Saisissez « ls --help » pour plus d'informations. $ ls -z 2>> erreur $ cat erreur find: "/var/db/sudo": Permission non accordée find: "/var/cache/ldconfig": Permission non accordée ... find: "/etc/openvpn/keys": Permission non accordée find: "/etc/openvpn/certs": Permission non accordée ls : option invalide -- 'z' Saisissez « ls --help » pour plus d'informations. Il est possible de rediriger plusieurs descripteurs sur une même ligne de commande. $ commande 1> fichier_a 2> fichier_b Ou encore : $ commande 2> fichier_b 1> fichier_a Exemple : $ find / -name passwd 1> resultat 2> erreur $ cat resultat /usr/bin/passwd /boot/initrd-tree/bin/passwd /boot/initrd-tree/etc/passwd /etc/passwd $ head -n 3 erreur find: "/var/db/sudo": Permission non accordée find: "/var/cache/ldconfig": Permission non accordée find: "/var/spool/cron": Permission non accordée L'option 'noclobber' du shell permet de se protéger d'un écrasement involontaire de fichier. Elle est désactivée par défaut. $ set -o noclobber $ date > resultat bash: resultat : impossible d'écraser le fichier existant Pour forcer l'écrasement il faudra utiliser le symbole de redirection ">|" : $ date >| resultat $ cat resultat mar. oct. 13 09:16:30 CEST 2015 Et voici comment on réactive l'écrasement des fichiers : $ set +o noclobber Toutes les plates-formes Unix possèdent un fichier spécial nommé '/dev/null' qui permet de faire disparaître les affichages. Ce fichier est géré comme un périphérique et n'a pas de notion de contenu. On peut donc considérer qu'il est toujours vide. $ find / -name passwd 1> resultat 2> /dev/null Redirection de l'entrée standard -------------------------------- La redirection de l'entrée standard concerne les commandes qui utilisent le descripteur 0, autrement dit celles qui déclenchent une saisie au clavier. $ commande 0< fichier_message Ou bien : $ commande < fichier_message Dans l'exemple qui suit, on va envoyer un mail à l'utilisateur 'glagaffe'. $ mail glagaffe Subject: RDV Rendez-vous au resto à 13h. Nico . EOT /!\ Pour mettre l'exemple ci-dessus en pratique, il faut que l'utilisateur 'glagaffe' existe et que le serveur mail soit configuré pour la machine locale. Sur un système Slackware, on pourra faire ceci : # adduser glagaffe # chmod +x /etc/rc.d/rc.sendmail # /etc/rc.d/rc.sendmail start Et pour lire le mail : $ su - glagaffe $ mutt La commande 'mail' lit l'entrée standard jusqu'à la saisie d'un point "." sur une ligne. Les données saisies seront envoyées dans la boîte aux lettres de l'utilisateur 'glagaffe'. Si l'on souhaite faire lire à la commande 'mail' non plus le clavier mais le contenu d'un fichier, il suffit de connecter le descripteur 0 sur le fichier désiré : $ cat message RDV au resto à 13h. Nico $ mail -s "RDV" glagaffe < message Redirections avancées --------------------- Pour envoyer la sortie standard et la sortie d'erreur standard dans le même fichier, il faut employer une syntaxe particulière : $ commande 1> fichier 2>&1 Ou bien : $ commande 2> fichier 1>&2 Reprenons l'exemple de tout à l'heure : $ find / -name passwd > resultat 2>&1 $ cat resultat find: "/home/glagaffe": Permission non accordée /usr/bin/passwd find: "/var/db/sudo": Permission non accordée find: "/var/cache/ldconfig": Permission non accordée find: "/var/spool/cron": Permission non accordée ... La double redirection en lecture est principalement utilisée dans les scripts shell. Elle permet de connecter l'entrée standard d'une commande sur une portion du script. $ commande < Rendez-vous à 13h au resto. > Nico. > FIN /!\ Les étiquettes doivent être immédiatement suivies d'un retour à la ligne. ====================== Tubes de communication ====================== Un tube ("pipe" en anglais) permet de faire communiquer deux processus. Le tube est représenté par une barre verticale '|' située entre deux commandes Unix. Le résultat de la commande de gauche va partir dans le tube, tandis que la commande de droite va en extraire les données afin de les traiter. Dans l'exemple qui suit, on va envoyer par mail la liste des utilisateurs connectés à la machine : $ who | mail -s "Utilisateurs connectés" glagaffe La sortie d'erreur standard de la commande de gauche ne part pas dans le tube. Pour que l'utilisation d'un tube ait un sens, il faut que la commande placée à gauche du tube envoie des données sur sa sortie standard et que la commande placée à droite lise son entrée standard. Commandes ne lisant pas leur entrée standard -------------------------------------------- Un certain nombre de commandes Unix n'ont aucun intérêt à être placées derrière un tube, car elles n'exploitent pas leur entrée standard : ls, who, find, chmod, cp, mv, rm, ln, mkdir, rmdir, date, kill, type, echo, etc. Commandes lisant leur entrée standard ------------------------------------- Les commandes qui lisent leur entrée standard sont facilement identifiables, étant donné qu'elles demandent une saisie au clavier : $ mail glagaffe Subject: ... ... Sous Unix, un certain nombre de commandes sont regroupées sous le nom de filtres : grep, cat, sort, cut, wc, lp, sed, awk, etc. Ces commandes peuvent fonctionner de deux manières. Si la commande reçoit au moins un nom de fichier en argument, elle traite le(s) fichier(s) et ne déclenche pas de lecture de l'entrée standard : $ wc -l /etc/passwd 26 /etc/passwd La commande ne reçoit aucun nom de fichier en argument. Dans ce cas, la commande traite les données qui arrivent sur son entrée standard. Dans l'exemple qui suit la commande 'wc' ("word count") compte le nombre de lignes qui arrivent sur son entrée standard et affiche le résultat sur la sortie standard. Pour terminer la saisie, appuyer sur [Ctrl]+[D] : $ wc -l Première ligne Deuxième ligne Troisième ligne 3 Il est donc possible de placer cette commande derrière un tube : $ who | wc -l 4 Comment savoir si une commande lit son entrée standard ? Pour répondre à cette question, considérons deux exemples. Voici une commande qui traite un fichier. Elle ne déclenche pas de lecture de l'entrée standard : $ cut -d':' -f1,3 /etc/passwd root:0 bin:1 daemon:2 ... La même commande sans le nom de fichier attend une saisie au clavier : $ cut -d':' -f1,3 1:2:3:4 1:3 10:20:30:40 10:30 100:200:300:400 100:300 Cette commande peut donc être placée derrière un tube : $ echo "1:2:3:4" | cut -d':' -f1,3 1:3 La majorité des commandes ne se soucient pas de savoir si elles sont placées derrière une tube ou non. Pour une commande donnée, l'action sera toujours la même. Dans l'exemple qui suit, 'wc -l' lit son entrée standard dans les deux cas : $ wc -l $ who | wc -l Quelques commandes font exception à la règle. Elles testent si leur entrée standard est connectée sur la sortie d'un tube ou sur un terminal. Dans l'exemple qui suit, la commande 'more' reçoit un nom de fichier en argument et pagine son contenu à l'écran. Elle ne lit pas son entrée standard : $ more /etc/passwd root:x:0:0::/root:/bin/bash bin:x:1:1:bin:/bin:/bin/false daemon:x:2:2:daemon:/sbin:/bin/false ... Sans le nom de fichier, la commande affiche un message d'erreur : $ more Utilisation : more [options] fichier ... Le nom du fichier peut être omis lorsque 'more' est placée à droite d'un tube. Dans ce cas, la commande lit son entrée standard et pagine les lignes qu'elle y extrait : $ cat /etc/passwd | more root:x:0:0::/root:/bin/bash bin:x:1:1:bin:/bin:/bin/false daemon:x:2:2:daemon:/sbin:/bin/false ... Enchaîner des tubes ------------------- Il est possible d'enchaîner plusieurs tubes sur une ligne de commande. Dans l'exemple qui suit, on affiche le nombre de connexions d'un utilisateur : $ who | grep kikinovak | wc -l 4 Dupliquer les sorties --------------------- On peut également dupliquer les sorties grâce à la commande 'tee'. Elle permet de visualiser un résultat à l'écran tout en la conservant dans un fichier : $ ls | tee liste.txt fichier.log readme.txt typescript $ cat liste.txt fichier.log readme.txt typescript Le résultat de la commande 'date' est affiché à l'écran et concaténé au fichier 'liste.txt' existant : $ date | tee -a liste.txt mer. oct. 14 10:43:16 CEST 2015 $ cat liste.txt fichier.log readme.txt typescript mer. oct. 14 10:43:16 CEST 2015 Envoyer la sortie standard et la sortie d'erreur standard dans le tube ---------------------------------------------------------------------- La commande suivante affiche un message d'erreur et une ligne de résultat : $ ls -l t* Z* ls: impossible d'accéder à Z*: Aucun fichier ou dossier de ce type -rw-r--r-- 1 kikinovak users 0 oct. 12 09:28 typescript Seule la sortie standard passe dans le tube : $ ls -l t* Z* | tee liste.txt ls: impossible d'accéder à Z*: Aucun fichier ou dossier de ce type -rw-r--r-- 1 kikinovak users 0 oct. 12 09:28 typescript $ cat liste.txt -rw-r--r-- 1 kikinovak users 0 oct. 12 09:28 typescript Voici ce qu'il faut faire pour envoyer la sortie standard et la sortie d'erreur standard dans le tube : $ ls -l t* Z* 2>&1 | tee liste.txt ls: impossible d'accéder à Z*: Aucun fichier ou dossier de ce type -rw-r--r-- 1 kikinovak users 0 oct. 12 09:28 typescript $ cat liste.txt ls: impossible d'accéder à Z*: Aucun fichier ou dossier de ce type -rw-r--r-- 1 kikinovak users 0 oct. 12 09:28 typescript ========================= Regroupement de commandes ========================= Le regroupement de commandes peut être utilisé pour : - rediriger la sortie d'écran de plusieurs commandes vers un même fichier ou vers un tube ; - faire exécuter plusieurs commandes dans le même environnement. Dans l'exemple qui suit, seule la sortie standard de la deuxième commande est redirigée dans le fichier 'resultat.txt' : $ date ; ls > resultat.txt ven. oct. 16 11:35:10 CEST 2015 $ cat resultat.txt fichier.log readme.txt resultat.txt typescript Les parenthèses ( ) et les accolades { } permettent de regrouper les commandes. Les parenthèses ( ) ------------------- Avec les parenthèses ( ), un shell enfant est systématiquement créé, et c'est ce dernier qui traite la ligne de commande : $ (commande1 ; commande2 ; commande3) Ici, l'utilisateur se sert des parenthèses ( ) pour rediriger la sortie de deux commandes : $ (date ; ls) > resultat.txt $ cat resultat.txt ven. oct. 16 11:38:55 CEST 2015 fichier.log readme.txt resultat.txt typescript Dans l'exemple suivant, les commandes regroupées 'pwd' et 'ls' ont pour répertoire courant '/tmp' : $ pwd /home/kikinovak $ (cd /tmp ; pwd ; ls) > liste.txt $ cat liste.txt /tmp 7FE2ZX.tmp 8AY85X.tmp 8UBXZX.tmp A6S6ZX.tmp ... Lorsque l'exécution des trois commandes regroupées est terminée, le shell de premier niveau reprend la main, et son répertoire courant est toujours '/home/kikinovak' : $ pwd /home/kikinovak Les accolades { } ----------------- Les deux commandes suivantes produisent le même résultat, mais la version avec les accolades { } est plus rapide : $ (date ; ls) > resultat.txt $ { date ; ls ; } > resultat.txt Au niveau de la syntaxe, les accolades ouvrante { et fermante } doivent être respectivement suivies et précédées par un espace. La dernière commande doit être suivie d'un point-virgule ';' : $ { commande1 ; commande2 ; commande3 ; } À la différence des commandes regroupées entre parenthèses ( ), les commandes regroupées entre accolades { } sont traitées par le shell courant. Reprenons l'exemple précédent en remplaçant les parenthèses ( ) par des accolades { }. L'environnement du shell de premier niveau va être modifié, ce qui peut être gênant : $ pwd /home/kikinovak $ { cd /tmp ; pwd ; ls ; } > ~/liste.txt $ cat ~/liste.txt /tmp 7FE2ZX.tmp 8AY85X.tmp 8UBXZX.tmp A6S6ZX.tmp ... $ pwd /tmp Conclusion ---------- Les parenthèses sont plus utilisées que les accolades pour deux raisons : * leur syntaxe est plus simple à utiliser ; * quel que soit le jeu de commandes, on est toujours sûr de retrouver l'environnement de travail initial. L'utilisation des accolades se justifiera dans le cas d'une recherche de performances. ========================= Processus en arrière-plan ========================= Les notions d'arrière-plan et d'avant-plan sont gérées par le shell. Par défaut, les commandes sont lancées en avant-plan. Dans ce mode, le shell parent s'endort en attendant la fin de la commande. Il reprend la main uniquement lorsque la commande est terminée. Le caractère "&" est un caractère spécial du shell qui permet de lancer la commande en arrière-plan. Le shell lance la commande et réaffiche immédiatement son prompt en attente d'une nouvelle commande. Comme le shell et la commande s'exécutent en parallèle et qu'ils sont tous deux liés au même terminal, il est conseillé de rediriger les sorties de la commande. Dans l'exemple qui suit, le shell affiche le PID de la commande (12421) ainsi que son index ([1]) dans la liste des tâches en arrière-plan lancées à partir de ce shell : $ find / -size +2000 1>/tmp/resultat.txt 2>/dev/null & [1] 12421 $ ========= Exercices ========= 1. Les commandes 'umask' et 'chown' sont-elles des commandes internes ? 2. Soit la liste de fichiers suivante : $ ls bd.class.php essai header.inc.php index.php install.txt mail.class.php readme.txt a. Afficher les noms de fichiers se terminant par ".php". b. Afficher les noms de fichiers ayant la lettre "e" en deuxième position. c. Afficher les noms de fichiers dont la première lettre est comprise entre "a" et "e". d. Afficher les noms de fichiers qui ne commencent pas par une voyelle. e. Afficher les noms de fichiers qui ne se terminent pas par ".php". f. Afficher les noms de fichiers qui ne se terminent ni par ".txt" ni par ".php". /!\ Les questions (e) et (f) nécessitent l'utilisation d'expressions complexes. 3. Comment écrire les deux commandes suivantes sur la même ligne ? $ cd /tmp $ ls -l 4. Lister tous les processus du système et rediriger le résultat dans un fichier. 5. Soit la commande 'who -A', qui génère un message d'erreur : $ who -A who : option invalide -- 'A' Saisissez « who --help » pour plus d'informations. a. Relancer cette commande et rediriger les erreurs dans un fichier. b. Relancer cette commande et faire disparaître les erreurs, sans les rediriger dans un fichier disque. 6. Exécuter les commandes suivantes : $ touch fichier_existe $ chmod 600 fichier_existe fichier_existepas chmod: impossible d'accéder à « fichier_existepas »: Aucun fichier ou dossier de ce type a. Rediriger les résultats de la commande 'chmod' dans un fichier, les erreurs dans un autre. b. Rediriger les résultats et les erreurs de la commande dans un même fichier. 7. Lancer les commandes 'date', 'who' et 'ls' et récupérer le résultat de ces trois commandes dans un fichier (une seule ligne de commande). 8. Lancer les commandes 'date' et 'who -A' et stocker l'affichage de ces deux commandes dans un fichier 'resultat.txt' (une seule ligne de commande). Notez que la commande 'who -A' génère un message d'erreur. 9. Afficher la liste des processus, en paginant l'affichage. 10. En combinant les commandes 'ps' et 'grep', afficher la liste des processus 'httpd' qui tournent sur le système. 11. En combinant les commandes 'tail' et 'head', afficher la sixième ligne du fichier '/etc/passwd'. 12. Créer les fichiers suivants : $ ls f1 f2 fic fic1.txt FIC.c Fic.doc fIc.PDF Compter le nombre de fichiers dont le nom contient le mot "fic". La recherche doit être insensible à la casse. ======== Corrigés ======== 1. Les commandes 'umask' et 'chown' sont-elles des commandes internes ? La commande interne 'type' permet de savoir si une commande possède une implémentation interne. 'umask' est une commande interne : $ type umask umask est une primitive du shell 'chmod' est une commande externe : $ type -a chmod chmod est /usr/bin/chmod chmod est /bin/chmod 2. Soit la liste de fichiers suivante : $ ls bd.class.php essai header.inc.php index.php install.txt mail.class.php readme.txt a. Afficher les noms de fichiers se terminant par ".php". $ ls *.php bd.class.php header.inc.php index.php mail.class.php b. Afficher les noms de fichiers ayant la lettre "e" en deuxième position. $ ls ?e* header.inc.php readme.txt c. Afficher les noms de fichiers dont la première lettre est comprise entre "a" et "e". $ ls [a-e]* bd.class.php essai d. Afficher les noms de fichiers qui ne commencent pas par une voyelle. $ ls [!aeiouy]* bd.class.php header.inc.php mail.class.php readme.txt e. Afficher les noms de fichiers qui ne se terminent pas par ".php". Il faut activer la reconnaissance des expressions complexes : $ shopt -s extglob Puis : $ ls !(*.php) essai install.txt readme.txt f. Afficher les noms de fichiers qui ne se terminent ni par ".txt" ni par ".php". $ ls !(*.php|*.txt) essai 3. Comment écrire les deux commandes suivantes sur la même ligne ? $ cd /tmp $ ls -l Comme ceci : $ cd /tmp ; ls -l 4. Lister tous les processus du système et rediriger le résultat dans un fichier. $ ps -ef > processus.txt 5. Soit la commande 'who -A', qui génère un message d'erreur : $ who -A who : option invalide -- 'A' Saisissez « who --help » pour plus d'informations. a. Relancer cette commande et rediriger les erreurs dans un fichier. $ who -A 2> erreur.txt b. Relancer cette commande et faire disparaître les erreurs, sans les rediriger dans un fichier disque. $ who -A 2> /dev/null 6. Exécuter les commandes suivantes : $ touch fichier_existe $ chmod 600 fichier_existe fichier_existepas chmod: impossible d'accéder à « fichier_existepas »: Aucun fichier ou dossier de ce type a. Rediriger les résultats de la commande 'chmod' dans un fichier, les erreurs dans un autre. $ chmod 600 fichier_existe fichier_existepas 1> resultat.txt \ 2> erreur.txt b. Rediriger les résultats et les erreurs de la commande dans un même fichier. $ chmod 600 fichier_existe fichier_existepas 1> resultat.txt 2>&1 7. Lancer les commandes 'date', 'who' et 'ls' et récupérer le résultat de ces trois commandes dans un fichier (une seule ligne de commande). $ ( date ; who ; ls ) > resultat.txt 8. Lancer les commandes 'date' et 'who -A' et stocker l'affichage de ces deux commandes dans un fichier 'resultat.txt' (une seule ligne de commande). Notez que la commande 'who -A' génère un message d'erreur. $ ( date ; who -A ) > resultat.txt 2>&1 9. Afficher la liste des processus, en paginant l'affichage. $ ps -ef | more Ou bien : $ ps -ef | less 10. En combinant les commandes 'ps' et 'grep', afficher la liste des processus 'httpd' qui tournent sur le système. $ ps -ef | grep httpd 11. En combinant les commandes 'tail' et 'head', afficher la sixième ligne du fichier '/etc/passwd'. $ head -n 6 /etc/passwd | tail -n 1 sync:x:5:0:sync:/sbin:/bin/sync 12. Créer les fichiers suivants : $ ls f1 f2 fic fic1.txt FIC.c Fic.doc fIc.PDF Compter le nombre de fichiers dont le nom contient le mot "fic". La recherche doit être insensible à la casse. $ ls | grep -i fic | wc -l 5 ------------------------------------------------------------------------------ # vim: syntax=txt