27

I have a directory that I am trying to clean out that contains both files and subdirectories. What I am trying to do is simple: move all the files into another directory, but leave all the sub-directories as they are.

I am thinking something like:

mv [*_but_no_dirs] ./other_directory

It seems like there should be a simple way to do this with wildcards * and regex...

Anyone have ideas?

3
  • 1
    So move all the files in a subfolder, but ignore any in the subdirectories?
    – Wilf
    Jul 29, 2014 at 18:37
  • @Wilf: Exactly. Jul 29, 2014 at 18:38
  • 2
    In zsh, you could do mv **/*(.) ./other_directory - with bash you'd need to resort to external commands like find, though.
    – godlygeek
    Jul 29, 2014 at 18:47

10 Answers 10

19

Regex aren't involved here. Wildcards in bash (like most other shells) only match files based on the file names, not based on the file type or other characteristics. There is one way to match by type: adding / at the end of the pattern makes it only match directories or symbolic links to directories. This way, you can move directories, then move what's left, and move directories back — cumbersome but it works.

tmp=$(TMPDIR=.. mktemp -d)
mv -- */ "$tmp"
mv -- * other_directory/
mv "$tmp"/* .
rmdir "$tmp"

(that approach should be avoided if the current directory is the mount point of a filesystem, as that would mean the moving of directories away and back would have to copy all the data in there twice).

A standard way to match files by type is to call find.

find . -name . -o -type d -prune -o -exec sh -c 'mv "$@" "$0"' other_directory/ {} +

(also moves symlinks, whether they point to directories or not).

In zsh, you can use glob qualifiers to match files by type. The . qualifier matches regular files; use ^/ to match all non-directories, or -^/ to also exclude symbolic links to directories.

mv -- *(.) other_directory/

In any shell, you can simply loop.

for x in *; do
   if [ ! -d "$x" ]; then
     mv -- "$x" other_directory/
   fi
done

(does not move symlinks to directories).

1
  • The first approach could also fail if some of the directories are mount points or are not owned by you (even if you have write access to the current working directory). Also note that mktemp is not a standard command. Aug 17, 2020 at 8:19
15

You could use something like

find . -maxdepth 1 \( ! -type d \) -exec sh -c 'mv  "$@" MYDIR' _ {} \;

First we use find to look only within the current directoy, then we ignore directories by using ! -type d finally we execute sh and move everything to the destination dir. You might try {} + at the end which will be faster.

4
  • 1
    Why do you suggest -exec sh -c 'mv "$@" MYDIR' _ {} \; rather than -exec mv {} MYDIR \;? Just to get something that can be streamlined by changing the \; to a +? You can do that with the -exec mv -t MYDIR {} + form. Jul 29, 2014 at 19:20
  • @Scott I had no idea about the -t flag. I also explained in the answer that the + is faster but not all find versions support it. So the above code is pretty much compatible with any find or shell that is used. Jul 29, 2014 at 19:58
  • 3
    @val0x00ff: “…the above code is pretty much compatible with any find or shell that is used.” True, but so is find … -exec mv {} MYDIR \;, and that uses less resources than … -exec sh -c 'mv "$@" MYDIR' _ {} \;. Jul 29, 2014 at 20:14
  • @Scott is right; this is an odd command. + is specified in POSIX anyway.
    – Wildcard
    Dec 3, 2016 at 1:17
7

It's potentially a bit dangerous but

cp * destination/
rm *

As both cp and rm won't operate on directories without the -r switch.

2
  • 7
    Clever, but surely living on the edge
    – Balu
    Jun 19, 2020 at 14:14
  • 2
    That has several issues: (1) it copies the files data, so if destination is on the same file system, it's going to be a lot less efficient than mv that just does a rename. (2) if fails if there are filenames starting with - (or have bad consequences, think of a -t.. file for instance. (3) for symlinks, it copies the target (provided it's a directory) instead of the symlink. (4) it doesn't preserve metadata (5) it breaks hard links... Aug 17, 2020 at 8:00
4

Here is a simple 1 line command to achieve what you are trying to do:

find ./sourceDir -maxdepth 1 -type f | xargs mv -t ./targetDir

Explanation

  1. Find all the files you want at depth 1 only, that are of type files. You can find explanation here.
    https://linux.die.net/man/1/find

  2. Pipe that using "| xargs" to mv.
    https://linux.die.net/man/1/xargs

  3. tell mv where to move the files
    https://linux.die.net/man/1/mv

This 1 liner is fairly portable, and can be run from anywhere in the system as long as you have the sourceDir and targetDir, either as full paths or relative paths.

1
  • 2
    You might want to use xargs -d '\n' to support filenames with spaces. Jun 2, 2023 at 12:12
1

mv $(find /my/source/directory -maxdepth 1 -type f) /my/destination/directory

Move will take a list of arguments, so when you ask find to give you everything that is of type "file" within /my/source/directory, it will move them one by one to /my/destination/directory. If you wanted to include the files in the subdirectories, too, while leaving the directory structure intact, remove -maxdepth 1 from the above command.

1
find . -maxdepth 1 -type f|awk '{ gsub( "\40", "\\\40"); print}' | xargs -I{} mv {} /path/to/dest/directory/{}

Please ensure you are in the current directory where your files to be moved are present while executing the command.

find . -maxdepth 1 -type f will find all the files in the folder .

awk '{ gsub( "\40", "\\\40"); print}' will replace "space" ( ) with "\space" (\ ) . \40 is the octal for "space". This ensures that files with spaces in their names are also included.

xargs -I{} mv {} /path/to/dest/directory/{} will capture the piped output as an argument and then {} will substitute the argument value i.e., filenames from previous command in {} of mv {} /path/to/dest/directory/{} source and destination arguments.

This command will ensure that files that has spaces in their names are moved as well.

1
  • 1
    I have removed the extra brackets. I was trying out individual commands within bracket since I was also learning the individual command behaviour. May 23, 2022 at 15:56
-1

I would suggest simply doing mv *.* destination/ with destination/ being the folder you are moving to.

4
  • 2
    check out: unix.stackexchange.com/help/formatting
    – Jeff Schaller
    Dec 3, 2016 at 1:41
  • 2
    Directory names can contain 'dot' and filenames can exclude it (and often do) so *.* will move some directories and leave some files, which is not what the asker wanted. Dec 3, 2016 at 5:18
  • Sorry but this is completely wrong. It will simply move anything with a dot in its name and not move anything without one. Dots are in no way exclusive to files and they're not required by files either, so this will just move a random selection of files and directories.
    – terdon
    Apr 14, 2017 at 16:25
  • maybe not ideal, but is good enough for me!... though why didn't I think of this.
    – fei0x
    Sep 9, 2021 at 19:10
-1
if [ -f "$PATH/$FILE_NAME" ]; then mv "$PATH/$FILE_NAME" "$OTHER_PATH/$FILE_NAME"; fi;
1
-2

If you don't intend to move dotfiles (e.g. ".foobar"), too, you can simply do:

mv * /some/destination/

If you do, see the above answer with find's -maxdepth.

1
  • 2
    The question says “move all the files …, but leave all the sub-directories as they are.”  This will move everything.   When you’re adding a new answer to a six-year-old question that already has seven answers, you might want to glance at them to see whether you have totally overlooked a key condition of the question. Aug 17, 2020 at 4:42
-3
mv $(ls -l | grep "^-" | awk '{print $9}') ./folder_where_move
1

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .