Subtilités de bash, je ne comprend pas tout

Fermé
gnugo - Modifié par gnugo le 12/10/2011 à 22:52
mamiemando Messages postés 33274 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 20 septembre 2024 - 16 oct. 2011 à 22:39
Bonjour,
je suis entrain d'essayer de comprendre certains aspects de bash, notamment sur les méta-caractères et comment protéger des chaines de caractères qui se passent de commandes en commandes.

Je bute un peu sur les différentes interprétations des espaces et sauts de lignes que font les utilitaires, notamment pour différencier les arguments qu'on passe à une commande.
Qu'il s'agisse d'une chaine avec des espaces "aa bb cc" ou de 3 chaines différentes aa bb cc, on ne voit jamais la différence sur le terminal et je ne comprends pas ce qui se passe derrière.

Par exemple, si je fais variable="aa bb cc" puis ls $variable , pourquoi il cherche 3 fichiers aa bb cc et non pas un seul "aa bb cc" ?
comment savoir dans un script bash si une variable texte va être interprétée par une commande comme un seul ou plusieurs arguments?


Ce serait super si vous pouviez m'expliquer comment cela fonctionne.


Sinon ma question plus pratique :
je voudrais faire un cpio de plusieurs fichiers
je fais donc echo "fic1 fic2" | cpio -o
mais cpio prend ça comme un seul fichier "fic1 fic2" et je ne trouve pas du tout comment lui dire que ce sont 2 fichiers différents.
Par contre avec la commande ls ( ls fic1 fic2 | cpio -o ) ça fonctionne ... pourquoi ?
Est-il possible d'utiliser cpio avec la sortie de la commande echo , ou de tout autre manière autre qu'avec ls ou find ?


Merci !


N'hésitez pas à m'insulter si je ne suis pas très clair!:-)





A voir également:

6 réponses

mamiemando Messages postés 33274 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 20 septembre 2024 7 782
Modifié par mamiemando le 13/10/2011 à 01:01
comment savoir dans un script bash si une variable texte va être interprétée par une commande comme un seul ou plusieurs arguments?

En fait tu mets le doigts sur l'un des gros soucis de bash, que ce soit
- pour appeler une fonction shell à laquelle on passe plusieurs arguments (ce qui sépare les arguements... c'est le caractère espace, comme dans n'importe quelle commande shell !)
- pour passer un argument à un script ou à une fonction shell.

Variables $@ et $* (scripts et appels de fonctions shell)

C'est justement à cause de ce genre de considérations qu'on utilise par exemple $@ ou $*.

Exemple : ll.sh

#!/bin/sh 
echo "************" 
ls -l "$*" # va échouer 
echo "@@@@@@@@@@@@" 
ls -l "$@" # ok 
exit 0


Test :

mkdir "~/mon rep" "~/mon rep2" 
./ll.sh ~/mon*


Séquence d'échappement

De manière générale, à moins d'explicitement échapper un espace, celui-ci est considéré comme le métacaractère de séparation.

Ainsi :

cd "mon répertoire" 
cd mon\ répertoire


... sont équivalentes. Par contre :

cd mon répertoire


... signifie qu'on passe un argument "mon" et "répertoire" (et donc là ça va planter).

xargs, pipe, for et opérateur $(...) '...'

En fait la principale difficulté en shell c'est de comprendre comment itérer sur un ensemble d'élément.

Alors avant tout je précise tout de suite que contrairement à ce que la mise en forme (stupide), ce ne sont pas des apostrophes mais des backquotes (alt gr 7) qu'ils faut utiliser quand je parle de l'opérateur '...'.

En bash l'opérateur $(...) se comporte comme l'opérateur '...', il permet d'évaluer le contenu de l'opérande en tant que commande et de la substituer par son résultat.

Exemple :

(mando@aldur) (~) $ seq 1 5 
1 
2 
3 
4 
5 
(mando@aldur) (~) $ for x in 'seq 1 5'; do echo "coucou" $x; done 
coucou 1 
coucou 2 
coucou 3 
coucou 4 
coucou 5


Note qu'en bash (un shell particulier qui fournit des opérateurs et des fonctions supplémentaires par rapport à un shell générique) on peut également écrire :

