Using operators in "find" - macos

I apologise if this had already been covered before. I am learning find on my OSX Mavericks. I am not quite sure I fully understand the operators.
My folder has 4 files:
123.123
123.abc
abc.123
abc.abc
When I try to "OR" -o operator:
find . \( -name "*.123" -o -name "123.*" \) -print
Output is as expected:
./123.123
./123.abc
./abc.123
But when I take away the bracket / parentheses, i.e.:
find . -name "*.123" -o -name "123.*" -print
... only ./123.abc is printed.
Why....??? I really don't understand how the command is being interpreted by the computer here.
My observation is that ./123.abc fits the second argument (123.*), but not the first (*.123). So it seems somehow, the use of -o before the second argument without brackets lead to the first argument behaving as if it had a "NOT" (!) operator.

Taking bracket out makes -print action execute for this condition only:
-name "123.*"
Which prints: ./123.abc

This is a question of operator precedence between the "and" and "or" operators.
find . -name "*.123" -o -name "123.*" -print
is in fact
find . -name "*.123" -o -name "123.*" -a -print
As and has higher priority, this is interpreted as:
find . -name "*.123" -o \( -name "123.*" -a -print \)
If you want your expected behaviour without parenthesis, write that:
sh$ find . -name "*.123" -o -name "123.*"
./abc.123
./123.abc
./123.123
Here the "print" action is implied, and not subject to grouping.
If you want the full-verbose equivalent, you could write:
sh$ find . -name "*.123" -a -print -o -name "123.*" -a -print
./abc.123
./123.abc
./123.123
Which is interpreted as:
find . \( -name "*.123" -a -print \) -o \( -name "123.*" -a -print \)

Related

Need to find files with multiples arguments in bash

I've done this atm, I need to find in the main directory and in the sub-directory everything starting with the letter 'a', every files ending with 'z' and every files starting with 'z' and ending with 'a!'.
find . -name "a*" | find . "*z" -type f | find . "z*a!" -type f
I tried to be as clear as possible, sorry if it wasn't clear enough.
find . -type f \( -name 'a*' -or -name '*z' -or -name 'z*a!' \)
Use -o instead of -or for POSIX compliance.
If you really want to also find links, directories, pipes etc. starting with a but only files matching the remaining conditions, you can do
find . -name 'a*' -or -type f \(-name '*z' -or -name 'z*a!' \)
TL;DR
find . -name 'a*' -o -type f \( -name '*z' -o -name 'z*a!' \)
Explanations:
The find logical operators are -a (AND) and -o (OR). You use them to combine elementary tests. Note that because of operator's precedence you sometimes need parentheses and that they must be escaped (with \) to prevent their interpretation by the shell. Your test is:
everything starting with the letter 'a': -name 'a*'.
every files ending with 'z': -type f -a -name '*z'.
every files starting with 'z' and ending with 'a!': -type f -a -name 'z*a!'.
So the complete test could be:
-name 'a*' -o \( -type f -a -name '*z' \) -o \( -type f -a -name 'z*a!' \)
As -a is the default we can omit it, and as -type f (file) is common to the two last terms of the disjunction we can factor it:
-name 'a*' -o -type f \( -name '*z' -o -name 'z*a!' \)

Bash: Find command with multiple -name variable [duplicate]

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[#]}" \)

find and delete folder and/or zip file in a directory [duplicate]

I was trying to get a list of all python and html files in a directory with the command find Documents -name "*.{py,html}".
Then along came the man page:
Braces within the pattern (‘{}’) are not considered to be special (that is, find . -name 'foo{1,2}' matches a file named foo{1,2}, not the files foo1 and foo2.
As this is part of a pipe-chain, I'd like to be able to specify which extensions it matches at runtime (no hardcoding). If find just can't do it, a perl one-liner (or similar) would be fine.
Edit: The answer I eventually came up with include all sorts of crap, and is a bit long as well, so I posted it as an answer to the original itch I was trying to scratch. Feel free to hack that up if you have better solutions.
Use -o, which means "or":
find Documents \( -name "*.py" -o -name "*.html" \)
You'd need to build that command line programmatically, which isn't that easy.
Are you using bash (or Cygwin on Windows)? If you are, you should be able to do this:
ls **/*.py **/*.html
which might be easier to build programmatically.
Some editions of find, mostly on linux systems, possibly on others aswell support -regex and -regextype options, which finds files with names matching the regex.
for example
find . -regextype posix-egrep -regex ".*\.(py|html)$"
should do the trick in the above example.
However this is not a standard POSIX find function and is implementation dependent.
You could programmatically add more -name clauses, separated by -or:
find Documents \( -name "*.py" -or -name "*.html" \)
Or, go for a simple loop instead:
for F in Documents/*.{py,html}; do ...something with each '$F'... ; done
This will find all .c or .cpp files on linux
$ find . -name "*.c" -o -name "*.cpp"
You don't need the escaped parenthesis unless you are doing some additional mods. Here from the man page they are saying if the pattern matches, print it. Perhaps they are trying to control printing. In this case the -print acts as a conditional and becomes an "AND'd" conditional. It will prevent any .c files from being printed.
$ find . -name "*.c" -o -name "*.cpp" -print
But if you do like the original answer you can control the printing. This will find all .c files as well.
$ find . \( -name "*.c" -o -name "*.cpp" \) -print
One last example for all c/c++ source files
$ find . \( -name "*.c" -o -name "*.cpp" -o -name "*.h" -o -name "*.hpp" \) -print
I had a similar need. This worked for me:
find ../../ \( -iname 'tmp' -o -iname 'vendor' \) -prune -o \( -iname '*.*rb' -o -iname '*.rjs' \) -print
My default has been:
find -type f | egrep -i "*.java|*.css|*.cs|*.sql"
Like the less process intencive find execution by Brendan Long and Stephan202 et al.:
find Documents \( -name "*.py" -or -name "*.html" \)
Braces within the pattern \(\) is required for name pattern with or
find Documents -type f \( -name "*.py" -or -name "*.html" \)
While for the name pattern with and operator it is not required
find Documents -type f ! -name "*.py" -and ! -name "*.html"
#! /bin/bash
filetypes="*.py *.xml"
for type in $filetypes
do
find Documents -name "$type"
done
simple but works :)
I needed to remove all files in child dirs except for some files. The following worked for me (three patterns specified):
find . -depth -type f -not -name *.itp -and -not -name *ane.gro -and -not -name *.top -exec rm '{}' +
This works on AIX korn shell.
find *.cbl *.dms -prune -type f -mtime -1
This is looking for *.cbl or *.dms which are 1 day old, in current directory only, skipping the sub-directories.
find MyDir -iname "*.[j][p][g]"
+
find MyDir -iname "*.[b][m][p]"
=
find MyDir -iname "*.[jb][pm][gp]"
What about
ls {*.py,*.html}
It lists out all the files ending with .py or .html in their filenames

"find: bad option -name "*.user"" while passing variables to find

In a bash script this fails:
fileloc='/var/adm/logs/morelogs'
filename=' -name "*.user"'
fileList="$(find "$fileloc"/* -type f -prune "$filename" -print)"
find: bad option -name "*.user"
find: [-H | -L] path-list predicate-list
but this works:
find /var/adm/logs/morelogs/* -type f -prune -name "*.user" -print
in the same manner:
this fails:
fileloc='/var/adm/logs/morelogs'
filename='\( -name "admin.*" -o -name "*.user" -o -name "*.user.gz" \)'
fileList="$(find "$fileloc"/* -type f -prune "$filename" -print)"
find: bad option \( -name "admin.*" -o -name "*.user" -o -name "*.user.gz" \)
find: [-H | -L] path-list predicate-list
but this works:
find /var/adm/logs/morelogs/* -type f -prune \( -name "admin.*" -o -name "*.user" -o -name "*.user.gz" \) -print
GNU bash, version 3.00.16(1)-release-(sparc-sun-solaris2.10)
This is usecase when you should use BASH arrays or BASH function.
Using BASH arrays:
#!/bin/bash
# initialize your constants
fileloc='/var/adm/logs/morelogs'
filename='*.user'
# create an array with full find command
cmd=( find "$fileloc" -type f -prune -name "$filename" -print )
# execute find command line using BASH array
"${cmd[#]}"
It sounds like you're trying to build the list of names to search for dynamically -- if this is the case, a variant of #anubhava's answer using the array for just the name patterns is the best approach:
namepatterns=() # Start with no filenames to search for
while something; do
newsuffix="whatever"
namepatterns+=(-o -name "*.$newsuffix")
done
# Note that "${namepatterns[#]}" is not quite what we want to pass to find, since
# it always starts with "-o" (unless it's empty, in which case this'll have other
# problems). But "${namepatterns[#]:1}" leaves off the first element, and gets us
# what we need.
fileList="$(find "$fileloc"/* -type f -prune "(" "${namepatterns[#]:1}" ")" -print)"
Other notes: I second #BroSlow's recommendation to read BashFAQ #50: I'm trying to put a command in a variable, but the complex cases always fail!, and also you're going to have trouble using that filelist variable if any of the filenames contain funny characters (esp. whitespace and wildcards) -- see BashFAQ #20: How can I find and safely handle file names containing newlines, spaces or both? (short answer: arrays are better for this as well!)
Lets see what are you doing with set -x:
$ fileloc='/var/adm/logs/morelogs'
+ fileloc=/var/adm/logs/morelogs
$ filename=' -name "*.user"'
+ filename=' -name "*.user"'
Everything seems fine, now, next line:
$ fileList="$(find "$fileloc"/* -type f -prune "$filename" -print)"
++ find '/var/adm/logs/morelogs/*' -type f -prune ' -name "*.user"' -print
find: paths must precede expression: -name "*.user"
Usage: find [-H] [-L] [-P] [-Olevel] [-D help|tree|search|stat|rates|opt|exec] [path...] [expression]
+ fileList=
I think you see the problem, if you execute find '/var/adm/logs/morelogs/*' -type f -prune ' -name "*.user"' -print it will throw you an error:
$ find '/var/adm/logs/morelogs/*' -type f -prune ' -name "*.user"' -print
find: paths must precede expression: -name "*.user"
Usage: find [-H] [-L] [-P] [-Olevel] [-D help|tree|search|stat|rates|opt|exec] [path...] [expression]
What's happening? Well, there's a bunch of single quotes that are in the way, but the one that causes problems is the two lasts, before -name and -print, which cause find to see it as a single parameter, the other can be ignored. So, how to fix this? Don't use double quotes to ask for the $filename variable:
$ find "$fileloc" -type f -prune $filename -print
+ find /var/adm/logs/morelogs -type f -prune -name '*.user' -print
That should solve it.
not an answer to problem, but a poor solution. After getting frustrated, i just hard-coded the search to have full options list.
so it looks like this now: and it works. i had to build some cases, and repeat myself - not a good programming practice, but i was tired of this shell ting....
so for example one option looks like:
fileList="$(find "$fileloc"/* -type f -prune \( -name "admin.*" -o -name "*.user" -o -name "*.user.gz" \) -print)"

Or condition in bash pattern

I'm searching some files with: find . -name "*.en.php" and find . -name "*.fr.php".
I want both commands in the same line, something like : find . -name "*.(en|fr).php" but it doesn't work.
Thanks in advance for your help.
EDIT
my command is like this : find . -not -path Config -name "*.fr.php", is there a solution do not repeat -not -path Config ?
Try:
find -name "*.en.php" -o -name "*.fr.php"
If you for example want to run command on each found file, than you need to additional ()
(this will count num of lines in all found files):
find \( -name "*.en.php" -o . -name "*.fr.php" \) -exec cat {} \; | wc -l
You should be able to combine expression with an or operator, thus:
find . -name '*.en.php' -o -name '*.fr.php' ...
You can see all the operators in the man page listed under OPERATORS (and, or, not, parentheses and so forth).
Use the find -o operator, eg.
find . -name "*.en.php" -o -name "*.fr.php"
Edit:
Like so:
find . -path './Config' -prune -o \( -name "*.en.php" -o -name "*.fr.php" \)
The default operator in find (if one is ommited) is and, the parentheses group the name expression. I've added -prune to prevent find from recursing into the Config directory.

Resources