32

I have a directory foo with several files:

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

and I want to move it into a directory with the same name:

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

I'm currently creating a temporary directory bar, move foo into bar and rename bar to foo afterwards:

mkdir bar
mv foo bar
mv bar foo

But this feels a little cumbersome and I have to pick a name for bar that's not already taken.

Is there a more elegant or straight-forward way to achieve this? I'm on macOS if that matters.

New contributor
Stefan is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
  • How do you feel about links? That is, do other non-sym-links exist linking to original foo? Do those links now need to point to foo/foo? – Harper - Reinstate Monica Dec 2 at 18:56
  • @Harper-ReinstateMonica indeed, there are some GUI applications having a link to foo (e.g. "recently opened files"). To keep those working, foo itself has to be moved, rather than just its content. (otherwise those links would refer to the new parent directory which isn't desired) – Stefan Dec 2 at 22:18
  • But are you sure these are plain links and not symlinks? Plain links increment the link count, symlinks do not. Plain links mean if the file is deleted, the file still lives in the other directory. – Harper - Reinstate Monica Dec 2 at 22:51
  • @Harper-ReinstateMonica those are not links in the file system. The applications store the file's unique ID which is based on its inode number. – Stefan Dec 3 at 5:54
  • mkdir foo/foo; mv foo/* foo/foo prints an error but still results in your expected outcome (at least on Ubuntu). Now: if you are interested on how inodes "move" then the result is not what you want (the original foo directory is not the inner foo so if you access via inode or something you will not find the expected structure). – Giacomo Alzetta Dec 3 at 11:38
20

To safely create a temporary directory in the current directory, with a name that is not already taken, you can use mktemp -d like so:

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

The mktemp -d command will create a directory at the given path, with the X-es at the end of the pathname replaced by random alphanumeric characters. It will return the pathname of the directory that was created, and we store this value in tmpdir.1

This tmpdir variable could then be used when following the same procedure that you are already doing, with bar replaced by "$tmpdir":

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

The unset tmpdir at the end just removes the variable.


1 Usually, one should be able to set the TMPDIR environment variable to a directory path where one wants to create temporary files or directories with mktemp, but the utility on macOS seems to work subtly differently with regards to this than the same utility on other BSD systems, and will create the directory in a totally different location. The above would however work on macOS. Using the slightly more convenient tmpdir=$(TMPDIR=$PWD mktemp -d) or even tmpdir=$(TMPDIR=. mktemp -d) would only be an issue on macOS if the default temporary directory was on another partition and the foo directory contained a lot of data (i.e. it would be slow).

  • Aside from the double copy, a different partition might also have insufficient space for the temporary file storage. – roaima Dec 1 at 15:33
  • 1
    @roaima Yes, but hopefully it was clear enough that the command that I actually show will create a unique name in the current directory. – Kusalananda Dec 1 at 16:29
  • 1
    Thanks for the example. I was playing with mktemp put couldn't find a way to create a temporary directory in the current working directory. Even though my TMPDIR is on the same partition, I don't want to move the files outside the current directory in case something goes wrong. Prefixing it with $PWD did the trick! – Stefan Dec 1 at 18:04
  • 2
    @Stefan Yes, this is an issue. See my question on the topic: mktemp on macOS not honouring $TMPDIR – Kusalananda Dec 1 at 18:06
10

On macOS, you can install the rename command (a Perl script) using Homebrew:

brew install rename

Then using the -p (a la mkdir) to have it make any necessary directories, and -A to add a prefix:

% 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

Run with -n to show changes without renaming (dry-run):

% 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'

If you have dot files, so that a simple * won't pick them up, then use other methods with rename:

  • With bash, (mis)use GLOBIGNORE to get * to match dot files:

    $ 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'
    
  • Or use find with -print0 and rename with -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'
    
  • What does the -p do on macOS? I don't have a -p option for my rename. Same for the -A, actually. What do they do? – terdon Dec 2 at 11:08
  • 1
    @terdon -p is like with mkdir - it creates missing directories. -a adds a suffix, -A adds a prefix. I assume your rename is one of the Perl renames on a Linux? – muru Dec 2 at 11:37
  • Yes, it's perl-rename but has neither a -p nor a -A option. Thanks for the edit. – terdon Dec 2 at 13:56
  • I didn't edit anything here. ¯\_(ツ)_/¯ Does your perl-rename report a version number? – muru Dec 2 at 14:37
  • Duh, so you didn't! My bad, I had missed that you had already explained these somehow, sorry! I have perl-rename 1.9. I suspect macOS just brings in one of the other ones: What's with all the renames: prename, rename, file-rename? – terdon Dec 2 at 14:55
9

As long as the contents are not sufficient to exceed the maximum parameter limits, (and you don't mind an "acceptable" error message) then it doesn't need to be any more complicated than this:

mkdir foo/foo
mv foo/* foo/foo

Amendment to handle hidden files:

mkdir foo/foo
mv foo/{.,}* foo/foo
  • 1
    Yes it has to be to handle hidden files: mv foo/* foo/.* foo/foo – Giacomo Alzetta Dec 3 at 11:44
  • 1
    mv foo/* foo does what? It appears to be moving stuff to where they were originally located. – Kusalananda Dec 3 at 13:08
  • 1
    @GiacomoAlzetta Better then to enable dotglob and nullglob (in bash) than trying to glob files that might not exist. – Kusalananda Dec 3 at 15:59
  • Sorry there was a typo missing the subdir off the target. Also added in handling for dot files as well. I concede it’s hacky, but the globs all exist — we just created subdirectory foo, and .. would match for dot files I think. – bxm Dec 3 at 16:37
8

I suggest the other way around. Don't move the directory, but only its content:

.
└── 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

If you have bash, you can also do

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

(as described here) to avoid running ls and grep.

The advantage of this solution is that it's really simple.

Disadvantages (as pointed out in the comments):

  • Cannot handle hidden files (ls -A fixes it, but not ls -a)
  • Cannot handle filenames containing spaces (can be fixed by quotes: mv "$(ls | grep -v '^foo$')" foo
  • Cannot handle filenames containing newlines and other special characters

Most disadvantages can be addressed by using some bash trick, but if one must handle crazy filenames, it's better to use more robust approach, as described in other answers.

  • 1
    You would need ls -A there (or ls -a and grep -v, if your ls does not hove -A) in order to move all the filenames starting with . . Then again, if you do have GNU coreutils, then mv will happily correctly handle trying to move directory into itself (with just a warning) when you try mv foo/* foo/foo – Matija Nalis Dec 2 at 0:10
  • 1
    Rather than your grep / mv -combination, I'd suggest using find. e.g.: find -mindepth 1 -maxdepth 1 ! -name 'foo' -print0 | xargs -0 -I mv '{}' foo. You might otherwise risk errors on filenames with e.g. spaces. – Fiximan Dec 2 at 9:37
  • 8
    This will miss any hidden files, as already pointed out, and will also fail on any file names with whitespace. Parsing ls is never a good idea. – terdon Dec 2 at 11:11
  • 1
    mv foo/* foo/.* foo/foo works like a charm. It does print an error but still produces the expected result. It should handle spaces, newlines etc and hidden files too. – Giacomo Alzetta Dec 3 at 11:43
  • 1
    Yes, mv * foo/ will print an error message, but it will also move the other args. I just tried it on my own Arch Linux system. That's exactly what @MatijaNalis said: correctly handle it with just a warning. – Peter Cordes 2 days ago
6

You've pretty much nailed it already. You could pick a different name for the transient directory, such as the target name with the current date/time in nanoseconds and our PID as a composite suffix, but this still presupposes the directory doesn't already exist:

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

If you want a guaranteed robust solution, I would loop around the mkdir until it was successful

now=$(date +'%s_%N')
while ! mkdir -m700 "$dir.$now.$$" 2>/dev/null
do
    sleep 0.$$
    now=$(date +'%s_%N')
done
  • I was hoping for a way to create an "unnamed" directory then move my directory into it and assign a name afterwards. Like a nameless inode, but I guess that doesn't exists. – Stefan Dec 1 at 13:42
  • @Stefan if you had a directory that was unnamed how would you reference it in the filesystem? – roaima Dec 1 at 15:03
  • Via it's inode number, e.g. find . -inum 123456 -exec mv {} foo \; would rename the file with inode number 123456 to foo. – Stefan Dec 1 at 16:46
  • 2
    @Stefan there's no way to access a file directly by its inode number; if that were possible, it would break completely break the security model. If creating another temporary dir in the parent dir of foo is complicated, you can restructure the foo dir in place: mkdir foo/foo; mv foo/* foo/foo (for the last part, you can ignore the error, or use extended globbing to skip the foo/foo subdir itself). – mosvy Dec 1 at 17:17
  • 2
    @Stefan your find references (uses) a directory by its name, where its identifying criterion is the inode number. – roaima Dec 1 at 17:27
5
mkdir foo/foo && mv foo/!(foo) foo/foo

You need to cd into the directory where the source folder (foo) is.

Then run the command above. It will create a folder called of the same name and move the contents of the parent foo into the child foo directory (except the child directory, hence the ! designation).

If the child foo directory already exists you can ignore the first part and just run the mv command:

mv foo/!(foo) foo/foo

In MacOS you may need to set the extglob option:

shopt -s extglob
  • Explanation: In Bash, !(foo) is like * except it won't match foo. It's a glob expression. Does it match dotfiles, though? Getting that right is one downside to the move-all-the-files approach. – Peter Cordes 2 days ago
2

In addition to the suggestions above, you might want to checkout rsync. Before I even start, always remember to use the --dry-run option with rsync before running it without it. The --dry-run will tell you what would happen in real life.

rsync has many options that can not only help copy/move files, but also speed up the process. Here is one way to get your job done. The --remove-source-filesoption deletes files from the source after the move process (which is actually a copy followed by a delete). Notice that the rsync command first reads the contents of foo, creates the directory foo within the existing directory called foo, then starts the copy process. It ends with deleting the source files in foo. Caution: Pay particular attention to the slashes for the directory names.

There are other considerations as many have pointed above (like symlinks), but a man rsync will help you pick the options that you need. A note here: Since you have asked for a solution for files, this will work. This will not remove empty directories that will be left behind. I am not aware of a way to do so without an additional step, so if anyone can add to this, thanks in advance!

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

a = archive mode

v = verbose

2

Simply don't touch foo and you don't have issues with identical names. Instead, create a directory with the same name inside it and move everything there. Use the $_ trick for that and && to make it a one-liner:

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

This will throw a harmless error (mv: rename foo to foo/foo: Invalid argument) that you can ignore.

Advantage over other answers: You only need to type the directory name once, so you can't make mistakes with typos.


you can mix this with other answers for even better effect:

  • mv {.,}* $_ will take care of hidden files (at the cost of more errors that you can also ignore)
  • mv ./!(foo) will avoid the error message, but only if you set shopt -s extglob
  • IMHO, in would be better to use the dotglob shell option and use that with the extglob variation (if there is a destination directory, you left it out). – Kusalananda 2 days ago
  • Another disadvantage is that it doesn't preserve foo's permissions or extended attributes (e.g. folder color or custom icon in macOS). Those would stick to the outer foo. – Stefan 2 days ago
  • @Stefan that's true, but it wasn't requested in the question. – Tom 2 days ago
  • Well, in the question I said that I want to move a directory foo into a directory with the same name. That implies that the original directory becomes the inner one. – Stefan yesterday

Your Answer

Stefan is a new contributor. Be nice, and check out our Code of Conduct.

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.