Bash copy all files that don't match the given extensions

You can use find to find all files in a directory tree that match (or don't match) some particular tests, and then to do something with them. For this particular problem, you could use:

find -type f ! \( -iname '*.png' -o -iname '*.gif' -o -iname '*.jpg' -o -iname '*.xcf' \) -exec echo mv {} /new/path \;

This limits the search to regular files (-type f), and then to files whose names do not (!) have the extension *.png in any casing (-iname '*.png') or (-o) *.gif, and so on. All the extensions are grouped into a single condition between \( ... \). For each matching file it runs a command (-exec) that moves the file, the name of which is inserted in place of the {}, into the directory /new/path. The \; tells find that the command is over.

The name substitution happens inside the program-execution code, so spaces and other special characters don't matter.

If you want to do this just inside Bash, you can use Bash's extended pattern matching features. These require that shopt extglob is on, and globstar too. In this case, use:

mv **/!(*.[gG][iI][fF]|*.[pP][nN][gG]|*.[xX][cC][fF]|*.[jJ][pP][gG]) /new/path

This matches all files in subdirectories (**) that do not match *.gif, *.png, etc, in any combination of character cases, and moves them into the new path. The expansion is performed by the shell, so spaces and special characters don't matter again.

The above assumes all files are in subdirectories. If not, you can repeat the part after **/ to include the current directory too.

There are similar features in zsh and other shells, but you've indicated you're using Bash.

(A further note: parsing ls is never a good idea - just don't try it.)

You use Ubuntu, so you will have GNU find, try:

find . -maxdepth 1 ! -iregex ".*\.\(jpg\|png\|gif\|xcf\)$" -exec mv -- -t /path/to/newdir "{}" +
  • -iregex use regex to find filename, but case insensitive, ! negates the regex.

  • -exec command + run command for files matching. It's like using find -print0 with xargs -0. Using this we can move multiple files found with one mv command, so the total number of invocation command is less than the number of matched files.

  • -maxdepth 1 limit find to search only in current directory, if you want recursive, remove it.