find command how to combine OR and AND - linux

I would like to find all php and js files inside a directory and exclude one of sub directory.
I may have to exclude more than one sub directory in the future.
I tried :
find /home/jul/here -type f -iname "*.php" -o -iname "*.js" ! -path "/home/jul/here/exclude/*"
Problem is that it is excluding only js file from /home/jul/here/exclude.
Is there a way to put some kind of parentheses?
find (something OR something else) AND exclude THIS

find /home/jul/here -type f \( -iname "*.php" -o -iname "*.js" \) ! -path "/home/jul/here/exclude/*"

You need to add the exclusion pattern after each group of files. So something like this should work:
find /home/jul/here -type f -iname "*.php" ! -path "/home/jul/here/exclude/*" -o -iname "*.js" ! -path "/home/jul/here/exclude/*"
Or maybe better with a variable:
EXCLUDE=/home/jul/here/exclude
find /home/jul/here -type f -iname "*.php" ! -path "$EXCLUDE/*" -o -iname "*.js" ! -path "$EXCLUDE/*"

Related

Linux find -type f ignored

I'm creating a cron job, that will find all *.log* files (it will be used to remove them later, when it works).
The find command looks like this:
find /data/dg \( -path /data/dg/kf/data -o -path /data/dg/pg/data \) -prune -o -name "*.log*" -type f
And it should find all files with name ".log" that are not in directories /data/dg/kf/data and /data/dg/pg/data
However the output this command gives contains also the directories.
...
/data/dg/kf/log/controller.log.2019-09-08-22
/data/dg/kf/log/server.log.2019-09-09-07
/data/dg/kf/data
/data/dg/pg/log/postgresql-2019-09-27_000000.log
/data/dg/pg/log/postgresql-2019-09-27_100859.log
/data/dg/pg/log/postgresql-2019-09-27_102411.log
/data/dg/pg/data
/data/dg/sim/log/sim_2019-09-27-11.0.log
/data/dg/sim/log/sim_2019-09-27-12.0.log
...
It seems that -type f doesn't work. What's wrong?
put -type f right after /data/dg
find /data/dg -type f -not -path "/data/dg/kf/data*" -not -path "/data/dg/pg/data*" -name "*.log*"

How to remove files without certain extension?

How to remove all files without the .txt and .exe extensions recursively in the current working directory? I need a one-liner.
I tried:
find . ! -name "*.txt" "*.exe" -exec rm -r {} \
find -type f -regextype posix-extended -iregex '.*\.(txt|exe)$'
Try this.
find . -type f ! -name "*.exe" ! -name "*.txt" -exec rm {} \;
The above command will remove all the files other than the .exe and .txt extension files in the current directory and sub directory recursively.
If you have GNU find with the -delete action:
find . -type f ! \( -name '*.txt' -o -name '*.exe' \) -delete
And if not:
find . -type f ! \( -name '*.txt' -o -name '*.exe' \) -exec rm -f {} +
using -exec ... {} + to execute rm as few times as possible, with the arguments chained.
Try the following:
rm -f $(find . -type f ! \( -name "*.txt" -o -name "*.exe" \))
This will first recursively find all files that do not end with .txt or .exe extensions, and then delete all of these files.

'Sed' not working on result of 'find' with multiple parameters

I'm trying to do a find and replace function, finding files which match a criteria then find/replace text within them.
Find statement (works find and returns list of files):
find / -type f -name "*.properties" -o -name "*.xml" -not \( -path '/tmp/*' -o -path '/var/tmp/*' \)
Sed find/replace:
sed -i 's/find/replace/g' {} \;
Putting together:
find / -type f -name "*.properties" -o -name "*.xml" -not \( -path '/tmp/*' -o -path '/var/tmp/*' \) -exec sed -i 's/10\.32\.19\.156/10.32.19.165/g' {} \;
However this does not seem to work. Removing some 'find' parameters causes it to work, for example this works:
find / -type f -name "*.properties" -exec sed -i 's/10\.32\.19\.156/10.32.19.165/g' {} \;
How can I get sed to work with the extended 'find' parameters?
Currently these two 'find' statements return exactly the same result in a test folder with only 2 files:
find /var/tmp/ipreplace/ -type f -name "*.properties"
find /var/tmp/ipreplace/ -type f -name "*.properties" -o -name "*.xml" -not \( -path '/tmp/*' -o -path '/var/tmp/*' \)
I guess the use of -path parameter in your find command is wrong.
Try the following:
find / -not \( -path '/tmp' -prune \) -not \( -path '/var/tmp' -prune \) -type f -name "*.properties" -o -name "*.xml" -exec sed -i 's/10\.32\.19\.156/10.32.19.165/g' {} \;
Look at this post for reference

