I am trying to find all images in subfolders of a given folder, and move them somewhere else. I have tried the following in zsh (my default) and sh (what most tutorials seem to be using) on a Mac running OS X 10.9.3.
This prints out all the images in the subfolders of $someDir:
find "$someDir" -iname \*.jpg -o -name \*.png -o -name \*.gif
However, when I want to pass those images to another command, I can’t get it to work. As an exercise, I tried it with echo:
find "$someDir" -iname \*.jpg -o -name \*.png -o -name \*.gif -exec sh -c "echo hello {}" \;
It just returns silently, and the value of $? is 0.
I eventually want to do something along these lines:
find "$someDir" -iname \*.jpg -o -name \*.png -o -name \*.gif -exec sh -c "mv {} $destination" \;
But I can‘t even get the echo example to work. What am I doing wrong?
You need to put parentheses around all the name tests:
find "$someDir" \( -iname \*.jpg -o -name \*.png -o -name \*.gif \) -exec sh -c "echo hello {}" \;
Otherwise, the -exec is only done for files that match *.gif.
When you leave out the action, there's a default -print in each branch of the -o. But if there's any action option in the command, there's no default actions anywhere.
These should work (make sure $destination is defined)
find "$someDir" \( -iname \*.jpg -o -name \*.png -o -name \*.gif \) -exec echo hello {}
find "$someDir" \( -iname \*.jpg -o -name \*.png -o -name \*.gif \) -exec mv {} $destination \;
Related
I have a find command that finds files with name matching multiple patterns mentioned against the -name parameter
find -L . \( -name "SystemOut*.log" -o -name "*.out" -o -name "*.log" -o -name "javacore*.*" \)
This finds required files successfully at the command line. What I am looking for is to use this command in a shell script and join this with a tar command to create a tar of all log files. So, in a script I do the following:
LIST="-name \"SystemOut*.log\" -o -name \"*.out\" -o -name \"*.log\" -o -name \"javacore*.*\" "
find -L . \( ${LIST} \)
This does not print files that I am looking for.
First - why this script is not functioning like the command? Once it does, can I club it with cpio or similar to create a tar in one shot?
Looks like find fails to match * in patterns from unquoted variables. This syntax works for me (using bash arrays):
LIST=( -name \*.tar.gz )
find . "${LIST[#]}"
Your example would become the following:
LIST=( -name SystemOut\*.log -o -name \*.out -o -name \*.log -o -name javacore\*.\* )
find -L . \( "${LIST[#]}" \)
eval "find -L . \( ${LIST} \)"
You could use an eval and xargs,
eval "find -L . \( $LIST \) " | xargs tar cf 1.tar
When you have a long list of file names you use, you may want to try the following syntax instead:
# List of file patterns
Pat=( "SystemOut*.log"
"*.out"
"*.log"
"javacore*.*" )
# Loop through each file pattern and build a 'find' string
find $startdir \( -name $(printf -- $'\'%s\'' "${Pat[0]}") $(printf -- $'-o -name \'%s\' ' "${Pat[#]:1}") \)
That method constructs the argument sequentially using elements from a list, which tends to work better (at least in my recent experiences).
You can use find's -exec option to pass the results to an archiving program:
find -L . \( .. \) -exec tar -Af archive.tar {} \;
LIST="-name SystemOut*.log -o -name *.out -o -name *.log -o -name javacore*.*"
The wildcards are already quoted and you don't need to quote them again. Moreover, here
LIST="-name \"SystemOut*.log\""
the inner quotes are preserved and find will get them as a part of the argument.
Building -name list for find command
Here is a proper way to do this:
cmd=();for p in \*.{log,tmp,bak} .debug-\*;do [ "$cmd" ] && cmd+=(-o);cmd+=(-name "$p");done
Or
cmd=()
for p in \*.{log,tmp,bak,'Spaced FileName'} {.debug,.log}-\* ;do
[ "$cmd" ] && cmd+=(-o)
cmd+=(-name "$p")
done
You could dump you $cmd array:
declare -p cmd
declare -a cmd=([0]="-name" [1]="*.log" [2]="-o" [3]="-name" [4]="*.tmp" [5]="-o"
[6]="-name" [7]="*.bak" [8]="-o" [9]="-name" [10]="*.Spaced FileName"
[11]="-o" [12]="-name" [13]=".debug-*" [14]="-o" [15]="-name" [16]=".log-*")
Then now you could
find [-L] [/path] \( "${cmd[#]}" \)
As
find \( "${cmd[#]}" \)
(Nota: if no path is submited, current path . is default)
find /home/user/SomeDir \( "${cmd[#]}" \)
find -L /path -type f \( "${cmd[#]}" \)
I am writing a bash script to find image files and to copy them to a dir. My problem is when I use find it finds all files but when I add
-exec cp -n {} $2 \;
I know that I have set do not clobber but my test case takes this into account I also have a renaming mechanism in place later in the script that is tried and tested.
When I enter straight in terminal I get same problem
find Desktop -iname '*.JPG' -o -iname '*.jpeg' -o -iname '*.jf[il1]f' -o -iname '*.exif' -o -iname '*.tiff' -o -iname '*.gif' -o -iname '*.bmp' -o -iname '*.png'
I get (my test case 300 images in different dir with duplicates and same named file but for this problem simplified to these files on my desktop)
Desktop/IMG_0316.PNG
Desktop/IMG_0191.JPG
Desktop/IMG_0292.JPG
Desktop/IMG_0269.PNG
Desktop/IMG_0318.PNG
Desktop/IMG_0172.JPG
when I add cp command
find Desktop/ -iname '*.JPG' -o -iname '*.jpeg' -o -iname '*.jf[il1]f' -o -iname '*.exif' -o -iname '*.tiff' -o -iname '*.gif' -o -iname '*.bmp' -o -iname '*.png' -exec cp -n {} test \;
The only files I get in test are
Desktop/IMG_0316.PNG
Desktop/IMG_0269.PNG
Desktop/IMG_0318.PNG
I have tried removing -n from cp but made no diffrence
Thanks any help is appreciated
If I add echo I get
cp -n Desktop/IMG_0316.PNG test
cp -n Desktop/IMG_0269.PNG test
cp -n Desktop/IMG_0318.PNG test
Use parens
find Desktop/ \( -iname '*.JPG' -o -iname '*.jpeg' -o -iname '*.jf[il1]f' -o -iname '*.exif' -o -iname '*.tiff' -o -iname '*.gif' -o -iname '*.bmp' -o -iname '*.png' \) -exec cp -n {} test \;
Actually, -exec is an action while -iname is a predicate (a test in terms of man find). They aren't interchangeable at all. Default action is -print. So your expression means:
find Desktop -iname '*.JPG' -print \
-o -iname '*.jpeg' -print -o ... -o -iname -iname '*.png' -exec cp...
By using parens your group tests and the provide a single action for the entire group.
So I got this as an answer to a previous question as an answer for looking recursively through files in a directory and deleting the files and directories if found:
find \( -name ".git" -o -name ".gitignore" -o -name "Documentation" \) -exec rm -rf "{}" \;
There are two problems with this:
One:
find: `./adam.balan/AisisAjax/.git': No such file or directory
because of this error the rest of the script doesn't execute. Now I don't want to have to check for any of the files or folders. I don't care if they exist or not, I want to suppress the error on this.
The second is that I am also getting the error on a directory that needs to be excluded from this search: vendor/
find: `./vendor/adam.balan/AisisAjax/.git': No such file or directory
I do not want it searching vendor. I want it to leave vendor alone.
How do I solve these two problems? Suppression and ignoring.
The problem is that you're deleting a directory that find then tries to descend into. You can use -prune to prevent that:
find \( -name ".git" -o -name ".gitignore" -o -name "Documentation" \) -prune -exec rm -rf "{}" \;
To ignore all errors, you can use 2> /dev/null to squash the error messages, and || true to avoid set -e making your script exit:
find \( -name ".git" -o -name ".gitignore" -o -name "Documentation" \) -prune -exec rm -rf "{}" \; 2> /dev/null || true
Finally, to avoid descending any directory named 'vendor', you can use -prune again:
find -name vendor -prune -o \( -name ".git" -o -name ".gitignore" -o -name "Documentation" \) -prune -exec rm -rf "{}" \; 2> /dev/null || true
I have been searching for a while, but can't seem to get a succinct solution. I am trying to delete old files but excluding some subdirectories (passed via parm) and their child subdirecories.
The issue that I am having is that when the subdirectory_name is itself older than the informed duration (also passed via parm) the find command is including the subdirectory_name on the list of the find. In reality the remove won't be able to delete these subdirectories because the rm command default option is f.
Here is the find commmand generated by the script:
find /directory/ \( -type f -name '*' -o -type d \
-name subdirectory1 -prune -o -type d -name directory3 \
-prune -o -type d -name subdirectory2 -prune -o \
-type d -name subdirectory3 -prune \) -mtime +60 \
-exec rm {} \; -print
Here is the list of files (and subdirectories brought by the find command)
/directory/subdirectory1 ==> this is a subdreictory name and I'd like to not be included
/directory/subdirectory2 ==> this is a subdreictory name and I'd like to not be included
/directory/subdirectory3 ==> this is a subdreictory name and I'd like to not be included
/directory/subdirectory51/file51
/directory/file1 with spaces
Besides this -- the script works fine not bringing (excluding) the files under these 3 subdirectories:
subdirectory1, subdirectory2 and subdirectory3.
Thank you.
Following command will delete only files older than 1 day.
You can exclude the directories as shown in the example below, directories test1 & test2 will be excluded.
find /path/ -mtime +60 -type d \( -path ./test1 -o -path ./test2 \) -prune -o -type f -print0 | xargs -0 rm -f
Though it would be advisable to see what's going to be deleted using -print
find /path/ -mtime +60 -type d \( -path ./test1 -o -path ./test2 \) -prune -o -type f -print
find /directory/ -type d \(
-name subdirectory1 -o \
-name subdirectory2 -o \
-name subdirectory3 \) -prune -o \
-type f -mtime +60 -print -exec rm -f {} +
Note that the AND operator (-a, implicit between two predicates if not specified) has precedence over the OR one (-o). So the above is like:
find /directory/ \( -type d -a \(
-name subdirectory1 -o \
-name subdirectory2 -o \
-name subdirectory3 \) -a -prune \) -o \
\( -type f -a -mtime +60 -a -print -a -exec rm -f {} + \)
Note that every file name matches the * pattern, so -name '*' is like -true and is of no use.
Using + instead of ; runs fewer rm commands (as few as possible, and each is passed several files to remove).
Do not use that code above on directories writeable by others as it's vulnerable to attacks whereby the attacker can change a directory to a symlink to another one in between the time find traverses the directory and calls rm to have you delete any file on the filesystem. Can be alleviated by changing the -exec part with -delete or -execdir rm -f {} \; if your find supports them.
See also the -path predicate if you want to exclude a specific subdirectory1 instead of any directory whose name is subdirectory1.
I have a find command that I would like to sort such that entries for certain directories are sorted last. The reason is that this list is to be passed to etags to create a tags table and I would like certain third-party tool directories to be after all the code I actively edit.
Can someone suggest a good easy way in to sort the list as a change to my makefile rule? Here is the current rule:
tags:
rm -f ../TAGS
find .. \( -not -regex '.*include/.*' \) \
-a \( -name '*.h' -o -name '*.hh' -o -name '*.y' \
-o -name '*.l' -o -name '*.cc' -o -name '*.cpp' \
-o -name '*.c' -o -name '*.inl' \) \
| xargs etags -o ../TAGS --append
For example, entries that begin "../flexlm/" or "../src/librsync" should come after entries that don't match one of these patterns.
Put multiple find commands in a brace block and pipe that into xargs:
# the single quotes take care of the escaping
pattern='( -not -regex ".*include/.*" )
-a ( -name "*.h" -o -name "*.hh" -o -name "*.y"
-o -name "*.l" -o -name "*.cc" -o -name "*.cpp"
-o -name "*.c" -o -name "*.inl" )'
{
find ! -path "../flexlm/*" ! -path "../src/librsync/*" $pattern
find -path "../flexlm/*" $pattern
find -path "../src/librsync/*" $pattern
} | xargs etags -o ../TAGS --append
Well assuming you can afford to run multiple find queries and you have your project set up in such a way that it is possible to find your own source files with one query and any libraries with other queries...
... That'd be what I'd do.
Here is what worked for me by combining the above answers and tweaking them:
PATTERN := \( -not -regex '.*include/.*' \) \
-a \( -name '*.h' -o -name '*.hh' -o -name '*.y' \
-o -name '*.l' -o -name '*.cc' -o -name '*.cpp' \
-o -name '*.c' -o -name '*.inl' \)
.PHONY: tags
tags:
rm -f ../TAGS
find .. \
! -path "../src/librsync/*" \
! -path "../flexlm/*" \
$(PATTERN) | xargs etags -o ../TAGS --append
find .. -path "../src/librsync/*" \
$(PATTERN) | xargs etags -o ../TAGS --append
find .. -path "../flexlm/*" \
$(PATTERN) | xargs etags -o ../TAGS --append