web-dev-qa-db-fra.com

Comment déplacer un répertoire dans un répertoire du même nom?

J'ai un répertoire foo avec plusieurs fichiers:

.
└── foo
    ├── a.txt
    └── b.txt

et je veux le déplacer dans un répertoire du même nom:

.
└── foo
    └── foo
        ├── a.txt
        └── b.txt

Je crée actuellement un répertoire temporaire bar, déplacez foo dans bar et renommez bar en foo par la suite:

mkdir bar
mv foo bar
mv bar foo

Mais cela semble un peu lourd et je dois choisir un nom pour bar qui n'est pas déjà pris.

Existe-t-il un moyen plus élégant ou simple d'y parvenir? Je suis sur macOS si cela importe.

32
Stefan

Pour créer en toute sécurité un répertoire temporaire dans le répertoire courant, avec un nom qui n'est pas déjà pris, vous pouvez utiliser mktemp -d Comme ceci:

tmpdir=$(mktemp -d "$PWD"/tmp.XXXXXXXX)   # using ./tmp.XXXXXXXX would work too

La commande mktemp -d Créera un répertoire au chemin donné, avec les X- à la fin du chemin d'accès remplacés par des caractères alphanumériques aléatoires. Il renverra le chemin du répertoire qui a été créé et nous stockons cette valeur dans tmpdir.1

Cette variable tmpdir pourrait alors être utilisée en suivant la même procédure que vous effectuez déjà, avec bar remplacé par "$tmpdir":

mv foo "$tmpdir"
mv "$tmpdir" foo
unset tmpdir

Le unset tmpdir À la fin supprime simplement la variable.


1Habituellement, on devrait pouvoir définir la variable d'environnement TMPDIR sur un chemin de répertoire où l'on veut créer des fichiers temporaires ou des répertoires avec mktemp, mais l'utilitaire sur macOS semble fonctionner subtilement différemment à cet égard que le même utilitaire sur d'autres systèmes BSD, et créera le répertoire dans un emplacement totalement différent. Ce qui précède fonctionnerait cependant sur macOS. Utiliser la fonction tmpdir=$(TMPDIR=$PWD mktemp -d) ou même tmpdir=$(TMPDIR=. mktemp -d) un peu plus pratique ne serait un issue sur macOS que si le répertoire temporaire par défaut était sur une autre partition et le foo le répertoire contenait beaucoup de données (c'est-à-dire qu'il serait lent).

21
Kusalananda

Sur macOS, vous pouvez installer la commande rename (un script Perl) en utilisant Homebrew:

brew install rename

Puis en utilisant le -p (à la mkdir) pour lui faire créer tous les répertoires nécessaires, et -A pour ajouter un préfixe:

