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.)
One of your problems is that you left out the double quotes around the command substitution, so the output from the date
command was split at spaces. See Why does my shell script choke on whitespace or other special characters? This is a valid command:
cp -a /home/bpacheco/Test1 "/home/bpacheco/Test2-$(date +"%m-%d-%y-%r")"
If you want to append to the original file name, you need to have that in a variable.
source=/home/bpacheco/Test1
cp -a -- "$source" "$source-$(date +"%m-%d-%y-%r")"
If you're using bash, you can use brace expansion instead.
cp -a /home/bpacheco/Test1{,"-$(date +"%m-%d-%y-%r")"}
If you want to copy the file to a different directory, and append the timestamp to the original file name, you can do it this way — ${source##*/}
expands to the value of source
without the part up to the last /
(it removes the longest prefix matching the pattern */
):
source=/home/bpacheco/Test1
cp -a -- "$source" "/destination/directory/${source##*/}-$(date +"%m-%d-%y-%r")"
If Test1
is a directory, it's copied recursively, and the files inside the directory keep their name: only the toplevel directory gets a timestamp appended (e.g. Test1/foo
is copied to Test1-05-10-15-07:19:42 PM
). If you want to append a timestamp to all the file names, that's a different problem.
Your choice of timestamp format is a bad idea: it's hard to read for humans and hard to sort. You should use a format that's easier to read and that can be sorted easily, i.e. with parts in decreasing order of importance: year, month, day, hour, minute, second, and with a separation between the date part and the time part.
cp -a /home/bpacheco/Test1 "/home/bpacheco/Test2-$(date +"%Y%m%d-%H%M%S")"
cp -a /home/bpacheco/Test1 "/home/bpacheco/Test2-$(date +"%Y-%m-%dT%H%M%S%:z")"
Best Answer
You can use bash's string substitution features for that:
The general format is
${string%substring}
which will removesubstring
from the end ofstring
. For example: