Existe-t-il un moyen de faire en sorte que mv crée le répertoire vers lequel il doit être déplacé s'il n'existe pas?

275

Donc, si je suis dans mon répertoire personnel et que je souhaite déplacer foo.c vers ~ / bar / baz / foo.c, mais que ces répertoires n'existent pas, existe-t-il un moyen de créer automatiquement ces répertoires, de sorte que il suffit de taper

mv foo.c ~/bar/baz/ 

et tout irait bien? Il semble que vous pouvez alias mv vers un simple script bash qui vérifierait si ces répertoires existaient et sinon appelait mkdir puis mv, mais j'ai pensé que je vérifierais si quelqu'un avait une meilleure idée.

Paul Wicks
la source

Réponses:

294

Que diriez-vous de ce one-liner (en bash):

mkdir --parents ./some/path/; mv yourfile.txt $_

Décomposer cela:

mkdir --parents ./some/path

crée le répertoire (y compris tous les répertoires intermédiaires), après quoi:

mv yourfile.txt $_

déplace le fichier vers ce répertoire ($ _ se développe jusqu'au dernier argument passé à la commande shell précédente, c'est-à-dire: le répertoire nouvellement créé).

Je ne sais pas jusqu'où cela fonctionnera dans d'autres coquilles, mais cela pourrait vous donner quelques idées sur ce qu'il faut rechercher.

Voici un exemple utilisant cette technique:

$ > ls
$ > touch yourfile.txt
$ > ls
yourfile.txt
$ > mkdir --parents ./some/path/; mv yourfile.txt $_
$ > ls -F
some/
$ > ls some/path/
yourfile.txt
KarstenF
la source
32
Sympa avec le '$ _'.
dmckee --- chaton ex-modérateur
1
Pourquoi l'obsession du redondant ./partout? Les arguments ne sont pas des commandes dans PATH sans le .répertoire ...
Jens
3
pour les débutants, --les parents peuvent être raccourcis en -p
sakurashinken
8
Curieusement, --parentsn'est pas disponible sur macOS 10.13.2. Vous devez utiliser -p.
Joshua Pinter
1
Ne fonctionne pas si vous déplacez un fichier, par exemple ./some/path/foo. Dans ce cas, la commande doit être:mkdir -p ./some/path/foo; mv yourfile.txt $_:h
hhbilly
63
mkdir -p `dirname /destination/moved_file_name.txt`  
mv /full/path/the/file.txt  /destination/moved_file_name.txt
Billmc
la source
2
Je suis le seul à réaliser que ce code n'a aucun sens? Vous créez récursivement le chemin absolu du fichier existant (ce qui signifie que ce chemin existe déjà), puis déplacez le fichier vers un emplacement (qui, dans votre exemple, n'existe pas).
vdegenne
1
@ user544262772 GNU coreutils ' dirnamene nécessite pas que son argument existe, c'est une pure manipulation de chaîne. Je ne peux pas parler pour d'autres distributions. Rien de mal à ce code afaict.
TroyHurts
C'est la réponse qui est la plus facile à automatiser, je pense. for f in *.txt; do mkdir -p `dirname /destination/${f//regex/repl}`; mv "$f" "/destination/${f//regex/repl}; done
Kyle
23

Enregistrer en tant que script nommé mv ou mv.sh

#!/bin/bash
# mv.sh
dir="$2"
tmp="$2"; tmp="${tmp: -1}"
[ "$tmp" != "/" ] && dir="$(dirname "$2")"
[ -a "$dir" ] ||
mkdir -p "$dir" &&
mv "$@"

Ou mettez à la fin de votre fichier ~ / .bashrc une fonction qui remplace le mv par défaut sur chaque nouveau terminal. L'utilisation d'une fonction permet à bash de conserver sa mémoire, au lieu d'avoir à lire un fichier de script à chaque fois.

function mv ()
{
    dir="$2"
    tmp="$2"; tmp="${tmp: -1}"
    [ "$tmp" != "/" ] && dir="$(dirname "$2")"
    [ -a "$dir" ] ||
    mkdir -p "$dir" &&
    mv "$@"
}

