I try to grep some string and want grep to stop at a '+' symbol. The searched string could be "foo" or "foo+bar". Grep treats all findings no matter what comes after foo and the -w option seems recognizing the '+' as whole word also. So I want to grep all files that include matches with 'foo' but not handle matches like 'foo+bar'. The command should be stored in a varaible like this

COUNT="$(find . -maxdepth 1 -type f -name '*.sh' | xargs grep -w -o -m 1 "foo" | wc -l)"

So how does this var has to be modified to only find the foo entries in the files but not foo+bar.

EDIT: Finally I have the solution for my case. Maybe I did not describe clearly enough what I needed but the trick was to add a ^ at beginning and a $ at the end of the searched string. So my files in the folder that could contain something like text=foo or text=foo+bar should be filtered so when I search for text=foo grep only outputs these and not including text=foo+bar. My code now looks like

COUNT="$(find . -maxdepth 1 -type f -name '*.sh' | xargs grep -x -o -m 1 "^text=foo$" | wc -l)"

And now in that case grep doesnt count findings with text=foo+bar. Sounds as simple as it is but needed again some struggle for me to figure out :)


1 Answer 1


If you want to match and count (filenames), then ...

You might not need grep at all for such task ... You can include and exclude filename patterns with finds -name "..." to include and ! -name "..." to exclude ... You can nest as many as you need of these to match only the filenames you want e.g. like so:

find -type f -name "*foo*.sh" ! -name "*+*.sh"

This will output files with .sh extension that have foo and don't have + in their names.

So your command substitution set parameter(Notice: ALL CAPS are used for shell's built-ins ... So use lower or mixed case for your parameters) would be:

count=$(find -maxdepth 1 -type f -name "*foo*.sh" ! -name "*+*.sh" | wc -l)

Notice .(current working directory) is finds default search path ... So you don't have to specify it.

If you want to match and count (text inside each file), then ...

You might not need any of that at all ... But, you'll, probably, need a more sophisticated tool other than grep to do exactly the rather complex task of yours ... You can use only awk with the shell's filename globbing like so:

awk '{for ( i=1; i <= NF; i++ ) { if ( $i !~ "+" ) { if ( $i == "foo" ) { count++ }} else { nextfile }}} END { print count }' *.sh

for ( i=1; i <= NF; i++ ) will pass each single field("word") in the current line on its own to the next action where if ( $i !~ "+" ) will check if the field doesn't contains the + character(e.g. the field is not +, foo+bar, +foo or foo+) and if true, pass it to the next action where if ( $i == "foo" ) will check if the field is exactly foo and if true, increment the counter using count++ or else it will exit the current file and move on to processing the next file(passed to awk in the file/s argument/s) using nextfile and at the END will print the value of the current counter(total for all processed files) using print count.

And your command substitution set parameter(Use printf instead of print to avoid injecting a newline into the parameter) would be:

count=$(awk '{for ( i=1; i <= NF; i++ ) { if ( $i !~ "+" ) { if ( $i == "foo" ) { count++ }} else { nextfile }}} END { printf "%d", count }' *.sh)

This should work fine even if you run it on thousands of files ... If, however, your files are in tens of thousands or more and you get Argument list too long error message, then, IMHO, you need to reconsider including such a task altogether in your script and find a different approach for achieving your goal.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .