web-dev-qa-db-fra.com

`os.symlink` vs` ln -s`

J'ai besoin de créer un lien symbolique pour chaque élément de dir1 (fichier ou répertoire) à l'intérieur de dir2. dir2 existe déjà et n'est pas un lien symbolique. Dans Bash, je peux facilement y parvenir en:

ln -s /home/guest/dir1/* /home/guest/dir2/

Mais en python en utilisant os.symlink Je reçois une erreur:

>>> os.symlink('/home/guest/dir1/*', '/home/guest/dir2/')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OSError: [Errno 17] File exist

Je sais que je peux utiliser subprocess et exécuter la commande ln. Je ne veux pas de cette solution.

Je suis également conscient que les solutions de contournement utilisant os.walk ou glob.glob sont possibles, mais je veux savoir s'il est possible de le faire en utilisant os.symlink.

35
jurgenreza

os.symlink crée un seul lien symbolique.

ln -s crée plusieurs liens symboliques (si son dernier argument est un répertoire et qu'il existe plusieurs sources). L'équivalent Python est quelque chose comme:

dst = args[-1]
for src in args[:-1]:
    os.symlink(src, os.path.join(dst, os.path.dirname(src)))

Alors, comment ça marche quand vous faites ln -s /home/guest/dir1/* /home/guest/dir2/? Votre Shell fait que cela fonctionne, en transformant le caractère générique en plusieurs arguments. Si vous deviez simplement exec la commande ln avec un caractère générique, elle chercherait une seule source nommée littéralement * dans /home/guest/dir1/, pas tous les fichiers de ce répertoire.

L'équivalent Python est quelque chose comme (si cela ne vous dérange pas de mélanger deux niveaux ensemble et d'ignorer beaucoup d'autres cas - tildes, variables env, substitution de commandes, etc.) qui sont possibles sur le Shell ):

dst = args[-1]
for srcglob in args[:-1]:
    for src in glob.glob(srcglob):
        os.symlink(src, os.path.join(dst, os.path.dirname(src)))

Vous ne pouvez pas faire ça avec os.symlink seul, soit une partie de celui-ci, car il ne fait pas cela. C'est comme dire "je veux faire l'équivalent de find . -name foo en utilisant os.walk sans filtrer sur le nom. "Ou, d'ailleurs, je veux faire l'équivalent de ln -s /home/guest/dir1/* /home/guest/dir2/ sans que le coquillage ne me touche. "

La bonne réponse consiste à utiliser glob, ou fnmatch, ou os.listdir plus une expression régulière, ou ce que vous préférez.

N'utilisez pas os.walk, car cela fait un parcours du système de fichiers récursif , donc il n'est même pas proche de Shell * expansion.

57
abarnert

* est un modèle d'extension Shell, qui dans votre cas désigne "tous les fichiers commençant par /home/guest/dir1/ ".

Mais c'est votre le rôle de Shell pour développer ce modèle aux fichiers auxquels il correspond. Pas la commande ln.

Mais os.symlink n'est pas un Shell, c'est un appel au système d'exploitation - par conséquent, il ne prend pas en charge les modèles d'extension Shell. Vous devrez faire ce travail dans votre script.

Pour ce faire, vous pouvez utiliser os.walk, ou os.listdir. Comme indiqué dans l'autre réponse, l'appel approprié dépendra de ce que vous voulez faire. (os.walk ne serait pas l'équivalent de *)


Pour vous convaincre: exécutez cette commande sur une machine Unix dans votre terminal: python -c "import sys; print sys.argv" *. Vous verrez que c'est le Shell qui fait l'appariement.

14
Thomas Orozco

Comme suggéré par @abarnert, c'est le Shell qui reconnaît * et le remplace par tous les éléments situés à l'intérieur de dir1. Je pense donc qu'en utilisant os.listdir est le meilleur choix:

for item in os.listdir('/home/guest/dir1'):
    os.symlink('/home/guest/dir1/' + item, '/home/guest/dir2/' + item)
5
jurgenreza