Forum Programmation.shell [résolu] Corriger des liens symboliques en masse

Posté par (page perso) .
Tags : aucun
2
25
sept.
2012

J'ai un gros paquet de liens symboliques dont la destination a changé. Je souhaite modifier ces liens pour qu'ils pointent vers le bon endroit.
Facile !
Hé ben non, je n'y arrive pas :)

Seul un élément du chemin est à modifier :
Les liens sont dans /home/kerro/xxxxxx/ (mélangés à d'autres fichiers)
ancien = /media/yyyy/zzzz/fichier_cible
nouveau = /mnt/yyyy/zzzz/fichier_cible

Il me faut donc faire 'ln -s /mnt/yyyy/zzzzz/fichier_cible /home/kerro/xxxxxx/lien1'
Je tente avec :

find /home/kerro/xxxxx -type l -print0 | xargs -0 -n 1 -I {} ln -s $(readlink -m {} \| sed 's/$.media/mnt/') {}

mais cela échoue lamentablement.
En simplifiant la commande, je bute sur :

echo '/toto' | xargs -0 -n 1 -I {} echo $(readlink -m '{}')
 --> /home/kerro//toto   alors que /toto est attendu

Je dois avoir les yeux plein de merde, je ne pige pas le problème.

  • # Ordre d'évaluation

    Posté par . Évalué à 3. Dernière modification le 25/09/12 à 03:04.

    readlink -m '{}' est évalué en premier, sans substitution, cela donne /home/kerro/{} .
    Puis {} est remplacé par /toto, cela donne /home/kerro//toto

    Question, pourquoi se compliquer la vie avec le -print0 de find et le xargs compliqué ?

    J'essaierais un truc comme ça :

    find /home/kerro/xxxxx -type l -exec change_link '{}' \;
    
    

    avec change_link un programme annexe ou une fonction

    ln -s $(readlink -m "$1" \| sed 's/$.media/mnt/') "$1"

    • [^] # Re: Ordre d'évaluation

      Posté par (page perso) . Évalué à 2.

      Je ne pige pas pourquoi readlink -m '{}' est évalué en premier sans substitution, puisque le principe de xargs est, justement, de faire ça en boucle.

      Concernant l'utilisation d'une fonction, c'était le plan B. Mais je tenais à trouver la solution en une ligne :)

      • [^] # Re: Ordre d'évaluation

        Posté par . Évalué à 2.

        le principe de xargs est, justement, de faire ça en boucle.

        absolument pas, et même au contraire !

        $ seq 1 3
        1
        2
        3
        $ seq 1 3 | xargs echo
        1 2 3
        
        

        xargs fabrique une ligne séparée par des espaces à partir d'une liste séparée par des retours chariot et invoque la commande en paramètre avec cette liste. Autrement dit:

        ligne1\\nligne2\\ligne3 | xargs command -options [paramètres]
        
        

        revient au final à

        command -options [paramètres] ligne1 ligne2 ligne3
        
        

        le problème pour ton ln, c'est qu'on voudrait plutôt que ça devienne

        command -options ligne1 ligne2 ligne3 [paramètres]
        
        

        vu que le répertoire cible pour les liens doit être le dernier argument de ln.

        A ma connaissance, xargs ne permet pas de préciser à quel endroit de la commande on veut que la liste séparée par des espaces soit injectée (mais j'aimerais bien que quelqu'un me contredise et me montre comment faire !).

        • [^] # Re: Ordre d'évaluation

          Posté par (page perso) . Évalué à 2.

          xargs ne permet pas de préciser à quel endroit de la commande on veut que la liste séparée par des espaces soit injectée

          C'est l'option -I replace-str

          seq 1 4 | xargs -I {} echo '***{}***'
          ***1***
          ***2***
          ***3***
          ***4***
          
          

          Ou encore :

          seq 1 4 | xargs -I insérez_ici_le_truc echo '***insérez_ici_le_truc***'
          ***1***
          ***2***
          ***3***
          ***4***
          
          

          Dans cet exemple on voit bien que echo '***{}***' est évalué à chaque fois.
          Donc je ne pige pas pourquoi mon readlink ne fonctionne pas. Il y a un effet de bord qui vient d'ailleurs et que je ne saisi pas.

          • [^] # Re: Ordre d'évaluation

            Posté par . Évalué à 2.

            Prenons ton deuxième exemple :

            echo '/toto' | xargs -0 -n 1 -I {} echo $(readlink -m '{}')
            
            

            Ce qui se passe est que lorsque ton shell évalue la ligne de commande, il va évaluer $(readlink -m '{}') avant de passer le résultat de cette évaluation à xargs.

            L'évaluation de $(readlink -m '{}') donne, dans ton cas, /home/kerro/{}

            Donc tout se passe comme si tu avais écrit :

            echo '/toto' | xargs -0 -n 1 -I {} echo /home/kerro/{}
            
            

            Ce qui donne bien le résultat que tu obtiens.

            • [^] # Re: Ordre d'évaluation

              Posté par (page perso) . Évalué à 2.

              lorsque ton shell évalue la ligne de commande, il va évaluer $(readlink -m '{}') avant de passer le résultat de cette évaluation à xargs.

              C'est tellement évident…
              La honte :)

              Merci de m'avoir éclairé.

          • [^] # Re: Ordre d'évaluation

            Posté par . Évalué à 3. Dernière modification le 26/09/12 à 00:25.

            ah chouette ! merci pour la réponse, j'ai appris un truc !

            du coup, en prenant en compte le commentaire de Barnabé sur l'évaluation du readlink, il "suffit" de faire ce qu'il faut pour readlink soit évalué à chaque itération au lieu d'être interpréter sur la ligne de commande.

            Du coup, on peut aussi s'en tirer avec:

            $ mkdir -p dir/link1 dir/link2 dir/link3
            $ touch dir/link1/1 dir/link1/2
            $ ln -s $(pwd)/dir/link1/* $(pwd)/dir/link2
            $ tree dir
            dir
            ├── link1
            │   ├── 1
            │   └── 2
            ├── link2
            │   ├── 1 -> /home/gab/dir/link1/1
            │   └── 2 -> /home/gab/dir/link1/2
            └── link3
            
            3 directories, 4 files
            $ find $(pwd)/dir/link2 -type l |xargs -I {} bash -c 'ln -s $(readlink -m {}) $(pwd)/dir/link3/'
            $ tree dir
            dir
            ├── link1
            │   ├── 1
            │   └── 2
            ├── link2
            │   ├── 1 -> /home/gab/dir/link1/1
            │   └── 2 -> /home/gab/dir/link1/2
            └── link3
                ├── 1 -> /home/gab/dir/link1/1
                └── 2 -> /home/gab/dir/link1/2
            
            3 directories, 6 files
            
            
  • # Faire un while ?

    Posté par (page perso) . Évalué à 2.

    Tout simplement ?

    find . -type l | while read mylink; do ln -s $(readlink -m {} \| sed 's/$.media/mnt/') $mylink; done
    
    

    Is it a Bird? Is it a Plane?? No, it's Super Poil !!!

    • [^] # Re: Faire un while ?

      Posté par (page perso) . Évalué à 3.

      Je tentais avec xargs sans avoir pensé à une autre solution. De la merde dans les yeux je disais.

      Voici la version qui fonctionne :

      find '/home/kerro/xxxxxx/' -type l | while read f; do ln -sf "$(readlink -m "$f" | sed 's/\/avant\//\/après\//')" "$f"; done
      
      
      • [^] # Re: Faire un while ?

        Posté par (page perso) . Évalué à 3.

        Tu peux éditer l'entrée de forum pour indiquer [résolu] :-)

        bizarre ton

        sed 's/\/avant\//\/après\//')
        
        

        j'utilise plutôt la syntaxe

        sed 's#/avant/#/après/#'
        
        

        Cela permet de gérer les manipulations de répertoire (où un / traîne souvent), sans avoir à échapper le caractère / vu que j'utilise # comme séparateur des chaînes ;-) Bon, j'ai sans doute été traumatisé par les exécutions de programmes shell dans des programmes shells en ksh où les caractères d'échappement sautaient (et il fallait les échapper une, deux ou plus de fois… pour qu'ils soient pris en compte), mais pas toujours de la même façon, sinon c'est pas drôle… (selon le nombre d'appels iirc).

  • # zsh

    Posté par (page perso) . Évalué à 3. Dernière modification le 25/09/12 à 09:16.

    Je ne sais pas utiliser find, donc j'utiliserais un peu de zsh dans une boucle for :

    for l in /home/kerro/xxxx/*(@); ln -sf "${${l:A}/media/mnt}" "$l"
    
    

    Explication : le @ permet de ne trouver que les liens symboliques, le :A résout le chemin absolu de la destination.

  • # FHS

    Posté par (page perso) . Évalué à 1.

    Curieux ton exemple. Tu es passé d'un chemin standard /media/truc vers un chemin non-standard /mnt/truc. Sachant que, d'après la FHS, /mnt est fait pour les montages temporaires, je te conseillerais plutôt de revenir à ta convention précédente.

    • [^] # Re: FHS

      Posté par . Évalué à 6.

      bizarre j'aurais cru l'inverse :
      - les montages temporaires dans /media (clef USB, CDROM)
      - les montages permanents dans /mnt

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.