I want to search multiple strings in two files. If one string is found in both files, then make something. If one string is found in only one file, then make another thing.

My commands are the next:

####This is for the affirmative sentence in both files
if grep -qw "$users" "$file1" && grep -qw "$users" "$file2"; then

####This is for the affirmative sentence in only one file, and negative for the other one
if grep -qw "$users" "$file1" ! grep -qw "$users" "$file2"; then

is it a correct the way to deny and affirm the statements? p.d. I'm using KSH shell.

Thank you in advance.


4 Answers 4


Another option:

grep -qw -- "$users" "$file1"; in_file1=$?
grep -qw -- "$users" "$file2"; in_file2=$?

case "${in_file1},${in_file2}" in
    0,0) echo found in both files ;;
    0,*) echo only in file1 ;;
    *,0) echo only in file2 ;;
      *) echo in neither file ;;
  • Hey thank you for the optional way. It works enough well!! works perfectly in another script I have. Aug 24, 2018 at 4:46
  • That's a great idea! Always learning.
    – Joe
    Aug 25, 2018 at 8:45

Try this:

if grep -wq -- "$user" "$file1" && grep -wq -- "$user" "$file2" ; then
   echo "string avail in both files"
elif grep -wq -- "$user" "$file1" "$file2"; then
   echo "string avail in only one file"
  • grep can search for patterns in multiple files, so no need to use an OR/NOT operator.
  • Is there a minimal difference between the "-wq" and the "-qw" in any each command? Aug 23, 2018 at 19:07
  • 2
    No, both of them provide the -q and -w options to grep. Aug 23, 2018 at 19:09
  • 3
    Why no quotes on the variable expansion? Aug 24, 2018 at 2:31
  • @D.BenKnoble is it correctly: "$user" "$file1"? Aug 24, 2018 at 4:44
  • 1
    @Mareyes yes that is correct Aug 24, 2018 at 12:30

#Or if you have more files to check, you can put your while here. 
grep -qw -- "$users" "$file1" && ((n++))
grep -qw -- "$users" "$file2" && ((n++))

case $n in 
       echo "Only one file with the string"
       echo "The two files are with the string"
       echo "No one file with the string"
       echo "Strange..."

Note: ((n++)) is a ksh extension (also supported by zsh and bash). In POSIX sh syntax, you'd need n=$((n + 1)) instead.

  • Oh sorry i was typing $((n++)) instead of ((n++)). Forget. Aug 23, 2018 at 19:04
  • 1
    Note that n=$((n++)) will NEVER change the value of n because n++ is post increment: it returns the current value of n, then increments n; then you set the variable to the returned value which was the original n. Aug 23, 2018 at 19:05
  • If you can assume your filenames don't contain newlines, local IFS=$'\n'; matches=( $(grep -lw "$users" "$file1" "$file2") ), and use case "${#matches[@]" in. @glenn: this idea applies to your answer, too, to avoid multiple invocations of grep. Piping it to grep -l | wc -l would also work. Aug 24, 2018 at 1:04
  • @Peter that would be worth writing up as a separate answer IMO. Aug 24, 2018 at 18:37
  • @StephenKitt: I wasn't going to because I couldn't find a bulletproof way to handle arbitrary filename characters. But since you think it's worth it, I wrote it up anyway. Aug 24, 2018 at 19:07

If your filenames don't contain newlines, you can avoid multiple invocations of grep by having grep print the names of matching files, and count the results.

 local IFS=$'\n'    # inside a function.  Otherwise use some other way to save/restore IFS
 matches=( $(grep -lw "$users" "$file1" "$file2") )

The number of matches is "${#matches[@]}".

There might be a way to use grep --null -lw here, but I'm not sure how to parse the output. Bash var=( array elements ) don't have a way to use a \0 delimiter instead of \n. Maybe bash's mapfile builtin can do it? But probably not, because you specify the delimiter with -d string.

You could count=$(grep -l | wc -l), but then you have two external processes so you might as well just run grep on the two files separately. (The difference between grep vs. wc startup overhead is small compared to fork+exec + dynamic linker stuff to start up a separate process at all).

Also, with wc -l you don't find out which file matched.

With the results captured in an array, that might already be what you want, or if there's exactly 1 match, you can check if it was the first input or not.

local IFS=$'\n'    # inside a function.  Otherwise use some other way to save/restore IFS
matches=( $(grep -lw "$users" "$file1" "$file2") )

# print the matching filenames
[[ -n $matches ]] && printf  'match in %s\n'  "${matches[@]}"

# figure out which input position the name came from, if there's exactly 1.
if [[ "${#matches[@]" -eq 1 ]]; then
    if [[ $matches == "$file1" ]];then
        echo "match in file1"
        echo "match in file2"

$matches is shorthand for ${matches[0]}, the first array element.

  • @StephenKitt: I meant -z, not -0, oops. Yes that would get grep to print the filenames separated by \0 like I already mentioned in the answer, but how can you get bash to parse that? You can't set IFS=$'\0', or basically do anything else with find -print0 style output directly with bash. Aug 24, 2018 at 19:33
  • Yes, I read too fast, missing that paragraph, and didn’t think things through (which is why I deleted my comment). There might well be a way, but I can’t think of one just now, and as you say it’s not worth making the solution too complex when there are just a few greps to deal with anyway. Aug 24, 2018 at 19:35
  • 1
    mapfile -d $'\0' sort of works, but it replaces the newlines with spaces (I haven’t tried tweaking IFS). Aug 24, 2018 at 19:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.