grep recursive exclusive search - search

I have text I want to recursively search in mydir/. I would normally type grep -r "text to find" mydir/" but what would I type if I wanted to search all the files except a specific one?
For example, I do not want to search the file "myfile.txt" but it is contained in the directory.
What would I type?

One quick way is to use BASH's extended file globbing.
There is an extended negation glob that you can use as follows:
shopt -s extglob
grep -r "text to find" !(myfile.txt)
Another more flexible method is to use a combination of find and xargs. Use find to filter out only the files you want, and xargs to pass them as arguments to grep.
Something like the following sould work:
find -type f -not -name myfile.txt | xargs grep "text to find"

You can use the following command to exclude a file:
grep -r --exclude="myfile.txt" "text to find"

Related

Using "grep" to search for specific type of files in all subdirectories

I am trying to find a specific line in files that contains "Mutual_Values_23.0" in a directory that contains a lot of subdirectories. I know this line number is stored in a file which starts with "gnuout_mutual_....txt" (the ellipses part of the file name is the time stamp so that varies).
I wanted to know if there is a way to specify "grep" command to look into the subdirectories only for the files starting with "gnuout_mutual_....txt"
I have tried
grep -r "Mutual_Values_23.0" *
but that's taking a long time
You can use the following option of grep:
--include=GLOB
Search only files whose base name matches GLOB (using wildcard matching as described under --exclude).
And for the line number you should use the -n option.
From within the root of the folders you want to look into, you can use this final command:
grep -nr "Mutual_Values_23.0" --include="gnuout_mutual_*txt"
Use find to search all sub-directories for the "gnuout...txt` file with the search string "Mutual_Values_23.0"
find . -mindepth 1 -name gnuout_mutual_\*.txt -type f -exec grep "Mutual_Values_23.0" {} +
If you make use of bash, you can use the globstar option:
globstar
If set, the pattern ** used in a pathname expansion context will
match all files and zero or more directories and subdirectories.
If the pattern is followed by a /, only directories and
subdirectories match.
So you can use it like:
$ shopt -s globstar
$ grep "search_string" **/glob-pattern
or in the case of the OP:
$ shopt -s globstar
$ grep Mutual_Values_23.0 **/gnuout_mutual_*.txt
GNU grep has the --include GLOB option where GLOB can be used to specify the file name pattern that you need to match.
grep -rn --include 'gnuout_mutual_*txt' 'Mutual_Values_23.0' .
You could use find to search for files and pass results to grep.
find /directory_where_to_search/ -iname 'gnuout_mutual_*.txt' | xargs grep 'Mutual_Values_23.0' -sl
Use this command:
$ find . -name "*Mutual_Values_23.0*"
Note: Run this command in the directory where you want to search your set of files.
Hope it helps, cheers!

find recursively, but with specific sub-folder name

This command find all files name "log_7" recursively in current folder.
find . -name log_7
Assume many sub-folders under the current folder tree has a file with that same name "log_7":
./am/f1/log_7
./ke/f2/log_7
./sa/f6/log_7
..
./xx/f97/log_7
Is there a way to explicitly say that we only want to search for "log_7" in a folder name "f2" ? such that the result from find will only list only one entry:
./ke/f2/log_7
You could use a regular expression.
find . -regex '.*/f2/log_7'
This will only match if log_7 is directly nested under f2
There is a different way is to use xargs for same thing
find . -name filename | xargs grep -e refectory
But find with built in regex is preferable.
Simple glob should do:
printf '%s\n' */f2/log_7
If there is a possibility for more leading folders, you can use the globstar option:
shopt -s globstar
printf '%s\n' **/f2/log_7

How to search and replace using grep

