IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Shell et commandes GNU Discussion :

problème de séparateur


Sujet :

Shell et commandes GNU

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre averti
    Homme Profil pro
    Technicien réseau
    Inscrit en
    Avril 2011
    Messages
    16
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Nouvelle-Calédonie

    Informations professionnelles :
    Activité : Technicien réseau
    Secteur : Administration - Collectivité locale

    Informations forums :
    Inscription : Avril 2011
    Messages : 16
    Par défaut problème de séparateur
    bonjour,
    j'essaie comprendre ce qui est dit dans l'article ci-dessous
    http://en.wikipedia.org/wiki/Xargs#cite_note-1

    Notamment ceci

    si on tape les commandes suivantes

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    touch important_file
    touch 'not important_file'
    find -name not\* | xargs rm
    le comportement attendu (par les newbies) est l'effacement du fichier 'not important_file', MAIS le comportement REEL est l'effacement du fichier important_file (c'est quand même un comportement surprenant)

    En fait la commande suivante apporte la solution (après avoir créé les fichiers en question par les commandes touch)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    find -name not\* | tr \\n \\0 | xargs -0 rm
    Donc, entre find et rm il y a conversion des \n en \0 dans le flux de sortie de find et ajout de l'option -0 dans xargs

    Je vois bien que çà marche (encore une fois c'est surprenant), mais je ne sais pas EXACTEMENT pourquoi....le problème des séparateurs et (plus généralement) des commandes qui sont ou non orientées ligne me paraît complexe....je ne sais jamais quel est le caractère de fin de ligne
    Pourriez vous me donner une explication sur l'exemple précédent ?
    Merci d'avance

  2. #2
    Expert confirmé Avatar de frp31
    Homme Profil pro
    Ingénieur systèmes et réseaux
    Inscrit en
    Juillet 2006
    Messages
    5 196
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 48
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Ingénieur systèmes et réseaux
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Juillet 2006
    Messages : 5 196
    Par défaut
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    francois@trillian:~/tmp$ ls -lrth
    total 8,0K
    drwx------ 4 francois francois 4,0K  3 mai   20:35 myhello
    -rw-r--r-- 1 francois francois  968  3 mai   21:12 myhello.deb
    francois@trillian:~/tmp$ touch important_file
    francois@trillian:~/tmp$ touch 'not important_file'
    francois@trillian:~/tmp$ find -name "not\ *" -exec rm {} \;
    francois@trillian:~/tmp$ ls
    important_file  myhello  myhello.deb
    francois@trillian:~/tmp$

    pourquoi ?

    la protection de caractère "\\" en lien et place de "\" vient du fait qu'on traverse un tunnel "|" pour arriver à rm hors l'usage des "|" doit tjrs être réduit au minimum pour des raisons évidentes de temps de traitement, c'est pourquoi la vraie solution n'est absolument pas celle mentionnée mais bel et bien d'utiliser l'option exec de find comme je l'ai indiqué.

  3. #3
    Membre averti
    Homme Profil pro
    Technicien réseau
    Inscrit en
    Avril 2011
    Messages
    16
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Nouvelle-Calédonie

    Informations professionnelles :
    Activité : Technicien réseau
    Secteur : Administration - Collectivité locale

    Informations forums :
    Inscription : Avril 2011
    Messages : 16
    Par défaut merci, j'ai bien noté la réponse
    cependant cela ne m'explique pas pourquoi dans la solution non optimisée qui est proposée on change le caractère de fin de ligne \n en \0 avant d'exécuter la commande rm

  4. #4
    Membre expérimenté Avatar de FRUiT
    Homme Profil pro
    Inscrit en
    Février 2011
    Messages
    83
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Février 2011
    Messages : 83
    Par défaut
    Je pense que c'est parce que find ne protège pas ses résultats avec des guillemets, ce qui fait que la commande :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    find -name not\* | xargs rm
    équivaut en fait au final à :
    et non :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    rm 'not important_file'
    Ce qui supprime le fichier « not » et le fichier « important_file ». Comme le fichier « not » n'existe pas, seul « important_file » est supprimé. Ce qui montre les limites de find, et pourquoi il faut plutôt utiliser son option exec dans ce genre de cas.

    Mais peut-être me trompé-je.

  5. #5
    Membre expérimenté Avatar de FRUiT
    Homme Profil pro
    Inscrit en
    Février 2011
    Messages
    83
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Février 2011
    Messages : 83
    Par défaut
    Pour les \n.

    En fait la commande finale, que j'avais shématisée par :
    une fois interprétée serait plutôt quelque chose du genre :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    rm <<EOF
    not
    important_file
    EOF
    C'est imagé hein juste pour illustrer comment le shell passe les fichiers à rm. Le shell a par défaut comme séparateurs d'arguments l'espace la tabulation et le retour à la ligne. Il découpe donc le résultat de find, et convertit les espaces en retours à la ligne afin de délimiter ce qu'il donne à rm.

    Donc le tr remet l'ensemble des arguments sur une seule ligne, puis xargs comvertit les \0 en espaces grâce à son option -0. Pour s'en convaincre :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    > echo -e "foo\nbar"
    foo
    bar
    > echo -e "foo\nbar" | tr \\n \\0
    foobar> 
    > echo -e "foo\nbar" | tr \\n \\0 | xargs -0 echo
    foo bar
    >

  6. #6
    Membre très actif

    Homme Profil pro
    Responsable projets techniques
    Inscrit en
    Février 2003
    Messages
    980
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 46
    Localisation : France, Ille et Vilaine (Bretagne)

    Informations professionnelles :
    Activité : Responsable projets techniques
    Secteur : Biens de consommation

    Informations forums :
    Inscription : Février 2003
    Messages : 980
    Par défaut
    Je ne suis pas tout à fait d'accord avec plusieurs infos des messages précédents, donc je vais donner mon point de vue Et je vais également essayer de préciser certains points. A noter que dans tous les exemples ci-dessous, je ne présente pas à chaque fois la création des fichiers qui ont été éventuellement supprimés par une commande pour ne pas surcharger... mais je pars évidemment toujours de la même liste de fichiers (avec des fichiers en plus au dernier exemple).

    1) Explication du non fonctionnement de find -name not\* | xargs rm.

    Cette commande signifie comme vous le savez que la sortie standard de find est dirigée vers l'entrée standard de rm. Donc, cela revient à faire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    $ find -name not\* > test_rm
    $ xargs rm < test_rm
    Or, si on fait ceci, qu'est ce qu'on constate ?
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    $ find -name not\* > test_rm
    $ cat test_rm
    ./not important_file
    $ xargs rm < test_rm
    rm: cannot remove `./not': No such file or directory
    On constate que le fichier test_rm contient bien une seule ligne avec le nom du fichier qui nous intéresse.
    Mais comme indiqué par FRUiT, ce nom comportant un espace, si on le passe tel quel à rm, cela revient à faire la commande suivante rm ./not important_file.
    Dans ce cas, rm cherche effectivement à supprimer 2 fichier : "./not" et "important_file".


    2) Que faire pour contourner le problème ?
    1ère solution pour un cas simple, utiliser l'option de find -exec comme indiqué par frp31 !
    2ème solution utile si exec ne permet pas de faire ce que l'on veut : utiliser proprement xargs
    Dans le cas de noms relativement simples comme ici, l'option -I{} de xargs permet de travailler proprement (anciennement option -i).
    -I replace-str
    Replace occurrences of replace-str in the initial-arguments with names read from standard input. Also, unquoted blanks do not terminate input items; instead the separator is the newline character.
    Implies -x and -L 1.
    Cette option est intéressante à plus d'un titre : elle ne termine plus un argument avec un blanc qui ne serait pas protégé mais uniquement avec un saut à la ligne, et elle implique les options -x et -L1 qui signifie que xargs arrête son traitement si la taille de l'argument est trop important et que xargs utilise une seule ligne pour construire son argument à chaque fois.
    Concrètement, voici une explication avec un exemple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    $ ll
    total 0
    -rw-r--r-- 1 user users 0 May 19 09:24 important_file
    -rw-r--r-- 1 user users 0 May 19 09:29 not important_file
    -rw-r--r-- 1 user users 0 May 19 09:29 not very important
    $ find -name not\* > test_rm
    $ cat test_rm
    ./not very important
    ./not important_file
    2.1) Premier cas avec xargs simple:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    $ xargs rm < test_rm
    rm: cannot remove `./not': No such file or directory
    rm: cannot remove `very': No such file or directory
    rm: cannot remove `important': No such file or directory
    rm: cannot remove `./not': No such file or directory
    $ ll
    total 4
    -rw-r--r-- 1 user users  0 May 19 09:29 not important_file
    -rw-r--r-- 1 user users  0 May 19 09:29 not very important
    -rw-r--r-- 1 user users 42 May 19 09:29 test_rm
    On voit que les deux fichiers sont mis à la suite et que les espaces ne sont pas protégés > catastrophe !

    2.2) Second cas avec l'option -I{}:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    $ xargs -I{} rm {} < test_rm
    $ ll
    total 4
    -rw-r--r-- 1 user users  0 May 19 09:39 important_file
    -rw-r--r-- 1 user users 42 May 19 09:29 test_rm
    C'est bien mieux les espaces ont été protégés et les fichiers correctement supprimés.

    Mais il peut rester un problème avec cette option -I{} !

    3) Le cas le plus compliqué qu'on verra ici : un nom de fichier comportant un saut de ligne ou des caractères spéciaux...
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    $ touch "not important_file
    at_all"
    $ touch "not important_file\\at_all"
    $ touch 'not "important"_file'
    $ ll
    total 4
    -rw-r--r-- 1 user users  0 May 19 09:40 important_file
    -rw-r--r-- 1 user users  0 May 19 10:19 not "important"_file
    -rw-r--r-- 1 user users  0 May 19 10:21 not important_file\at_all
    -rw-r--r-- 1 user users  0 May 19 10:24 not important_file?at_all
    -rw-r--r-- 1 user users 72 May 19 10:21 test_rm
    $ find -name not\* > test_rm
    $ cat test_rm
    ./not important_file\at_all
    ./not important_file
    at_all
    ./not "important"_file
    $ xargs -I{} rm {} < test_rm
    rm: cannot remove `./not important_fileat_all': No such file or directory
    rm: cannot remove `./not important_file': No such file or directory
    rm: cannot remove `at_all': No such file or directory
    rm: cannot remove `./not important_file': No such file or directory
    Voilà, on a encore le problème puisque cette fois-ci, xargs construit bien des arguments à partir de chaque ligne mais manque de pot, l'un des noms est sur deux lignes, un autre comporte des guillements qui vont passer à la trappe et même chose pour le backslash qui n'est pas échappé...
    C'est là qu'intervient l'option -0 (c'est un zéro) de xargs :
    --null, -0
    Input items are terminated by a null character instead of by whitespace, and the quotes and backslash are not special every character is taken literally). Disables the end of file string, which is treated like any other argument. Useful when input items might contain white space, quote marks, or backslashes. The GNU find -print0 option produces input suitable for this mode.
    couplée généralement avec l'option -print0 de find :
    -print0
    True; print the full file name on the standard output, followed by a null character (instead of the newline character that '-print' uses). This allows file names that contain newlines or other types of white space to be correctly interpreted by programs that process the find output. This option corresponds to the '-0' option of xargs.
    En lisant, on voit clairement qu'elles ont été pensées l'une pour l'autre

    Ces deux options permettent, pour find, de séparer les différents noms trouvés par des caractères nulls plutôt que des sauts de ligne et pour xargs, de lire les arguments en découpant en suivant les caractères nulls au lieu des espaces (conséquence indirecte, les caractères spéciaux sont interprétés directement).

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    $ find -name not\* -print0 > test_rm
    $ cat test_rm
    ./not important_file\at_all./not important_file
    at_all./not "important"_file$# le shell reprend ici puisqu'il n'y a pas de saut de ligne à la fin du fichier mais un caractère null !
    $ xargs -I{} -0 rm {} < test_rm
    $ ll
    total 4
    -rw-r--r-- 1 user users  0 May 19 09:40 important_file
    -rw-r--r-- 1 user users 70 May 19 09:48 test_rm
    Le comportement est bien celui attendu. Si on regarde le fichier intérmédiaire test_rm en détail:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    $ cat test_rm | tr "\0" "|"
    ./not important_file\at_all|./not important_file
    at_all|./not "important"_file|
    On voit clairement les 3 fichiers séparés ici par des "|" (j'ai utilisé tr pour bien mettre en évidence les caractères null en les remplaçant par des "|").
    On voit également que le saut de ligne du fichier "not important_file at_all" est bien présent dans le fichier test_rm ! Et que c'est finalement le seul saut de ligne du fichier.

    Et pour conclure, la solution suivante est à proscrire d'une manière générale :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    $ find -name not\* | tr \\n \\0 | xargs -0 rm
    rm: cannot remove `at_all': No such file or directory
    rm: cannot remove `./not important_file': No such file or directory
    En effet, elle va marcher quand les noms comportent uniquement des espaces (mais on a vu que l'option -I{} suffisait dans ce cas), mais pas quand le nom comporte un saut à la ligne ou d'autres caractères spéciaux puisque le tr va modifier le saut à la ligne du nom sans permettre de traiter les caractères spéciaux, et on va donc se retrouver exactement dans le même cas qu'avec l'utilisation de -I{} seul
    Il faut utiliser l'option -print0 de find qui fait le boulot correctement à la source.

    Je ne sais pas si certains vont lire ça, mais j'espère que c'est clair :p

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Problème de séparateur dans fichier CSV
    Par mbibim63 dans le forum Excel
    Réponses: 4
    Dernier message: 05/07/2007, 08h29
  2. problème de séparateur
    Par intik dans le forum Applets
    Réponses: 1
    Dernier message: 04/12/2006, 13h24
  3. Réponses: 2
    Dernier message: 04/04/2006, 13h40
  4. [VB.NET]Problème de séparateur décimal [débutant]
    Par Cantalou dans le forum Windows Forms
    Réponses: 5
    Dernier message: 03/03/2006, 13h37
  5. Problème de séparateur
    Par Didier94 dans le forum Langage
    Réponses: 3
    Dernier message: 26/09/2005, 16h37

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo