Using find on multiple file extensions in combination with grep - bash

I am having a problems using find and grep together in msys on Windows. However, I also tried the same command on a Linux machine and it behaved the same. Notwithstanding, the syntax below is for windows in that the semicolon on the end of the command is not preceded by a backslash.
I am trying to write a find expression to find *.cpp and *.h files and pass the results to grep. If I run this alone, it successfully finds all the .cpp and .h files:
find . -name '*.cpp' -o -name '*.h'
But if I add in an exec grep expression like this:
find . -name '*.cpp' -o -name '*.h' -exec grep -l 'std::deque' {} ;
It only greps the .h files. If I switch the .h and .cpp order in the command, it only searches the .h. Essentially, it appears to only grep the last file extension in the expression. What do I need to do to grep both .h and .cpp??

Since you're using -o, you will need to use parentheses around it:
find . \( -name '*.cpp' -o -name '*.h' \) -exec grep -l 'std::deque' {} \;

Or.. you can ...
bash$> grep '/bin' `find . -name "*.pl" -o -name "*.sh"`
./a.sh:#!/bin/bash
./pop3.pl:#!/usr/bin/perl
./seek.pl:#!/usr/bin/perl -w
./move.sh:#!/bin/bash
bash$>
Above command greps 'bin' in ".sh" and ".pl" files. And it has found them !!

Related

Bash wrapping parts of a variable in quotes when expanded

I'm trying to recursively find c and header files in a script, while avoiding globbing out any that exist in the current directory.
FILE_MATCH_LIST='"*.c","*.cc","*.cpp","*.h","*.hh","*.hpp"'
FILE_MATCH_REGEX=$(echo "$FILE_MATCH_LIST" | sed 's/,/ -o -name /g')
FILE_MATCH_REGEX="-name $FILE_MATCH_REGEX"
This does exactly what I want it to:
+ FILE_MATCH_REGEX='-name "*.c" -o -name "*.cc" -o -name "*.cpp" -o -name "*.h" -o -name "*.hh" -o -name "*.hpp"'
Now, if I call find with that string (in quotes), it maintains the leading and trailing quotes and breaks find:
files=$(find $root_dir "$FILE_MATCH_REGEX" | grep -v $GREP_IGNORE_LIST)
+ find [directory] '-name "*.c" -o -name "*.cc" -o -name "*.cpp" -o -name "*.h" -o -name "*.hh" -o -name "*.hpp"'
This results in a "unknown predicate" error from find, because the entire predicate is single quoted.
If I drop the quotes from the variable in the find command, I get a strange behavior:
files=$(find $root_dir $FILE_MATCH_REGEX | grep -v $GREP_IGNORE_LIST)
+ find [directory] -name '"*.c"' -o -name '"*.cc"' -o -name '"*.cpp"' -o -name '"*.h"' -o -name '"*.hh"' -o -name '"*.hpp"'
Where are these single quotes coming from? They exist if I echo that variable as well, but they aren't there in the command when I'm actually setting the $FILE_MATCH_REGEX (As seen at the beginning of the question).
This of course also breaks find, because it's looking for the actual double quoted string, instead of expanding the *.h etc.
How do I get these strings into find without all of these quoting woes?
Fleshing out the array answer:
#!/bin/bash
patterns=( '*.c' '*.cc' '*.h' '*.hh' )
find_args=( "-name" "${patterns[0]}" )
for (( i=1 ; i < "${#patterns[#]}" ; i++ )) ; do
find_args+=( "-o" "-name" "${patterns[i]}" )
done
find [directory] "${find_args[#]}"
Also, to clear up the misconception around quotes, if you echo the last line the output might not be what you expect:
echo find [directory] "${find_args[#]}"
# outputs: find [directory] -name *.c -o -name *.cc -o -name *.h -o -name *.hh
Where are the quotes? Your shell removed them after it was done with them. Quotes are not find syntax, they are shell syntax that tell the shell how to interpret (or perhaps how NOT to interpret) your command line.
The reason for the strange behavior in your debug output is that the quotes in your data are literal quotes, not shell syntax quotes that get removed during command parsing. The debugger is just trying to point out the distinction.
Some useful resources on the Bash wiki:
BashParser explains how your command line gets parsed and executed
BashFAQ/050 explains why embedding quotes in your data isn't sufficient
If you have GNU find - adjust to your liking:
#!/bin/bash
#FILE_MATCH_LIST='"*.c","*.cc","*.cpp","*.h","*.hh","*.hpp"'
FILE_MATCH_LIST='.*/.*\.(c|cc|cpp|h|hh|hpp)'
find . -type f -regextype posix-egrep -regex "${FILE_MATCH_LIST}"

Why is my find and xargs copy command working for one folder and not for the other?

