linux find command is not taking proper argument - linux

find . -maxdepth 1 ! -path . -type f ! -name "*.gz" ${FILE_PATTERN} -mtime +${DAYS_AFTER_ARCHIVE}
I am trying to execute above command inside the script where ${FILE_PATTERN} and ${DAYS_AFTER_ARCHIVE} are the variable provided while executing the script. The variable value for ${FILE_PATTERN} would be ! -name "*warnings*".
I am looking for executing above command as like below command in script
find . -maxdepth 1 ! -path . -type d ! -name "*warnings*" -mtime +7
I am providing file pattern argument as "! -name "warnings""
but receiving following error message
find: paths must precede expression
Usage: find [-H] [-L] [-P] [path...] [expression]
suggest on above.

First of all
-name "*.gz" ${FILE_PATTERN}
has too many option values (this is what usually causes the message shown)
If you use bash or similar, escape the exclamation marks
find . -maxdepth 1 \! -path . -type f \! -name "*.gz" ${FILE_PATTERN} -mtime +${DAYS_AFTER_ARCHIVE}
I am providing file pattern argument as "! -name "warnings"" but receiving following error message
You can't combine flags and their values like that. Also, you can't nest " like that. So, it could be like
! -name "$FILE_PATTERN"

If you're using BASH you can make use of BASH arrays:
# find options
FILE_PATTERN=(! -name "warnings*")
# build the find command
cmd=(find . -maxdepth 1 ! -path . -type f \( ! -name "*.gz" "${FILE_PATTERN[#}}" \) -mtime +${DAYS_AFTER_ARCHIVE})
# execute the command
"${cmd[#]}"
If not using BASH then you will have to use eval with caution:
FILE_PATTERN='! -name "warnings*"'
eval find . -maxdepth 1 ! -path . -type f ! -name "*.gz" "${FILE_PATTERN}" -mtime +${DAYS_AFTER_ARCHIVE}

Related

How to write a bash script to find files with complex conditions [duplicate]

This question already has answers here:
Expanding a bash array only gives the first element
(1 answer)
Why does shell ignore quoting characters in arguments passed to it through variables? [duplicate]
(3 answers)
Closed 11 months ago.
Expected Function: write a bash script showing all the regular files
with filename ended with ".xml" or ".yml" and with path not begined
with "./target/", i.e., exclude the "target" subdirectory.
Example File List: ./a/a1.xml; ./a/a2.txt; ./b/b1.yml; ./target/t.xml;
Example Outout: ./a/a1.xml; ./b/b1.yml
I construct find options in a bash shell script like
#!/bin/bash
find_opts=( -type f -a ( -not -path "./target/*" ) -a ( -false -o -name "*.xml" -o -name "*.yml" ) )
find . $find_opts
But it does not output the expected result. Howver, when I tpye the full command string in bash terminal as follows:
[root#localhost]#find . \( -type f -a \( -not -path "./target/*" \) -a \( -false -o -name "*.xml" -o -name "*.yml" \) \)
it works. What is the problem about the above bash script ?
==============================================================
Someone gives this reference link : Expanding a bash array only gives the first element
It is about "bash array". But it seems that my problem is not about "bash array". Would anyone give any reasons?
Please see How to exclude a directory in find . command to known why I use parentheses whiches look like an array. Anyway, I try two others attempts:
#the first one
find_opts=( -type f -a ( -not -path "./target/*" ) -a ( -false -o -name "*.xml" -o -name "*.yml" ) )
#the second one
find_opts=\\( -type f -a \\( -not -path "./target/*" \\) -a \\( -false -o -name "*.xml" -o -name "*.yml" \\) \\)
#the third one
find_opts=\( -type f -a \( -not -path "./target/*" \) -a \( -false -o -name "*.xml" -o -name "*.yml" \) \)
The first one and the third one give the same output which is unexpeced result. The second one occures an error.
syntax error near unexpected token `('
The problem is still here.
==============================================================
Someone give one more reference:Why does shell ignore quoting characters in arguments passed to it through variables?
It is about how to use bash array to pass arguments and the problem is solved by the following code:
#!/bin/bash
find_opts=(\( -type f -a \( -not -path './target/*' \) -a \( -false -o -name '*.xml' -o -name '*.yml' \) \))
find . "${find_opts[#]}"

How to use -regex in the find command on Linux command line

The objective is to find and list anything with "messages" and/or "error.log" etc.. in the beginning then list both "messages.1..99" and "error.log.1..99" using regular expressions.
This command works for however, it would require me to make many -or searches, but to simplify, I would like to have multiple in a set within the search. Like for instance:
# find /var/log -maxdepth 1 -type f -size +1M -name [messages|error.log|secure.log|kern.log...]?[0-9]|[0-9][0-9] ! -iname "*.gz"
not
# find /var/log -maxdepth 1 -type f -size +1M -name "messages?[0-9]" -o -name "messages?[0-9][0-9]"
How might I perform this command with regular expressions?
# find /var/log -maxdepth 1 -type f -size +1M -name "[messages,error.log,kern,secure]?[0-9]" ! -iname "*.gz"
My attempt with regex doesn't print anything in standard out:
# find /var/log -maxdepth 1 -type f -size +1M -regex -name "[messages,error,kern,secure]?[0-9]" ! -iname "*.gz"
Try this:
find /var/log -maxdepth 1 -type f -size +1M -type f -regextype egrep -regex '.*(messages|error|kern|secure)\.[0-9]+.*' -not -name \*gz

Combine two find commands in one

I currently have the following line:
find . -type f ! -name "*.xml" -delete && find . -type d -empty -delete
But as they are 2 find commands, I suppose I can merge them into one. But I don't want to use -exec.
I wan't to use something native in bash, that comes preinstalled with any linux distro.
You can group primaries with the -o ("or", as opposed to the implict "and" that find applies to its primaries) primary. The parentheses are escaped to avoid shell syntax errors and ensure they are passed as arguments to find.
find . \( \( -type f ! -name "*.xml" \) -o \( -type d -empty \) \) -delete

How can I find files of multiple types using an or statement?

I can't seem to find this on StackOverflow by searching. How can I find all files of type .html and .py?
I'm trying find ./ -name *.py or *.html -type f
But this is returning find: or: unknown primary or operator
$ find . -name '*.h' -o -name '*.cpp'.
or find . -type f \( -name "*.class" -o -name "*.sh" \)

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",' -- {} \;)

Resources