I need to recursively search for a specified string within all files and subdirectories within a directory and replace this string with another string.
I know that the command to find it might look like this:
grep 'string_to_find' -r ./*
But how can I replace every instance of string_to_find with another string?
Another option is to use find and then pass it through sed.
find /path/to/files -type f -exec sed -i 's/oldstring/new string/g' {} \;
I got the answer.
grep -rl matchstring somedir/ | xargs sed -i 's/string1/string2/g'
You could even do it like this:
Example
grep -rl 'windows' ./ | xargs sed -i 's/windows/linux/g'
This will search for the string 'windows' in all files relative to the current directory and replace 'windows' with 'linux' for each occurrence of the string in each file.
This works best for me on OS X:
grep -r -l 'searchtext' . | sort | uniq | xargs perl -e "s/matchtext/replacetext/" -pi
Source: http://www.praj.com.au/post/23691181208/grep-replace-text-string-in-files
Usually not with grep, but rather with sed -i 's/string_to_find/another_string/g' or perl -i.bak -pe 's/string_to_find/another_string/g'.
Other solutions mix regex syntaxes. To use perl/PCRE patterns for both search and replace, and process only matching files, this works quite well:
grep -rlIZPi 'match1' | xargs -0r perl -pi -e 's/match2/replace/gi;'
match1 and match2 are usually identical but match2 can contain more advanced features that are only relevant to the substitution, e.g. capturing groups.
Translation: grep recursively and list matching filenames, each separated by null to protect any special characters; pipe any filenames to xargs which is expecting a null-separated list; if any filenames are received, pass them to perl to perform the actual substitutions.
For case-sensitive matching, drop the i flag from grep and the i pattern modifier from the s/// expression, but not the i flag from perl itself. To include binary files, remove the I flag from grep.
Be very careful when using find and sed in a git repo! If you don't exclude the binary files you can end up with this error:
error: bad index file sha1 signature
fatal: index file corrupt
To solve this error you need to revert the sed by replacing your new_string with your old_string. This will revert your replaced strings, so you will be back to the beginning of the problem.
The correct way to search for a string and replace it is to skip find and use grep instead in order to ignore the binary files:
sed -ri -e "s/old_string/new_string/g" $(grep -Elr --binary-files=without-match "old_string" "/files_dir")
Credits for #hobs
Here is what I would do:
find /path/to/dir -type f -iname "*filename*" -print0 | xargs -0 sed -i '/searchstring/s/old/new/g'
this will look for all files containing filename in the file's name under the /path/to/dir, than for every file found, search for the line with searchstring and replace old with new.
Though if you want to omit looking for a specific file with a filename string in the file's name, than simply do:
find /path/to/dir -type f -print0 | xargs -0 sed -i '/searchstring/s/old/new/g'
This will do the same thing above, but to all files found under /path/to/dir.
Modern rust tools can be used to do this job.
For example to replace in all (non ignored) files "oldstring" and "oldString" with "newstring" and "newString" respectively you can :
Use fd and sd
fd -tf -x sd 'old([Ss]tring)' 'new$1' {}
Use ned
ned -R -p 'old([Ss]tring)' -r 'new$1' .
Use ruplacer
ruplacer --go 'old([Ss]tring)' 'new$1' .
Ignored files
To include ignored (by .gitignore) and hidden files you have to specify it :
use -IH for fd,
use --ignored --hiddenfor ruplacer.
Another option would be to just use perl with globstar.
Enabling shopt -s globstar in your .bashrc (or wherever) allows the ** glob pattern to match all sub-directories and files recursively.
Thus using perl -pXe 's/SEARCH/REPLACE/g' -i ** will recursively
replace SEARCH with REPLACE.
The -X flag tells perl to "disable all warnings" - which means that
it won't complain about directories.
The globstar also allows you to do things like sed -i 's/SEARCH/REPLACE/g' **/*.ext if you wanted to replace SEARCH with REPLACE in all child files with the extension .ext.

How to perform grep operation on all files in a directory?

Working with xenserver, and I want to perform a command on each file that is in a directory, grepping some stuff out of the output of the command and appending it in a file.
I'm clear on the command I want to use and how to grep out string(s) as needed.
But what I'm not clear on is how do I have it perform this command on each file, going to the next, until no more files are found.
In Linux, I normally use this command to recursively grep for a particular text within a directory:
grep -rni "string" *
where
r = recursive i.e, search subdirectories within the current directory
n = to print the line numbers to stdout
i = case insensitive search
grep $PATTERN * would be sufficient. By default, grep would skip all subdirectories. However, if you want to grep through them, grep -r $PATTERN * is the case.
Use find. Seriously, it is the best way because then you can really see what files it's operating on:
find . -name "*.sql" -exec grep -H "slow" {} \;
Note, the -H is mac-specific, it shows the filename in the results.
To search in all sub-directories, but only in specific file types, use grep with --include.
For example, searching recursively in current directory, for text in *.yml and *.yaml :
grep "text to search" -r . --include=*.{yml,yaml}
If you want to do multiple commands, you could use:
for I in `ls *.sql`
do
grep "foo" $I >> foo.log
grep "bar" $I >> bar.log
done

How would you do a search from the unix shell with both constraints on the filename and the file content?

Can find perform full-text search? How would you do a search with both some constraints on the filename and the file content?
find . -name whatever -print | xargs grep whatever
Add "-l" option to grep to just get filenames.
I would strongly recommend getting hold of ack and using it for any findy-greppy-type-stuff that you want to do - I use it every day and can't imagine how I lived without it! In this case it sounds like ack -G <file-regex> <text-regex> would do what you want.
find -name whatever -exec grep --with-filename you_search_for_it {} \;
{} contains the file name returned by find
\; to terminate the find command
In some cases globbing will provide enough constraints on your filenames:
shopt -s nullglob # Bash: prevents "No such file or directory" errors
grep string {.,[jm]*,{one,two}}/{[a-c],[hlz]}?{earth,mars,venus}[[:ascii:]]*atm*.dat
which would search files such as:
./bZmars_321atmBB111.dat
m42a/z3venus-a18atm9.dat
two/aaearth+GHIatm9876.dat

Resources