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.)
This wildcard exists in ksh93, bash ≥4.3 (≥ 4.0 if there are no symbolic link to directories in your tree) and zsh. It's spelled **
. In ksh93, it needs to be activated first with set -o globstar
. In bash, it needs to be activated first with shopt -o globstar
.
ls -l src/**/foobar/**/*.java
This won't do to make the copy, though. The target of cp
is a single directory, cp
doesn't do any wildcard matching. You can't use a single cp
command to drop files in different places.
You can use rsync instead. Pass it the root of the source tree and the root of the destination tree, and define include and exclude rules to copy only the files you want and the directories leading to them. Rsync will copy empty directories as well if they match the pattern, you can make it delete them afterwards with --prune-empty-dirs
.
rsync --include='**/foobar/**/*.java' --include='**/' \
--exclude='*' --prune-empty-dirs \
src/ trusted/src/main/java/
Another tool you can use is pax. Note that pax is standard in that it is defined by POSIX (unlike rsync), but some Linux distributions omit it from the default installation (it's always available as a package however). The approach is similar to rsync: include .java
files, exclude the rest; the syntax is a bit stranger: you specify a pattern replacement, which can be the original to include a file and not rename it, or an empty replacement to exclude a file. Leading directories are automatically created in the destination as necessary.
pax -rw -s '/\.java$/&/' -s '/.*//' src/* trusted/src/main/java/
Best Answer
You can use
tar
orcpio
orpax
(if any of these is available) to copy certain files, creating target directories as necessary. With GNU tar, to copy all regular files called*.txt
orREADME.*
underneath the current directory to the same hierarchy under../destination
:With just
find
,cp
,mkdir
and the shell, you can loop over the desired files withfind
and launch a shell command to copy each of them. This is slow and cumbersome but very portable. The shell snippet receives the destination root directory as$0
and the path to the source file as$1
; it creates the destination directory tree as necessary (note that directory permissions are not preserved by the code below) then copies the file. The snippet below works on any POSIX system and most BusyBox installations.You can group the
sh
invocations; this is a little complicated but may be measurably faster.If you have bash ≥4 (I don't know whether Git Bash is recent enough), you don't need to call
find
, you can use the**
glob pattern to recurse into subdirectories.