for x in $(seq 1 5); do echo "coucou" $x; done


Par la suite j'utiliserai $(...) pour éviter les ambiguïtés.

Si on utilise un opérateur capable de manipuler un flux sur /dev/stdin (l'entrée standard), comme grep, cut etc... on peut appliquer cette commande à chaque ligne reçu au niveau du flux.

Exemple :

dpkg -l | grep "^ii" | cut -d" " -f3


Le processus dpkg -l liste les paquets installés ou partiellement installés sous debian. Chaque paquets installés est précédé d'un tag (ii). Le nom du paquet apparaît derrière le deuxième caractère espace.

Ainsi :

(mando@aldur) (~) $ dpkg -l 
... 
ii  xterm          271-1          X terminal emulator 
rc  xulrunner-1.9. 1.9.1.19-3     XUL + XPCOM application runner 
ii  xulrunner-7.0  7.0.1-2        XUL + XPCOM application runner 
ii  xz-utils       5.1.1alpha+201 XZ-format compression utilities 
ii  zip            3.0-4          Archiver for .zip files 
ii  zlib1g         1:1.2.3.4.dfsg compression library - runtime 

(mando@aldur) (~) $ dpkg -l | grep "^ii" 
... 
ii  xterm          271-1          X terminal emulator 
rc  xulrunner-1.9. 1.9.1.19-3     XUL + XPCOM application runner 
ii  xulrunner-7.0  7.0.1-2        XUL + XPCOM application runner 
ii  xz-utils       5.1.1alpha+201 XZ-format compression utilities 
ii  zip            3.0-4          Archiver for .zip files 
ii  zlib1g         1:1.2.3.4.dfsg compression library - runtime 

(mando@aldur) (~) $ dpkg -l | grep "^ii" | cut -d" " -f3 
... 
xterm 
xulrunner-7.0 
xz-utils 
zip 
zlib1g


De manière générale quand un traitement peut être réalisée par un pipe c'est toujours mieux (plus performant).

Ici supposons que je veuille supprimer tous les paquets "rc" (partiellement installés, sous debian ça veut dire que le paquets est supprimé, mais pas ses fichiers de configuration).

dpkg -l | grep "^rc" | cut -d" " -f3


... me renverra ces paquets. Maintenant je peux dire que je lance successivement "aptitude purge paquet1", "aptitude purge paquet2", "aptitude purge paquet3". Ceci se réaliserait par :

for paquet in $(dpkg -l | grep "^rc" | cut -d" " -f3); do aptitude purge $paquet, done


Ou alors je pourrais vouloir écrire "aptitude purge paquet1 paquet2 paquet3" car la commande aptitude supporte le passage de plusieurs arguments. Dans ce cas là je peux utiliser xargs :

dpkg -l | grep "^rc" | cut -d" " -f3 | xargs aptitude purge


En effet voici ce que fait xargs :

(mando@aldur) (~) $ dpkg -l | grep "^rc" | cut -d" " -f3 
gimp-data 
libatkmm-1.6-1 
libavformat52 
libbabl-0.0-0 
libcairomm-1.0-1 
... 

(mando@aldur) (~) $ dpkg -l | grep "^rc" | cut -d" " -f3 | xargs 
gimp-data libatkmm-1.6-1 libavformat52 libbabl-0.0-0 libcairomm-1.0-1 libcroco3 libdv4 libexif12 libgail18 libgconfmm-2.6-1c2 libgegl-0.0-0 libgimp2.0 libglibmm-2.4-1c2a libgraphite3...


... et je peux passer à xargs une commande en paramètre à laquelle elle va passer le flux qu'elle a mis en ligne :

dpkg -l | grep "^rc" | cut -d" " -f3 | xargs aptitude purge


Dernier truc à savoir, une commande sépare dans un flux les éléments grâce aux espaces, mais aussi les retours chariots. Ainsi je peux écrire directement (et c'est la commande que j'utilise en pratique) :

aptitude purge $(dpkg -l | grep "^rc" | cut -d" " -f3)


J'espère que ça t'aura éclairé, mais si tu as besoin de précisions, n'hésite pas ;-)
0
jisisv Messages postés 3645 Date d'inscription dimanche 18 mars 2001 Statut Modérateur Dernière intervention 15 janvier 2017 934
Modifié par jisisv le 13/10/2011 à 01:22
C'est normal., cpio prend sur l'entrée standard les noms des fichiers séparés par des newlines.
donc:
  
johand@osiris: ~/tmp/cpio $ echo -ne  "a\nb\nc"   | cpio -o > out.cpio  
1 bloc  
johand@osiris: ~/tmp/cpio $ echo -ne  "a b c"   | cpio -o > out.cpio  
cpio: a b c: échec de la fonction : stat: Aucun fichier ou dossier de ce type  
1 bloc  
johand@osiris: ~/tmp/cpio $ touch "Un nom de fichier avec des espaces"  
johand@osiris: ~/tmp/cpio $ echo Un\ nom\ de\ fichier\ avec\ des\ espaces | cpio -o > brol.cpio  
1 bloc  
johand@osiris: ~/tmp/cpio $ cpio -i --list < brol.cpio  
Un nom de fichier avec des espaces  
1 bloc


Enfin cpio est plutôt obsolète de nos jours.
Utilise plutôt tar (avec compression gzip, bzip2, xz éventuelle)
(à moins que tu ne doives résoudre un execice scolaire, les enseignants ayant une fâcheuse tendance à ne pas se tenir au courant)

Johan
Gates gave ^H sold you the windows.
GNU gave us the whole house.(Alexandrin)
0
Merci pour vos réponses! Ça m'aide beaucoup, certaines commandes comme cpio attendent donc des arguments séparés par des sauts de ligne, et d'autres comme le for, une séparation que ce soit espace ou saut de ligne.

Je vais quand même en redemander puisque je peux:)

- Comment se fait il qu'un "ls" nous affiche un résultat avec des espaces pour séparateurs, alors qu'en fait elle les sépare bien avec des retours à la ligne ?? (visible en faisant ls | cat)
- (J'avais une autre question sur les différences entre l'éxécution dans un script bash et la ligne de commande "à la main" mais je viens de comprendre!)


re merci d'avance.



ps: en fait je n'utilise ni cpio ni tar mais pax, qui permet de faire plus de choses et génère du tar. Il se comporte comme cpio donc j'ai mis cpio dans mon exemple car il est plus connu je pense.
0
mamiemando Messages postés 33274 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 20 septembre 2024 7 782
13 oct. 2011 à 19:53
- Comment se fait il qu'un "ls" nous affiche un résultat avec des espaces pour séparateurs, alors qu'en fait elle les sépare bien avec des retours à la ligne ?? (visible en faisant ls | cat)

cat prend un paramètre un fichier. Il le lit "ligne par ligne" et affiche successivement chaque ligne. Ce fichier peut être /dev/stdin (la saisie au clavier typiquement). C'est sur ça que repose le fait d'utiliser | cat.

Fais un ls > toto et regarde le contenu de toto. Tu verras que dans toto tu as bien un fichier par ligne. C'est ce flux qui est manipulé transmis par |. Je ne peux pas te dire exactement comment cette remise en forme est faite mais dans la même veine, la commande mysql est capable de remettre en forme la sortie d'une requête mysql, et quand tu rediriges sont résultat dans un fichier le résultat est différent. Bref ce que tu vois dans la console n'est pas ce qui est réellement écrit dans le flux.

ps: en fait je n'utilise ni cpio ni tar mais pax, qui permet de faire plus de choses et génère du tar. Il se comporte comme cpio donc j'ai mis cpio dans mon exemple car il est plus connu je pense.

Attention car pax n'est pas une commande installée forcément sur tous les systèmes, tandis que tar est généralement présent. Utiliser pax restreint le domaine d'utilisation de ton script. Si tu peux utiliser tar c'est mieux par exemple.

Bonne chance
0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
merci
Je l'accepte alors meme si je sais pas pourquoi ça se comporte comme ça.J'en tiendrai compte et je pourrai me débrouiller
0
mamiemando Messages postés 33274 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 20 septembre 2024 7 782
16 oct. 2011 à 22:39
Du coup est-ce que ton problème est résolu ?
0