386

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.

Paul Wicks
  • 62,960
  • 55
  • 119
  • 146
  • Related: https://stackoverflow.com/questions/1529946/linux-copy-and-create-destination-dir-if-it-does-not-exist – kvantour Apr 17 '19 at 07:59

20 Answers20

384

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
Aakash
  • 21,375
  • 7
  • 100
  • 81
KarstenF
  • 5,345
  • 3
  • 21
  • 16
80
mkdir -p `dirname /destination/moved_file_name.txt`  
mv /full/path/the/file.txt  /destination/moved_file_name.txt
radiaph
  • 4,001
  • 1
  • 16
  • 19
Billmc
  • 833
  • 6
  • 2
  • 5
    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). – vdegenne Dec 03 '17 at 19:06
  • 5
    @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. – TroyHurts Mar 16 '18 at 13:37
  • 1
    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`` – Kyle Jul 11 '19 at 03:33
  • @vdegenne - note that BSD (and therefore Macos) also perform `dirname` as string manipulation and do not check for file/path existence. – ocodo Mar 12 '23 at 12:14
31

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.

sbolel
  • 3,486
  • 28
  • 45
Sepero
  • 4,489
  • 1
  • 28
  • 23
  • 4
    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. – Brian Duncan Nov 26 '14 at 01:42
  • Seems like the best solution idea, butimplementation crashes a lot. – Ulises Layera Apr 09 '15 at 13:58
  • 2
    @UlisesLayera I modified the algorithm to make it more robust. It should not crash now – Sepero May 21 '15 at 11:58
  • 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 (/) – bazz Jun 21 '15 at 05:28
  • @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. – Sepero Jun 22 '15 at 07:32
  • The function crashes my shell after spamming something about !=: Unary operator expected – unixandria Sep 02 '20 at 10:29
  • 1
    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... – Rufus Sep 04 '20 at 11:14
  • this is exactly what i was looking for! – sbolel Dec 06 '21 at 19:13
16

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 "$@"
sbolel
  • 3,486
  • 28
  • 45
strager
  • 88,763
  • 26
  • 134
  • 176
  • When I run "$ dirname ~?bar/baz/", I get "/home/dmckee/bar", which is not what yo want here... – dmckee --- ex-moderator kitten Feb 13 '09 at 21:33
  • @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 Feb 13 '09 at 21:38
  • @dmckee, I've used regexp/sed to come up with a solution. Does it work to your liking? =] – strager Feb 13 '09 at 21:42
  • 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. – Jonathan Leffler Feb 13 '09 at 21:44
  • @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. =] – strager Feb 13 '09 at 22:06
  • 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. – dmckee --- ex-moderator kitten Feb 13 '09 at 22:12
  • dmckee, I was going to try shift, but then found a more elegent solution. See update. – strager Feb 13 '09 at 22:21
12

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.

Pat
  • 16,515
  • 15
  • 95
  • 114
  • 1
    Ctrl + a is your friend. "I'm already in the middle of typing the mv command" so hit C-a and type. – ocodo Mar 12 '23 at 12:17
8

The simpliest way to do that is:

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

Let's suppose I downloaded pdf files located in my download directory (~/download) and I want to move all of them into a directory that doesn't exist (let's say my_PDF).

I'll type the following command (making sure my current working directory is ~/download):

mkdir my_PDF && mv *.pdf $_

You can add -p option to mkdir if you want to create subdirectories just like this: (supposed I want to create a subdirectory named python):

mkdir -p my_PDF/python && mv *.pdf $_
juzraai
  • 5,693
  • 8
  • 33
  • 47
Abdoul Seibou
  • 89
  • 1
  • 4
7

Making use of the tricks in "Getting the last argument passed to a shell script" we can make a simple shell function that should work no matter how many files you want to move:

# Bash only
mvdir() { mkdir -p "${@: -1}" && mv "$@"; }

# Other shells may need to search for the last argument
mvdir() { for last; do true; done; mkdir -p "$last" && mv "$@"; }

Use the command like this:

mvdir foo.c foo.h ~/some/new/folder/
sbolel
  • 3,486
  • 28
  • 45
Dean Serenevy
  • 1,284
  • 12
  • 13
5

rsync command can do the trick only if the last directory in the destination path doesn't exist, e.g. for the destination path of ~/bar/baz/ if bar exists but baz doesn't, then the following command can be used:

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)

In this case baz directory will be created if it doesn't exist. But if both bar and baz don't exist rsync will fail:

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]

So basically it should be safe to use rsync -av --remove-source-files as an alias for mv.

sepehr
  • 5,479
  • 3
  • 29
  • 33
3

The following shell script, perhaps?

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

That's the basic part. You might want to add in a bit to check for arguments, and you may want the behavior to change if the destination exists, or the source directory exists, or doesn't exist (i.e. don't overwrite something that doesn't exist).

sbolel
  • 3,486
  • 28
  • 45
Chris Lutz
  • 73,191
  • 16
  • 130
  • 183
  • 1
    With your code, the move isn't performed if the directory does not exist! – strager Feb 13 '09 at 21:21
  • 1
    And you missed the "-p" flag to mkdir. – dmckee --- ex-moderator kitten Feb 13 '09 at 21:26
  • If a directory doesn't exist, why create it if you're just going to move it? (-p flag fixed) – Chris Lutz Feb 13 '09 at 21:27
  • I mean, when you run the script and the directory $2 does not exist, it is created but the file is not copied. – strager Feb 13 '09 at 21:30
  • he gave a simple construct and added information on how it should be expanded. Why vote it down?! – ypnos Feb 13 '09 at 21:57
  • 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 Feb 13 '09 at 21:58
  • @ypnos: Because so far it doesn't do what it is supposed to? – dmckee --- ex-moderator kitten Feb 13 '09 at 22:00
  • And I haven't fixed it myself for pedagogical reasons. – dmckee --- ex-moderator kitten Feb 13 '09 at 22:01
  • 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'. – dmckee --- ex-moderator kitten Feb 13 '09 at 22:22
  • 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 Mar 01 '16 at 17:37
  • 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 ;). – ArchLinuxTux Mar 01 '16 at 17:46
