Scripts de shell UNIX: comment déplacer récursivement des fichiers dans un répertoire?

8

J'ai un grand nombre de petits fichiers, f, disposés dans une structure de répertoires comme ceci:

/A/B/C/f

Il existe 11 répertoires au niveau A, chacun avec environ 100 répertoires au niveau B, chacun avec environ 30 répertoires au niveau C, chacun avec un fichier f.

Comment déplacer tous les fichiers d'un niveau? Par exemple, étant donné cet ensemble de fichiers ...

/ A / B / C / f1
/ A / B / C / f2
/ A / B / C / f3
/ A / B / C / f4

Je veux que le répertoire /A/B/contienne 4 fichiers, f1 à f4. La suppression des répertoires C n'est pas nécessaire.

J'espère que c'est un problème résolu, impliquant éventuellement find, xargset whatnot. Des idées?

À votre santé,

James

jl6
la source

Réponses:

9

C'est assez simple avec la recherche GNU (telle que trouvée sous Linux) ou toute autre découverte qui prend en charge -execdir:

find A -type f -execdir mv -i {} .. \;

Avec un standard find:

find A -type f -exec sh -c 'mv -i "$1" "${1%/*}/.."' sh {} \;

Avec zsh:

zmv -Q -o-i 'A/(**/)*/(*)(.)' 'A/$1$2'

Si la structure de répertoires a toujours le même niveau d'imbrication, vous n'avez pas besoin de parcours récursif (mais supprimez d'abord les répertoires vides):

for x in */*; do; echo mv -i "$x"/*/* "$x"/..; done
Gilles
la source
1

Pour cet ensemble de fichiers, ceci ferait:

$ cd /A/B/C/
$ mv ./* ../

Mais je m'attends à ce que votre problème soit un peu plus compliqué ... Je ne peux pas répondre à cela ... Je ne sais pas trop comment votre structure de répertoire est ... Pourriez-vous préciser?

BloodPhilia
la source
Cela fonctionnera évidemment pour n’importe quel répertoire / A / B / C, mais comme j’ai environ 30000 répertoires de ce type, j’espère une commande pouvant être exécutée au sommet de la hiérarchie pour s’occuper de tous en même temps.
jl6
1
@ jl6 je vois, et seuls les fichiers doivent être déplacés? Donc C n'a pas besoin de passer à B, et B pas à A, etc ...
BloodPhilia
C'est exact - l'opération ne doit être effectuée que sur des fichiers, au niveau le plus bas de la hiérarchie, et ils ne doivent être déplacés que d'un niveau supérieur.
jl6
0

Ma première hypothèse est

$ find A -type f -exec mv {} .. \;  

tant que vous ne spécifiez pas, -depthça devrait aller. Je ne l'ai pas essayé alors testez-le avant de vous y engager.

Hotei
la source
Que trouve-t-il quand il y a deux fichiers portant le même nom?
Trouver n'est pas le problème, la commande "mv" incorporée l'est. Les noms Dupe ne seraient pas bons car mv ne va pas renommer, il va écraser. Vous pouvez utiliser mv -i pour aider un peu, mais avec plus de 30 000 fichiers, cela pourrait être douloureux. Si vous avez besoin de beaucoup de contrôle sur la situation, vous aurez peut-être besoin d'un programme écrit dans un langage de script (mon choix serait python, YMMV).
accueil le
Je n'ai pas utilisé de "backups" avec mv, mais si vous regardez la page de manuel, cela pourrait être une solution à votre problème de noms de dupe. "trouver un type f -exec mv - sauvegarde numérotée {} .. \;" pourrait fonctionner (notez que c'est un double tiret dans - backup)
Hotei le
L'exemple de recherche donné déplacera tous les fichiers du niveau le plus bas dans un répertoire situé au-dessus de A (je pense que le ".." est en train d'être interprété par le shell avant d'être transmis à / mv?). J'ai besoin des fichiers pour monter d'un niveau dans la hiérarchie. Une précision supplémentaire: il n'y aura pas de noms de fichiers en double, à condition que les fichiers ne remontent que d'un niveau.
jl6
@hotei: Les mvcommandes sont exécutées dans le répertoire en cours, ..ne faites donc pas ce que vous espérez. Voir ma réponse pour des solutions. @ jl6: le shell n'a rien à voir .., c'est géré par le noyau.
Gilles
0

Si vous ne souhaitez que déplacer les fichiers qui sont dans des répertoires de feuilles (vous ne voulez pas passer /A/B/fileà /Asi Bcontient des sous - répertoires) , alors voici quelques façons de le faire:

Les deux exigent cela

leaf ()
{
    find $1 -depth -type d | sed 'h; :b; $b; N; /^\(.*\)\/.*\n\1$/ { g; bb }; $ {x; b}; P; D'
}
shopt -s nullglob

Celui-ci fonctionne:

leaf A | while read -r dir
do
    for file in "$dir"/*
    do
        parent=${dir%/*}
        if [[ -e "$parent/${file##*/}" ]]
        then
            echo "not moved: $file"
        else
            mv "$file" "$parent"
        fi
    done
done

Ce sera plus rapide, mais cela n'aime pas les répertoires sources vides:

leaf A | while read -r dir
do
    mv -n "${dir}"/* "${dir%/*}"
    remains=$(echo "$dir"/*)
    [[ -n "$remains" ]] && echo "not moved: $remains"
done
Dennis Williamson
la source