Déplacer et renommer des fichiers en fonction du répertoire de leur parent

1

J'ai une collection de répertoires qui contiennent tous un fichier particulier qui porte le même nom dans tous les répertoires.

dir1/dirA/file.txt
dir1/dirB/file.txt
dir1/dirC/file.txt
....
dir4/dirX/file.txt
dir4/dirY/file.txt
dir4/dirZ/file.txt

Je souhaite copier ces fichiers dans un autre répertoire, de sorte que le nom de chaque fichier correspond au nom de son répertoire parent:

all_files/
    dirA.txt
    dirB.txt
    dirC.txt
    ....
    dirX.txt
    dirY.txt
    dirZ.txt

Bien que je comprenne que ces exemples peuvent entraîner des chevauchements, ce n'est pas un problème pour moi - je sais que tous les répertoires cibles ont des noms uniques.

J'utilise bash sous OS X. J'ai essayé de faire plusieurs choses comme celle-ci (voir ici et ici ). Ma pensée serait de scinder la chaîne sur les barres obliques, d’obtenir l’avant-dernière et de l’utiliser, mais je ne pouvais pas le faire fonctionner (en fonction ce et d'autres):

for name in `ls */*/file`
    directory=${DIRS[${#DIRS[@]} - 2]}
    mv name ../all_files/${directory}.txt
done

Cependant, je ne pouvais pas le faire fonctionner.

dantiston
la source
Je devais l'utiliser à nouveau aujourd'hui ... merci mon ancien!
dantiston

Réponses:

2
for name in ./*/*/file.txt
do
    mv "$name" "../all_files/$(basename -- "$(dirname -- "$name")").txt"
done

Certains commentaires:

  • Ne faites jamais ceci ou quelque chose comme ça:

    for name in `ls */*/file`
    

    Si les noms de fichiers comportent des espaces ou d’autres caractères difficiles, la procédure ci-dessus échouera. Utilisez plutôt:

    for name in ./*/*/file
    

    Le shell gérera correctement ce qui précède, peu importe l'étrangeté du nom de fichier.

  • Cela souffre de plusieurs problèmes:

    mv name ../all_files/${directory}.txt
    

    Plus important encore, cela déplacera le fichier littéralement nommé name. Vous voulez plutôt déplacer le fichier que le name variable fait référence à. Pour cet usage "$name" plutôt que name. Notez l'utilisation de guillemets doubles ici. Cela protège le nom du fractionnement des mots afin que cela fonctionne même si le nom du fichier contient des espaces ou d'autres caractères difficiles.

    De même, l'emplacement de destination doit être entouré de guillemets doubles.

John1024
la source
1

J'y ai un peu réfléchi et j'ai trouvé deux solutions. Le premier utilise basename et dirname:

for name in */*/file
    do cp $name ../all_files/$(basename -- "$(dirname -- "$name")")
done

Celui-ci utilise le fractionnement de chaîne (comme je voulais le faire à l'origine)

for name in */*/choices
    do parts=(${name//\// })
    directory=${parts[${#parts[@]} - 2]}
    cp $name all_files/${directory}.txt
done

J'espère que cela aide quelqu'un!

dantiston
la source
+1 pour utiliser -- après basename et dirname. Cette fonctionnalité n'est pas documentée sur mon système (linux) mais je vois que cela fonctionne.
John1024
@ John1024 à l'époque, je n'étais pas tout à fait sûr de ce que le - faisait non plus, je l'ai trouvé dans un exemple. Je viens de retrouver ce message aujourd'hui et j'ai décidé de le comprendre. Il semble que cela mette fin aux arguments de position, ce qui est pratique pour une fonction telle que basename et dirname qui se comportent différemment si elles ne reçoivent aucun argument. unix.stackexchange.com/questions/11376/…
dantiston