Is there a way to make mv create the directory to be moved to if it doesn't exist?

261,159

Solution 1

How about this one-liner (in bash):

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

Breaking that down:

mkdir --parents ./some/path
# if it doesn't work; try
mkdir -p ./some/path

creates the directory (including all intermediate directories), after which:

mv yourfile.txt $_

moves the file to that directory ($_ expands to the last argument passed to the previous shell command, ie: the newly created directory).

I am not sure how far this will work in other shells, but it might give you some ideas about what to look for.

Here is an example using this technique:

$ > ls
$ > touch yourfile.txt
$ > ls
yourfile.txt
$ > mkdir --parents ./some/path/; mv yourfile.txt $_
$ > ls -F
some/
$ > ls some/path/
yourfile.txt

Solution 2

mkdir -p `dirname /destination/moved_file_name.txt`  
mv /full/path/the/file.txt  /destination/moved_file_name.txt

Solution 3

Save as a script named mv.sh

#!/bin/bash
# mv.sh
dir="$2" # Include a / at the end to indicate directory (not filename)
tmp="$2"; tmp="${tmp: -1}"
[ "$tmp" != "/" ] && dir="$(dirname "$2")"
[ -a "$dir" ] ||
mkdir -p "$dir" &&
mv "$@"

Or put at the end of your ~/.bashrc file as a function that replaces the default mv on every new terminal. Using a function allows bash keep it memory, instead of having to read a script file every time.

function mvp ()
{
    dir="$2" # Include a / at the end to indicate directory (not filename)
    tmp="$2"; tmp="${tmp: -1}"
    [ "$tmp" != "/" ] && dir="$(dirname "$2")"
    [ -a "$dir" ] ||
    mkdir -p "$dir" &&
    mv "$@"
}

Example usage:

mv.sh file ~/Download/some/new/path/ # <-End with slash

These based on the submission of Chris Lutz.

Solution 4

You can use mkdir:

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

A simple script to do it automatically (untested):

#!/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 "$@"

Solution 5

It sounds like the answer is no :). I don't really want to create an alias or func just to do this, often because it's one-off and I'm already in the middle of typing the mv command, but I found something that works well for that:

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

If mv fails (dir does not exist), it will make the directory (which is the last argument to the previous command, so $_ has it). So just run this command, then up to re-run it, and this time mv should succeed.

Share:
261,159
Paul Wicks
Author by

Paul Wicks

Updated on December 07, 2021

Comments

  • Paul Wicks
    Paul Wicks over 2 years

    So, if I'm in my home directory and I want to move foo.c to ~/bar/baz/foo.c , but those directories don't exist, is there some way to have those directories automatically created, so that you would only have to type

    mv foo.c ~/bar/baz/ 
    

    and everything would work out? It seems like you could alias mv to a simple bash script that would check if those directories existed and if not would call mkdir and then mv, but I thought I'd check to see if anyone had a better idea.

  • strager
    strager over 15 years
    With your code, the move isn't performed if the directory does not exist!
  • dmckee --- ex-moderator kitten
    dmckee --- ex-moderator kitten over 15 years
    And you missed the "-p" flag to mkdir.
  • visual_learner
    visual_learner over 15 years
    If a directory doesn't exist, why create it if you're just going to move it? (-p flag fixed)
  • strager
    strager over 15 years
    I mean, when you run the script and the directory $2 does not exist, it is created but the file is not copied.
  • dmckee --- ex-moderator kitten
    dmckee --- ex-moderator kitten over 15 years
    When I run "$ dirname ~?bar/baz/", I get "/home/dmckee/bar", which is not what yo want here...
  • strager
    strager over 15 years
    @dmckee, Ah, you are right. Any idea on how to solve this? If you input ~/bar/baz you either (a) want to copy to ~/bar/ and rename to baz, or (b) copy to ~/bar/baz/. What's the better tool for the job?
  • strager
    strager over 15 years
    @dmckee, I've used regexp/sed to come up with a solution. Does it work to your liking? =]
  • Jonathan Leffler
    Jonathan Leffler over 15 years
    Nice, except $2 is not the last argument unless $# = 2. I have a program, la, that prints its last argument. I used to use it in a version of the cp command (to add the current directory if the last argument wasn't a directory). Even modern shells support $1..$9 only; a Perl script may be better.
  • ypnos
    ypnos over 15 years
    he gave a simple construct and added information on how it should be expanded. Why vote it down?!
  • dmckee --- ex-moderator kitten
    dmckee --- ex-moderator kitten over 15 years
    Closer. But why are you making the construction of the target directory conditional on the non-existence of the file to be moved?
  • dmckee --- ex-moderator kitten
    dmckee --- ex-moderator kitten over 15 years
    @ypnos: Because so far it doesn't do what it is supposed to?
  • dmckee --- ex-moderator kitten
    dmckee --- ex-moderator kitten over 15 years
    And I haven't fixed it myself for pedagogical reasons.
  • strager
    strager over 15 years
    @Leffler, Not true -- I know bash supports more than 9 arguments (using ${123} is one method). I don't know Perl, so feel free to make an answer yourself. =]
  • dmckee --- ex-moderator kitten
    dmckee --- ex-moderator kitten over 15 years
    And even if you're using a plain vanilla /bin/sh, you can loop over 'store=${store} " " $1; shift;' until only one argument remains... Though at that point we've lost any semblance of elegance the script might have had.
  • strager
    strager over 15 years
    dmckee, I was going to try shift, but then found a more elegent solution. See update.
  • dmckee --- ex-moderator kitten
    dmckee --- ex-moderator kitten over 15 years
    The logic here: No point in try the move unless $1 exists, then if the target does not exist, make it. Might want to add a test so we don't overwrite an existing regular file named '~/bar/baz'.
  • strager
    strager over 15 years
    Agreed, but this is a less general solution. My solution could serve as a mv substitude (thus an alias in bash) (assuming it works properly -- still untested!).
  • Alexey Feldgendler
    Alexey Feldgendler over 11 years
    Interesting, but doesn't answer the OP's question.
  • Jens
    Jens about 11 years
    Why the obsession with redundant ./ everywhere? The args are not commands in PATH without the . directory...
  • Brian Duncan
    Brian Duncan over 9 years
    This answers the question most accurately IMHO. The user would like an enhanced mv command. Especially useful for scripting, ie you don't necessarily want to run the checks and run mkdir -p anytime you need to use mv. But since I would want the default error behavior for mv, I changed the function name to mvp -- so that I know when I could be creating directories.
  • Ulises Layera
    Ulises Layera about 9 years
    Seems like the best solution idea, butimplementation crashes a lot.
  • Sepero
    Sepero about 9 years
    @UlisesLayera I modified the algorithm to make it more robust. It should not crash now
  • bazz
    bazz about 9 years
    Could someone please explain the meaning of [ ! -a "$dir" ] I have conducted experiments with the right half being true and false, both evaluated to true.. ??? For other's sake, tmp="${tmp: -1}" appears to grab the last character of the file to make sure it's not a path (/)
  • Sepero
    Sepero about 9 years
    @bazz It should be doing "if $dir does not exist, then continue". Unfortunately, you are correct, there is a bug when using "! -a", and I don't know why. I have edited the code a bit, and should now work.
  • ArchLinuxTux
    ArchLinuxTux over 8 years
    ok, so at first I didn't understand why Chris Lutz checks $1, since $2 is the directory to be created. But Chris Lutz is absolutely right, if $1 does not exist mv will raise an error and therefore one may also not want the destination directory to be created. In the second if-condition one may not only test for existance of type directory but for existance regardless of type. Since when it exists and it is a file makedir will cause an error even with the -p option (btw. which it will not do for an existing directory with -p option on archlinux). So I suggest to test for existance regardless of
  • ArchLinuxTux
    ArchLinuxTux over 8 years
    type. And finally @dmckee's last point: If one wants this function/script to behave like the standard mv apart from creating nested directories, than one shall not add a test whether we are copying a file or a directory. But that may be different depending on one's needs. So if you dmckee meant this as optional we both agree ;).
  • cab404
    cab404 almost 8 years
    to make it standalone sh script, just replace $dest and $src with $1 and $2
  • sakurashinken
    sakurashinken about 7 years
    for the newbies, --parents can be shortened to -p
  • vdegenne
    vdegenne over 6 years
    I am the only one to realize this code makes no sense? You create recursively the absolute path to the existing file (which means this path already exist) and then move the file to a location (which in your example doesn't exist).
  • Joshua Pinter
    Joshua Pinter over 6 years
    Oddly, --parents is not available on macOS 10.13.2. You need to use -p.
  • TroyHurts
    TroyHurts over 6 years
    @user544262772 GNU coreutils' dirname doesn't require its argument to exist, it's pure string manipulation. Can't speak for other distributions. Nothing wrong with this code afaict.
  • hhbilly
    hhbilly over 5 years
    Doesn't work if moving a file eg ./some/path/foo. In this case the command should be: mkdir -p ./some/path/foo; mv yourfile.txt $_:h
  • Tylla
    Tylla about 5 years
    Yepp, kinda silly.:-) If $2 is a directory (as in the original question) then it fails miserably, as the last directory will be deleted, so 'mv' will fail. And if $2 already exists then it also tries to delete it.
  • Kyle
    Kyle almost 5 years
    This is the answer that is most easily automated, I think. for f in *.txt; do mkdir -p `dirname /destination/${f//regex/repl}`; mv "$f" "/destination/${f//regex/repl}; done
  • Mike Murray
    Mike Murray over 4 years
    Thanks! This is exactly what I needed to accomplish: mv ./old ./subdir/new where subdir doesn't yet exist
  • unixandria
    unixandria almost 4 years
    The function crashes my shell after spamming something about !=: Unary operator expected
  • Rufus
    Rufus almost 4 years
    If anyone is getting the error [: too many arguments, convert all all single square brackets to double square brackets. I'm too lazy to find out why that's happening...
  • Orestis Kapar
    Orestis Kapar over 3 years
    This answer is more of a note to anyone stumbling upon a specific sub-case of the question. I have had to rethink this solution multiple times after arriving to this exact SO question, so I decided to post it here for future reference. Still, it's possible it doesn't belong here.
  • sbolel
    sbolel over 2 years
    this is exactly what i was looking for!
  • pmiguelpinto
    pmiguelpinto over 2 years
    That is not an one-liner.