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.
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).
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'
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
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):
ls -A
Le corrige, mais pas ls -a
)mv "$(ls | grep -v '^foo$')" foo
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.
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
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
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-files
option 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
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
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!