I have two directories, x86_64 and i386.
I want to filter out test RPMS from both of these folders and place them in a seperate one; test_release/{version}-x86_64/x86_64 and test_release/{version}-i386/i386, respectively.
So my first command works fine:
find x86_64/ -type f -name '*test*' -o -name '*demo*' -o -name '*log*' |
xargs cp -rt test_release/${RELEASE}-x86_64/x86_64
My second command is exactly the same, except with different folder names:
find i386/ -type f -name '*test*' -o -name '*demo*' -o -name '*log*' |
xargs cp -rt test_release/${RELEASE}-i386/i386
Only the second command gives me the error:
cp: missing file operand
Am I missing something?
The error happens because your find command doesn't return any files. Disappointingly, xargs still runs cp, but ends up calling it with too few arguments; and so you get the error message.
GNU xargs has an -r option which solves this specific case; but a more portable and more robust solution is to use find -exec.
find i386/ -type f -name '*test*' -o -name '*demo*' -o -name '*log*' \
-exec cp -rt "test_release/${RELEASE}-i386/i386" {} +
The + statement terminator for -exec is not entirely portable, but if you have cp -t I guess you're on Linux, or at least are using a recent find (GNU or not GNU. If not, replacing + with \; is a workaround, though it will end up running more processes than the equivalent xargs construct).

List of object files to gcc format

I'm trying to link all my files with object *.o extension in my directory.
I tried to use:
for i in $(find . -name "*.o" -type f);
do
echo $i >> myFiles
done
Then I need:
gcc -o myFile <myFiles
gcc: fatal error: no input files
compilation terminated.
I see several problem in your approach:
find should not be used in a loop, but rather with a -fprint <file>. So in your case:
find . -name "*.o" -type f -fprintf myfiles
Secondly, redirecting your file to gcc stdin will not work as you think, as it uses the input as the source of the code: see this question. What you want instead is to expand the list of objects to a list of arguments:
cat myfiles | xargs gcc -o myFile
xargs does it nicely. But as #n.m. mentioned, you could do everything at once with a command substitution:
gcc -o myfile $(find . -type f -name *.o -print0)
The only difference I suggest is to use a -print0 so that find put a \0 at the end of a find instead of a \n.
Good Luck

Bash Script How to find every file in folder and run command on it

So im trying to create a script that looks in a folder and finds all the file types that have .cpp and run g++ on them. so far i have but it doesn't run it says unexpected end
for i in `find /home/Phil/Programs/Compile -name *.cpp` ; do echo $i ;
done
Thanks
The problem with your code is that the wildcard * is being expanded by the shell before being passed to find. Quote it thusly:
for i in `find /home/Phil/Programs/Compile -name '*.cpp'` ; do echo $i ; done
xargs as suggested by others is a good solution for this problem, though.
find has an option for doing exactly that:
find /p/a/t/h -name '*.cpp' -exec g++ {} \;
This code works for me:
#!/bin/bash
for i in `find /home/administrator/Desktop/testfolder -name *.cpp` ; do echo $i ;
done
I get:
administrator#Netvista:~$ /home/administrator/Desktop/test.sh
/home/administrator/Desktop/testfolder/main.cpp
/home/administrator/Desktop/testfolder/main2.cpp
You could use xargs like:
find folder/ -name "*.cpp" | xargs g++
Or if you want to handle files which contain whitespaces:
find folder/ -name "*.cpp" -print0 | xargs -0 g++
I think you want to use xargs:
For example:
find /home/Phil/Programs/Compile -name *.cpp | xargs g++
How about using xargs like this:
find $working_dir -type f -name *.cpp | xargs -n 1 g++

Using `find` for multiple file types and replacing strings in found files

I'm trying to recursively find files of multiple types, and replace a certain string in each of the files found. But when I run the script, it only finds files of one single type.
The command line I'm using is this
find . -name '*.tcl' -o -name '*.itcl' -o -name '*.db' -exec sed -i 's/abc/cba/g' {} +
Every time I run the command above, it only finds files of type .db. When I run it with a single file type, it works fine.
You need to group the -names * part with () like that :
Moreover, it's better (from my experience) to run sed -i only on files that match the pattern, so :
find . \( -name '*.tcl' -o -name '*.itcl' -o -name '*.db' \) -exec sed -i '/abc/s/abc/cba/g' {} +
Below command will search for either txt or log files and replace the source string with the target string.
find . -name "*.txt" -o -name "*.log"|xargs perl -pe 's/source/target/g'
you can do the replace ment by adding an -i in the perl statement as below:
find . -name "*.txt" -o -name "*.log"|xargs perl -pi -e 's/source/target/g'
You can do:
find . -regex ".*\.\(tcl\|itcl\|db\)" -exec sed -i 's/abc/cba/g' {} +
This is more compact, and you do not have to mess around with OR and grouping the logical operations.

Resources