2

Sillier, but working way:

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

Make the directory with mkdir -p including a temporary directory that is shares the destination file name, then remove that file name directory with a simple rmdir, then move your file to its new destination. I think answer using dirname is probably the best though.

  • 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. – Tylla Apr 08 '19 at 21:58
  • Thanks! This is exactly what I needed to accomplish: mv ./old ./subdir/new where subdir doesn't yet exist – Mike Murray Dec 18 '19 at 19:39
2

Code:

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

Example:

arguments: "d1" "d2/sub"

mkdir: created directory 'd2'
renamed 'd1' -> 'd2/sub'
sbolel
  • 3,486
  • 28
  • 45
John Doe
  • 373
  • 3
  • 4
2

This will move foo.c to the new directory baz with the parent directory bar.

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

The -p option to mkdir will create intermediate directories as required.
Without -p all directories in the path prefix must already exist.

Everything inside backticks `` is executed and the output is returned in-line as part of your command.
Since mkdir doesn't return anything, only the output of echo $_ will be added to the command.

$_ references the last argument to the previously executed command.
In this case, it will return the path to your new directory (~/bar/baz/) passed to the mkdir command.


I unzipped an archive without giving a destination and wanted to move all the files except demo-app.zip from my current directory to a new directory called demo-app.
The following line does the trick:
mv `ls -A | grep -v demo-app.zip` `mkdir -p demo-app && echo $_`

ls -A returns all file names including hidden files (except for the implicit . and ..).

The pipe symbol | is used to pipe the output of the ls command to grep (a command-line, plain-text search utility).
The -v flag directs grep to find and return all file names excluding demo-app.zip.
That list of files is added to our command-line as source arguments to the move command mv. The target argument is the path to the new directory passed to mkdir referenced using $_ and output using echo.

Evan Wunder
  • 131
  • 3
2

Based on a comment in another answer, here's my shell function.

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

Include this in .bashrc or similar so you can use it everywhere.

Ben Winding
  • 10,208
  • 4
  • 80
  • 67
2

i accomplished this with the install command on linux:

root@logstash:# myfile=bash_history.log.2021-02-04.gz ; install -v -p -D $myfile /tmp/a/b/$myfile

bash_history.log.2021-02-04.gz -> /tmp/a/b/bash_history.log.2021-02-04.gz

the only downside being the file permissions are changed:

root@logstash:# ls -lh /tmp/a/b/

-rwxr-xr-x 1 root root 914 Fev  4 09:11 bash_history.log.2021-02-04.gz

if you dont mind resetting the permission, you can use:

-g, --group=GROUP   set group ownership, instead of process' current group
-m, --mode=MODE     set permission mode (as in chmod), instead of rwxr-xr-x
-o, --owner=OWNER   set ownership (super-user only)
RASG
  • 5,988
  • 4
  • 26
  • 47
1
((cd src-path && tar --remove-files -cf - files-to-move) | ( cd dst-path && tar -xf -))
Pradeep
  • 9,667
  • 13
  • 27
  • 34
svaardt
  • 19
  • 1
1

I frequently stumble upon this issue while bulk moving files to new subdirectories. Ideally, I want to do this:

mv * newdir/  

Most of the answers in this thread propose to mkdir and then mv, but this results in:

mkdir newdir && mv * newdir 
mv: cannot move 'newdir/' to a subdirectory of itself

The problem I face is slightly different in that I want to blanket move everything, and, if I create the new directory before moving then it also tries to move the new directory to itself. So, I work around this by using the parent directory:

mkdir ../newdir && mv * ../newdir && mv ../newdir .

Caveats: Does not work in the root folder (/).

Orestis Kapar
  • 129
  • 1
  • 9
  • 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. – Orestis Kapar Nov 02 '20 at 13:08
1

There's a lot of conflicting solutions around for this, here's what worked for us:

## ss_mv ##
function ss_mv {
    mkdir -p $(dirname "$2") && mv -f "$@"
}

This assumes commands in the following syntax:

ss_mv /var/www/myfile /var/www/newdir/myfile

In this way the directory path /var/www/newdir is extracted from the 2nd part of the command, and that new directory is then created (it's critical that you use the dirname tag to avoid myfile being added to the new directory being created).

Then we go ahead and mv on the entire string again by using the "$@" tag.

Jesse Nickles
  • 1,435
  • 1
  • 17
  • 25
0

My one string solution:

test -d "/home/newdir/" || mkdir -p "/home/newdir/" && mv /home/test.txt /home/newdir/
Andrey
  • 175
  • 6
-1
$what=/path/to/file;
$dest=/dest/path;

mkdir -p "$(dirname "$dest")";
mv "$what" "$dest"
cab404
  • 152
  • 1
  • 5
-1

You can even use brace extensions:

mkdir -p directory{1..3}/subdirectory{1..3}/subsubdirectory{1..2}      
  • which creates 3 directories (directory1, directory2, directory3),
    • and in each one of them two subdirectories (subdirectory1, subdirectory2),
      • and in each of them two subsubdirectories (subsubdirectory1 and subsubdirectory2).

You have to use bash 3.0 or newer.

Mr.Wizard
  • 24,179
  • 5
  • 44
  • 125