Celles-ci sont basées sur la soumission de Chris Lutz.

Sepero
la source
3
Cela répond le plus précisément à la question à mon humble avis. L'utilisateur souhaite une mvcommande améliorée . Particulièrement utile pour les scripts, c'est-à-dire que vous ne voulez pas nécessairement exécuter les vérifications et exécuter à mkdir -ptout moment que vous devez utiliser mv. Mais comme je voudrais le comportement d'erreur par défaut pour mv, j'ai changé le nom de la fonction en mvp- afin que je sache quand je pourrais créer des répertoires.
Brian Duncan
Cela semble être la meilleure idée de solution, mais la mise en œuvre se bloque beaucoup.
Ulises Layera
2
@UlisesLayera J'ai modifié l'algorithme pour le rendre plus robuste. Il ne devrait pas planter maintenant
Sepero
Quelqu'un pourrait-il s'il vous plaît expliquer la signification de [ ! -a "$dir" ]J'ai mené des expériences avec la moitié droite étant vraie et fausse, toutes deux évaluées à vrai .. ??? Pour l'amour des autres, tmp = "$ {tmp: -1}" semble saisir le dernier caractère du fichier pour s'assurer qu'il ne s'agit pas d'un chemin (/)
bazz
@bazz Il devrait faire "si $ dir n'existe pas, alors continuez". Malheureusement, vous avez raison, il y a un bug lors de l'utilisation de "! -A", et je ne sais pas pourquoi. J'ai un peu modifié le code et je dois maintenant travailler.
Sepero
13

Vous pouvez utiliser mkdir:

mkdir -p ~/bar/baz/ && \
mv foo.c ~/bar/baz/

Un script simple pour le faire automatiquement (non testé):

#!/bin/sh

# Grab the last argument (argument number $#)    
eval LAST_ARG=\$$#

# Strip the filename (if it exists) from the destination, getting the directory
DIR_NAME=`echo $2 | sed -e 's_/[^/]*$__'`

# Move to the directory, making the directory if necessary
mkdir -p "$DIR_NAME" || exit
mv "$@"
strager
la source
Quand je lance "$ dirname ~? Bar / baz /", j'obtiens "/ home / dmckee / bar", ce qui n'est pas ce que vous voulez ici ...
dmckee --- chaton ex-modérateur
@dmckee, Ah, vous avez raison. Une idée sur la façon de résoudre ce problème? Si vous saisissez ~ / bar / baz, vous (a) souhaitez copier dans ~ / bar / et renommer en baz, ou (b) copier dans ~ / bar / baz /. Quel est le meilleur outil pour le travail?
strager
@dmckee, j'ai utilisé regexp / sed pour trouver une solution. Cela fonctionne-t-il à votre goût? =]
strager
Bien, sauf que $ 2 n'est pas le dernier argument à moins que $ # = 2. J'ai un programme, la, qui affiche son dernier argument. Je l'ai utilisé dans une version de la commande cp (pour ajouter le répertoire courant si le dernier argument n'était pas un répertoire). Même les obus modernes prennent en charge 1 $ .. 9 $ seulement; un script Perl peut être mieux.
Jonathan Leffler
@Leffler, pas vrai - je sais que bash prend en charge plus de 9 arguments (utiliser $ {123} est une méthode). Je ne connais pas Perl, alors n'hésitez pas à répondre vous-même. =]
strager
7

Il semble que la réponse soit non :). Je ne veux pas vraiment créer un alias ou une fonction juste pour le faire, souvent parce que c'est unique et que je suis déjà en train de taper la mvcommande, mais j'ai trouvé quelque chose qui fonctionne bien pour cela:

mv *.sh  shell_files/also_with_subdir/ || mkdir -p $_

