11

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.

11

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"
fi
  • 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? – Mareyes Aug 23 '18 at 19:07
  • 2
    No, both of them provide the -q and -w options to grep. – glenn jackman Aug 23 '18 at 19:09
  • 3
    Why no quotes on the variable expansion? – D. Ben Knoble Aug 24 '18 at 2:31
  • @D.BenKnoble is it correctly: "$user" "$file1"? – Mareyes Aug 24 '18 at 4:44
  • 1
    @Mareyes yes that is correct – D. Ben Knoble Aug 24 '18 at 12:30
13

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 ;;
esac
  • Hey thank you for the optional way. It works enough well!! works perfectly in another script I have. – Mareyes Aug 24 '18 at 4:46
  • That's a great idea! Always learning. – Joe Aug 25 '18 at 8:45
7
n=0

#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 
   1) 
       echo "Only one file with the string"
    ;;
   2)
       echo "The two files are with the string"
   ;;
   0)
       echo "No one file with the string"
   ;;
   *)
       echo "Strange..."
   ;;
esac 

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. – Luciano Andress Martini Aug 23 '18 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. – glenn jackman Aug 23 '18 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. – Peter Cordes Aug 24 '18 at 1:04
  • @Peter that would be worth writing up as a separate answer IMO. – Stephen Kitt Aug 24 '18 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. – Peter Cordes Aug 24 '18 at 19:07
2

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"
    else
        echo "match in file2"
    fi
fi

$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. – Peter Cordes Aug 24 '18 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. – Stephen Kitt Aug 24 '18 at 19:35
  • 1
    mapfile -d $'\0' sort of works, but it replaces the newlines with spaces (I haven’t tried tweaking IFS). – Stephen Kitt Aug 24 '18 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.