Use find command but exclude files in two directories

I want to find files that end with _peaks.bed, but exclude files in the tmp and scripts folders.
My command is like this:
find . -type f \( -name "*_peaks.bed" ! -name "*tmp*" ! -name "*scripts*" \)
But it didn't work. The files in tmp and script folder will still be displayed.
Does anyone have ideas about this?
Here's how you can specify that with find:
find . -type f -name "*_peaks.bed" ! -path "./tmp/*" ! -path "./scripts/*"
Explanation:
find . - Start find from current working directory (recursively by default)
-type f - Specify to find that you only want files in the results
-name "*_peaks.bed" - Look for files with the name ending in _peaks.bed
! -path "./tmp/*" - Exclude all results whose path starts with ./tmp/
! -path "./scripts/*" - Also exclude all results whose path starts with ./scripts/
Testing the Solution:
$ mkdir a b c d e
$ touch a/1 b/2 c/3 d/4 e/5 e/a e/b
$ find . -type f ! -path "./a/*" ! -path "./b/*"
./d/4
./c/3
./e/a
./e/b
./e/5
You were pretty close, the -name option only considers the basename, where as -path considers the entire path =)
Use
find \( -path "./tmp" -o -path "./scripts" \) -prune -o -name "*_peaks.bed" -print
or
find \( -path "./tmp" -o -path "./scripts" \) -prune -false -o -name "*_peaks.bed"
or
find \( -path "./tmp" -path "./scripts" \) ! -prune -o -name "*_peaks.bed"
The order is important. It evaluates from left to right.
Always begin with the path exclusion.
Explanation
Do not use -not (or !) to exclude whole directory. Use -prune.
As explained in the manual:
−prune The primary shall always evaluate as true; it
shall cause find not to descend the current
pathname if it is a directory. If the −depth
primary is specified, the −prune primary shall
have no effect.
and in the GNU find manual:
-path pattern
[...]
To ignore a whole
directory tree, use -prune rather than checking
every file in the tree.
Indeed, if you use -not -path "./pathname",
find will evaluate the expression for each node under "./pathname".
find expressions are just condition evaluation.
\( \) - groups operation (you can use -path "./tmp" -prune -o -path "./scripts" -prune -o, but it is more verbose).
-path "./script" -prune - if -path returns true and is a directory, return true for that directory and do not descend into it.
-path "./script" ! -prune - it evaluates as (-path "./script") AND (! -prune). It revert the "always true" of prune to always false. It avoids printing "./script" as a match.
-path "./script" -prune -false - since -prune always returns true, you can follow it with -false to do the same than !.
-o - OR operator. If no operator is specified between two expressions, it defaults to AND operator.
Hence, \( -path "./tmp" -o -path "./scripts" \) -prune -o -name "*_peaks.bed" -print is expanded to:
[ (-path "./tmp" OR -path "./script") AND -prune ] OR ( -name "*_peaks.bed" AND print )
The print is important here because without it is expanded to:
{ [ (-path "./tmp" OR -path "./script" ) AND -prune ] OR (-name "*_peaks.bed" ) } AND print
-print is added by find - that is why most of the time, you do not need to add it in you expression. And since -prune returns true, it will print "./script" and "./tmp".
It is not necessary in the others because we switched -prune to always return false.
Hint: You can use find -D opt expr 2>&1 1>/dev/null to see how it is optimized and expanded,
find -D search expr 2>&1 1>/dev/null to see which path is checked.
Here is one way you could do it...
find . -type f -name "*_peaks.bed" | egrep -v "^(./tmp/|./scripts/)"
for me, this solution didn't worked on a command exec with find, don't really know why, so my solution is
find . -type f -path "./a/*" -prune -o -path "./b/*" -prune -o -exec gzip -f -v {} \;
Explanation: same as sampson-chen one with the additions of
-prune - ignore the proceding path of ...
-o - Then if no match print the results, (prune the directories and print the remaining results)
18:12 $ mkdir a b c d e
18:13 $ touch a/1 b/2 c/3 d/4 e/5 e/a e/b
18:13 $ find . -type f -path "./a/*" -prune -o -path "./b/*" -prune -o -exec gzip -f -v {} \;
gzip: . is a directory -- ignored
gzip: ./a is a directory -- ignored
gzip: ./b is a directory -- ignored
gzip: ./c is a directory -- ignored
./c/3: 0.0% -- replaced with ./c/3.gz
gzip: ./d is a directory -- ignored
./d/4: 0.0% -- replaced with ./d/4.gz
gzip: ./e is a directory -- ignored
./e/5: 0.0% -- replaced with ./e/5.gz
./e/a: 0.0% -- replaced with ./e/a.gz
./e/b: 0.0% -- replaced with ./e/b.gz
You can try below:
find ./ ! \( -path ./tmp -prune \) ! \( -path ./scripts -prune \) -type f -name '*_peaks.bed'
Try something like
find . \( -type f -name \*_peaks.bed -print \) -or \( -type d -and \( -name tmp -or -name scripts \) -and -prune \)
and don't be too surprised if I got it a bit wrong. If the goal is an exec (instead of print), just substitute it in place.
With these explanations you meet your objective and many others. Just join each part as you want to do.
MODEL
find ./\
-iname "some_arg" -type f\ # File(s) that you want to find at any hierarchical level.
! -iname "some_arg" -type f\ # File(s) NOT to be found on any hirearchic level (exclude).
! -path "./file_name"\ # File(s) NOT to be found at this hirearchic level (exclude).
! -path "./folder_name/*"\ # Folder(s) NOT to be found on this Hirearchic level (exclude).
-exec grep -IiFl 'text_content' -- {} \; # Text search in the content of the found file(s) being case insensitive ("-i") and excluding binaries ("-I").
EXAMPLE
find ./\
-iname "*" -type f\
! -iname "*pyc" -type f\
! -path "./.gitignore"\
! -path "./build/*"\
! -path "./__pycache__/*"\
! -path "./.vscode/*"\
! -path "./.git/*"\
-exec grep -IiFl 'title="Brazil - Country of the Future",' -- {} \;
Thanks! 🤗🇧🇷
[Ref(s).: https://unix.stackexchange.com/q/73938/61742 ]
EXTRA:
You can use the commands above together with your favorite editor and analyze the contents of the files found, for example...
vim -p $(find ./\
-iname "*" -type f\
! -iname "*pyc" -type f\
! -path "./.gitignore"\
! -path "./build/*"\
! -path "./__pycache__/*"\
! -path "./.vscode/*"\
! -path "./.git/*"\
-exec grep -IiFl 'title="Brazil - Country of the Future",' -- {} \;)

find command search only non hidden directories

In the following command i want to search only only the directories which are non hidden how can i do this using the following command .Iwant to ignore hidden directories while searching the log file
find /home/tom/project/ -name '.log.txt'
ls /home/tom/project/
dir1
dir2
.backup
.snapshot/
.ignore/
Try
find /home/tom/project -type d -name '.*' -prune -o -name .log.txt -print
This will find all files but ignore those that start with a dot so hidden files.
find /home/tom/project/ -type f \( -iname ".log.txt" ! -iname ".*" \)
EDIT:
If the above those not work, this should do the trick. It has a better regex.
find /home/tom/project/ \( ! -regex '.*/\..*' \) -type f -name ".log.txt"
EDIT2:
The following will exclude hidden folders but will search for the hidden files that have the requested pattern:
find /home/tom/project/ \( ! -regex '.*/\..*/..*' \) -type f -name ".log.txt"
EDIT3:
The grep solution :) if this doesn't work i'm lost :)
find /home/tom/project/ \( ! -regex '.*/\..*/..*' \) -exec grep -l ".log.txt" {} \;
EDIT4:
Have you tried the simple solutions?
find /home/tom/project/ -type f -name ".log.txt"
OR
find /home/tom/project/ -type f -name "*" -exec grep -l ".log.txt" {} \;

Resources