Si mvéchoue (dir n'existe pas), il fera le répertoire (qui est le dernier argument de la commande précédente, il en $_est de même). Il suffit donc d'exécuter cette commande, puis upde la réexécuter, et cette fois, cela mvdevrait réussir.

Tapoter
la source
5

Le script shell suivant, peut-être?

#!/bin/sh
if [[ -e $1 ]]
then
  if [[ ! -d $2 ]]
  then
    mkdir --parents $2
  fi
fi
mv $1 $2

Voilà l'essentiel. Vous voudrez peut-être ajouter un peu pour vérifier les arguments, et vous voudrez peut-être que le comportement change si la destination existe, ou si le répertoire source existe, ou n'existe pas (c'est-à-dire ne pas écraser quelque chose qui n'existe pas) .

Chris Lutz
la source
1
Avec votre code, le déplacement n'est pas effectué si le répertoire n'existe pas!
strager
1
Et vous avez manqué le drapeau "-p" pour mkdir.
dmckee --- chaton ex-modérateur
Si un répertoire n'existe pas, pourquoi le créer si vous allez simplement le déplacer? (-p flag fixed)
Chris Lutz
Je veux dire, lorsque vous exécutez le script et que le répertoire $ 2 n'existe pas, il est créé mais le fichier n'est pas copié.
strager
il a donné une construction simple et a ajouté des informations sur la façon dont il devrait être développé. Pourquoi voter contre?!
ypnos
5

La commande rsync ne peut faire l'affaire que si le dernier répertoire du chemin de destination n'existe pas, par exemple pour le chemin de destination de ~/bar/baz/si barexiste mais bazn'existe pas, alors la commande suivante peut être utilisée:

rsync -av --remove-source-files foo.c ~/bar/baz/

-a, --archive               archive mode; equals -rlptgoD (no -H,-A,-X)
-v, --verbose               increase verbosity
--remove-source-files   sender removes synchronized files (non-dir)

Dans ce cas, le bazrépertoire sera créé s'il n'existe pas. Mais si les deux baret bazn'existent pas, rsync échouera:

sending incremental file list
rsync: mkdir "/root/bar/baz" failed: No such file or directory (2)
rsync error: error in file IO (code 11) at main.c(657) [Receiver=3.1.2]

Donc, fondamentalement, il devrait être sûr de l'utiliser rsync -av --remove-source-filescomme alias mv.

sepehr
la source
4

La façon la plus simple de le faire est:

mkdir [directory name] && mv [filename] $_

Supposons que j'ai téléchargé des fichiers pdf situés dans mon répertoire de téléchargement ( ~/download) et que je souhaite les déplacer tous dans un répertoire qui n'existe pas (disons my_PDF).

Je vais taper la commande suivante (en m'assurant que mon répertoire de travail actuel est ~/download):

mkdir my_PDF && mv *.pdf $_

Vous pouvez ajouter une -poption mkdirsi vous souhaitez créer des sous-répertoires comme ceci: (supposé que je veux créer un sous-répertoire nommé python):

mkdir -p my_PDF/python && mv *.pdf $_
Abdoul Seibou
la source
2

Sillier, mais façon de travail:

mkdir -p $2
rmdir $2
mv $1 $2

Créez le répertoire avec mkdir -p, y compris un répertoire temporaire qui partage le nom du fichier de destination, puis supprimez ce répertoire de nom de fichier avec un simple rmdir, puis déplacez votre fichier vers sa nouvelle destination. Je pense que la réponse en utilisant dirname est probablement la meilleure.

Blarn Blarnson
la source
Ouais, un peu idiot. :-) Si $ 2 est un répertoire (comme dans la question d'origine) alors il échoue lamentablement, car le dernier répertoire sera supprimé, donc 'mv' échouera. Et si $ 2 existe déjà, il essaie également de le supprimer.
Tylla
Merci! C'est exactement ce que je devais accomplir: mv ./old ./subdir/new où subdir n'existe pas encore
Mike Murray
2
((cd src-path && tar --remove-files -cf - files-to-move) | ( cd dst-path && tar -xf -))
svaardt
la source
1

Code:

if [[ -e $1 && ! -e $2 ]]; then
   mkdir --parents --verbose -- "$(dirname -- "$2")"
fi
mv --verbose -- "$1" "$2"

Exemple:

arguments: "d1" "d2 / sub"

mkdir: created directory 'd2'
renamed 'd1' -> 'd2/sub'
John Doe
la source
1

Cela se déplacera foo.cvers le nouveau répertoire bazavec le répertoire parent bar.

mv foo.c `mkdir -p ~/bar/baz/ && echo $_`

L' -poption de mkdircrée des répertoires intermédiaires selon les besoins.
Sans -ptous les répertoires, le préfixe du chemin doit déjà exister.

Tout ce qui se trouve à l'intérieur des backticks ``est exécuté et la sortie est retournée en ligne dans le cadre de votre commande.
Puisque mkdirne renvoie rien, seule la sortie de echo $_sera ajoutée à la commande.

$_référence le dernier argument à la commande précédemment exécutée.
Dans ce cas, il retournera le chemin de votre nouveau répertoire ( ~/bar/baz/) passé à la mkdircommande.


J'ai décompressé une archive sans donner de destination et j'ai voulu déplacer tous les fichiers sauf demo-app.zipde mon répertoire actuel vers un nouveau répertoire appelé demo-app.
La ligne suivante fait l'affaire:

mv `ls -A | grep -v demo-app.zip` `mkdir -p demo-app && echo $_`

ls -Arenvoie tous les noms de fichiers, y compris les fichiers cachés (à l' exception... des fichiers implicites et ).

Le symbole de canal |est utilisé pour diriger la sortie de la lscommande vers grep( un utilitaire de recherche en texte brut en ligne de commande ).
L' -vindicateur indique grepde rechercher et de renvoyer tous les noms de fichiers à l'exclusion demo-app.zip.
Cette liste de fichiers est ajoutée à notre ligne de commande en tant qu'arguments source de la commande move mv. L'argument cible est le chemin d'accès au nouveau répertoire transmis à mkdirréférencé à l'aide $_et la sortie à l'aide echo.

Evan Wunder
la source
1
C'est un bon niveau de détail et d'explication - et surtout pour un premier post. Merci!
Jeremy Caney
Merci, @JeremyCaney! Je suis ravi de commencer à aider!
Evan Wunder
0

Vous pouvez même utiliser des extensions d'accolade:

mkdir -p directory{1..3}/subdirectory{1..3}/subsubdirectory{1..2}      
  • qui crée 3 répertoires (répertoire1, répertoire2, répertoire3),
    • et dans chacun d'eux deux sous-répertoires (sous-répertoire1, sous-répertoire2),
      • et dans chacun d'eux deux sous-répertoires (sous-répertoire1 et sous-répertoire2).

Vous devez utiliser bash 3.0 ou plus récent.

Stefan Gruenwald
la source
5
Intéressant, mais ne répond pas à la question du PO.
Alexey Feldgendler
0
$what=/path/to/file;
$dest=/dest/path;

mkdir -p "$(dirname "$dest")";
mv "$what" "$dest"
cab404
la source
1
pour en faire un script sh autonome, il suffit de remplacer $ dest et $ src par $ 1 et $ 2
cab404
0

Ma solution à une chaîne:

test -d "/home/newdir/" || mkdir -p "/home/newdir/" && mv /home/test.txt /home/newdir/
Andrey
la source
0

Vous pouvez enchaîner les commandes: mkdir ~/bar/baz | mv foo.c ~/bar/baz/
ou le script shell est ici:

#!/bin/bash
mkdir $2 | mv $1 $2

1. Ouvrez n'importe quel éditeur de texte
2. Script shell copier-coller .
3. Enregistrez- le sous mkdir_mv.sh
4. Entrez le répertoire de votre script: cd your/script/directory
5. Changez le mode de fichier : chmod +x mkdir_mv.sh
6. Définissez l' alias : alias mv="./mkdir_mv.sh"
Maintenant, chaque fois que vous exécutez la commande, mvun répertoire mobile sera créé s'il n'existe pas.

Sharofiddin
la source
0

Sur la base d'un commentaire dans une autre réponse, voici ma fonction shell.

# mvp = move + create parents
function mvp () {
    source="$1"
    target="$2"
    target_dir="$(dirname "$target")"
    mkdir --parents $target_dir; mv $source $target
}

Incluez-le dans .bashrc ou similaire afin de pouvoir l'utiliser partout.

Ben Winding
la source