% mkdir -p foo/bar; touch foo/{a,b}.txt foo/bar/c.txt
% rename -p -A foo/ foo/*
% tree foo
foo
└── foo
    ├── a.txt
    ├── b.txt
    └── bar
        └── c.txt

Courir avec -n pour afficher les modifications sans renommer (exécution à sec):

% rename -p -A foo/ foo/* -n
'foo/a.txt' would be renamed to 'foo/foo/a.txt'
'foo/b.txt' would be renamed to 'foo/foo/b.txt'
'foo/bar' would be renamed to 'foo/foo/bar'

Si vous avez des fichiers dot, de sorte qu'un simple * ne les récupérera pas, alors utilisez d'autres méthodes avec rename:

  • Avec bash, (mis) utilisez GLOBIGNORE pour obtenir * pour faire correspondre les fichiers de points:

    $ GLOBIGNORE=.; rename -p -A foo/ foo/* -n
    'foo/.baz' would be renamed to 'foo/foo/.baz'
    'foo/a.txt' would be renamed to 'foo/foo/a.txt'
    'foo/b.txt' would be renamed to 'foo/foo/b.txt'
    'foo/bar' would be renamed to 'foo/foo/bar'
    
  • Ou utilisez find avec -print0 et rename avec -0:

    % find foo -mindepth 1 -maxdepth 1 -print0 | rename -0 -p -A foo/ -n
    Reading filenames from STDIN
    Splitting on NUL bytes
    'foo/b.txt' would be renamed to 'foo/foo/b.txt'
    'foo/a.txt' would be renamed to 'foo/foo/a.txt'
    'foo/bar' would be renamed to 'foo/foo/bar'
    'foo/.baz' would be renamed to 'foo/foo/.baz'
    
10
muru

Tant que le contenu n'est pas suffisant pour dépasser les limites maximales des paramètres (et cela ne vous dérange pas un message d'erreur "acceptable"), cela n'a pas besoin d'être plus compliqué que cela:

mkdir foo/foo
mv foo/* foo/foo

Amendement pour gérer les fichiers cachés:

mkdir foo/foo
mv foo/{.,}* foo/foo
10
bxm

Je suggère l'inverse. Ne déplacez pas le répertoire, mais uniquement son contenu:

.
└── foo
    ├── a.txt
    └── b.txt
mkdir foo/foo
.
└── foo
    ├── foo
    ├── a.txt
    └── b.txt
cd foo
mv $(ls | grep -v '^foo$') foo
cd -
.
└── foo
    └── foo
        ├── a.txt
        └── b.txt

Si vous avez bash, vous pouvez aussi faire

shopt -s extglob
cd foo
mv !(foo) foo
cd -

(comme décrit ici ) pour éviter d'exécuter ls et grep.

L'avantage de cette solution est qu'elle est vraiment simple.

Inconvénients (comme indiqué dans les commentaires):

  • Impossible de gérer les fichiers cachés (ls -A Le corrige, mais pas ls -a)
  • Ne peut pas gérer les noms de fichiers contenant des espaces (peut être corrigé par des guillemets: mv "$(ls | grep -v '^foo$')" foo
  • Ne peut pas gérer les noms de fichiers contenant des sauts de ligne et d'autres caractères spéciaux

La plupart des inconvénients peuvent être résolus en utilisant une astuce bash, mais si l'on doit gérer des noms de fichiers fous, il est préférable d'utiliser une approche plus robuste, comme décrit dans d'autres réponses.

8
Adam Trhon

Vous l'avez déjà assez bien compris. Vous pouvez choisir un nom différent pour le répertoire transitoire, tel que le nom cible avec la date/heure actuelle en nanosecondes et notre PID en tant que suffixe composite, mais cela présuppose toujours que le répertoire n'existe pas déjà:

dir=foo                         # The directory we want to nest
now=$(date +'%s_%N')            # Seconds and nanoseconds
mkdir "$dir.$now.$$"            # Create a transient directory
mv -f "$dir" "$dir.$now.$$/"    # Move our directory inside transient directory
mv -f "$dir.$now.$$" "$dir"     # Rename the transient directory to the name with which we started

Si vous voulez une solution robuste garantie, je ferais le tour du mkdir jusqu'à ce qu'il réussisse

now=$(date +'%s_%N')
while ! mkdir -m700 "$dir.$now.$$" 2>/dev/null
do
    sleep 0.$$
    now=$(date +'%s_%N')
done
6
roaima
mkdir foo/foo && mv foo/!(foo) foo/foo

Vous devez cd dans le répertoire où se trouve le dossier source (foo).

Exécutez ensuite la commande ci-dessus. Il va créer un dossier appelé du même nom et déplacer le contenu du foo parent dans le répertoire enfant foo (sauf le répertoire enfant, d'où la désignation!).

Si le répertoire enfant foo existe déjà, vous pouvez ignorer la première partie et simplement exécuter la commande mv:

mv foo/!(foo) foo/foo

Sous MacOS, vous devrez peut-être définir l'option extglob:

shopt -s extglob
6
masterl

En plus des suggestions ci-dessus, vous souhaiterez peut-être commander rsync. Avant même de commencer, n'oubliez pas d'utiliser le --dry-run option avec rsync avant de l'exécuter sans lui. Le --dry-run vous dira ce qui se passerait dans la vraie vie.

rsync propose de nombreuses options qui peuvent non seulement aider à copier/déplacer des fichiers, mais aussi accélérer le processus. Voici une façon de faire votre travail. Le --remove-source-filesoption supprime les fichiers de la source après le processus de déplacement (qui est en fait une copie suivie d'une suppression). Notez que la commande rsync lit d'abord le contenu de foo, crée le répertoire foo dans le répertoire existant appelé foo, puis démarre le processus de copie. Cela se termine par la suppression des fichiers source dans foo. Attention: Faites particulièrement attention aux barres obliques pour les noms de répertoire.

Il y a d'autres considérations comme beaucoup l'ont souligné ci-dessus (comme les liens symboliques), mais un man rsync vous aidera à choisir les options dont vous avez besoin. Une note ici: Puisque vous avez demandé une solution pour les fichiers, cela fonctionnera. Cela ne supprimera pas les répertoires vides qui seront laissés pour compte. Je ne suis pas au courant d'un moyen de le faire sans étape supplémentaire, donc si quelqu'un peut ajouter à cela, merci d'avance!

mkdir foo
cd foo
touch aaa bbb ccc ddd
cd ..
rsync -av --remove-source-files foo foo/

a = mode archive

v = verbeux

2
Hopping Bunny

Ne touchez simplement pas foo et vous n'avez pas de problèmes avec des noms identiques. Au lieu de cela, créez un répertoire avec le même nom à l'intérieur et déplacez-y tout. Utilisez l'astuce $ _ pour cela et && pour en faire une doublure:

cd foo && mkdir $_ && mv * $_

Cela générera une erreur inoffensive (mv: renommer foo en foo/foo: argument non valide) que vous pouvez ignorer.

Avantage par rapport aux autres réponses: vous n'avez qu'à taper le nom du répertoire une fois, vous ne pouvez donc pas faire d'erreurs avec les fautes de frappe.


vous pouvez mélanger cela avec d'autres réponses pour un effet encore meilleur:

  • mv {.,}* $_ S'occupera des fichiers cachés (au prix de plus d'erreurs que vous pouvez également ignorer)
  • mv ./!(foo) évitera le message d'erreur, mais uniquement si vous définissez shopt -s extglob
2
Tom

C'est en fait d'une simplicité trompeuse et je suis choqué que personne d'autre n'ait encore proposé cette réponse, alors je vais utiliser les éléments suivants par exemple ...

$ mkdir foo
$ mkdir bar
$ mkdir ./bar/foo
$ touch ./bar/foo/file1 && touch ./bar/foo/file2 && touch ./bar/foo/file3
$ ls ./
bar/ foo/
$ ls ./bar/foo
file1 file2 file3

Vient maintenant la partie magique

$ mv ./bar/foo ./foo/
$ ls ./foo/
foo/
$ ls ./foo/foo/
file1 file2 file3

Maintenant pourquoi ça marche? Je ne peux pas vous donner la réponse la plus précise car je ne suis qu'un néophyte - mais je comprends que cela se résume à la façon dont les caractères / Sont traités dans les dossiers.

Regardez attentivement cette commande mv

$ mv ./bar/foo ./foo/

Remarquez comment le dossier cible des fichiers est spécifié sans / De fin, tandis que la destination partageant le nom du dossier cible a été spécifiée à l'aide d'un / De fin. '' Ceci est votre ticket magique et peut vous mordre dans le cul si vous ne faites pas attention.

Je suis désolé, je ne peux pas offrir une réponse plus élaborée que celle actuellement, mais peut-être que quelqu'un de plus couramment informé prendra la liberté de modifier ma réponse.

En tout état de cause, cela semble être la solution la plus simple et la plus directe, avec une seule utilisation de la commande ls et aucune création de dossiers intermédiaires.

J'espère que cela fonctionne pour vous, cheers!

0
PowerLuser