22

I need to check if a file contains a specific line. This file is written continuously by someone, so I put the check inside a while loop.

FILE="/Users/test/my.out"
STRING="MYNAME"
EXIT=1
while [ $EXIT -ne 0 ]; do 
    if [ -f $FILE ] ; then CHECK IF THE "STRING" IS IN THE FILE - IF YES echo "FOUND"; EXIT=0; fi
done

The file contains text and multiple lines.

Or Assayag
  • 5,662
  • 13
  • 57
  • 93
user3472065
  • 1,259
  • 4
  • 16
  • 32

8 Answers8

23

if $FILE contains the file name and $STRING contains the string to be searched, then you can display if the file matches using the following command:

if [ ! -z $(grep "$STRING" "$FILE") ]; then echo "FOUND"; fi
fstab
  • 4,801
  • 8
  • 34
  • 66
  • 5
    You should quote your variables: the real data may not be just a single word, and filenames can contain whitespace. Useless use of cat: grep takes filename arguments. – glenn jackman Apr 04 '14 at 11:54
  • 8
    imho this does does not really answer the question, since it will also report "FOUND" if $STRING is part of a line. The question was about presence of a specific **line**. `grep "^$STRING"'$'` comes to mind but is not general enough here as the STRING might contain some elements that could be mistaken as regex. This also applies to the presented solution. – Holger Brandl Aug 11 '17 at 16:23
  • 2
    I kept getting a "[: Status: binary operator expected" when using this in my own bash script. I changed it to have double square brackets and it worked – Juan Pablo Sep 18 '20 at 00:32
  • 1
    I wonder why nobody uses `fgrep` (`grep -F`) here... Then, this check will succeed particularly when the searched string is a substring of the file's line. – x-yuri Dec 21 '20 at 20:35
  • For some reason here I needed to use double [[ https://stackoverflow.com/a/26712360/2478283 – Code Slicer May 29 '22 at 18:04
10

From the grep's man:

-q, --quiet, --silent
    Quiet; do not write anything to standard output.  Exit immediately with zero status if any match is found, even if an error was detected.  Also see the -s or --no-messages option.
-F, --fixed-strings
    Interpret PATTERNS as fixed strings, not regular expressions.
-x, --line-regexp
    Select only those matches that exactly match the whole line.  For a regular expression pattern, this is like parenthesizing the pattern and then surrounding it with ^ and $.
# Inline
grep -q -F "$STRING" "$FILE" && echo 'Found' || echo 'Not Found'
grep -q -F "$STRING" "$FILE" || echo 'Not Found'

# Multiline
if grep -q -F "$STRING" "$FILE"; then
  echo 'Found'
else
  echo 'Not Found'
fi

if ! grep -q -F "$STRING" "$FILE"; then
  echo 'Not Found'
fi

If you need to check the whole line use -x flag:

grep -q -x -F "$STRING" "$FILE" && echo 'Found' || echo 'Not Found'
Anton Korneychuk
  • 632
  • 1
  • 7
  • 12
  • 2
    Thank you for this. I did a quick performance check on my use case, then compared it with the `awk` answer. Original runtime: 0.4s; Using `awk`: 0.3s; Using this answer: 0.09s. – Niet the Dark Absol Mar 03 '22 at 12:36
6

Poll the file's modification time and grep for the string when it changes:

while :; do
    a=$(stat -c%Y "$FILE") # GNU stat
    [ "$b" != "$a" ] && b="$a" && \
        grep -q "$STRING" "$FILE" && echo FOUND
    sleep 1
done

Note: BSD users should use stat -f%m

Cole Tierney
  • 9,571
  • 1
  • 27
  • 35
4

Try:

while : ;do
    [[ -f "$FILE" ]] && grep -q "$STRING" "$FILE" && echo "FOUND" && break
done

This will loop continuously without waiting, you may want to add a waiting period (eg 5 seconds in this example):

while : ;do
    [[ -f "$FILE" ]] && grep -q "$STRING" "$FILE" && echo "FOUND" && break
    sleep 5
done
Josh Jolly
  • 11,258
  • 2
  • 39
  • 55
  • `grep -q` should be preferred: if it finds the string, it won't have to read the whole file: `[[ -f "$f" ]] && grep -q "$str" "$f" && break` – glenn jackman Apr 04 '14 at 11:52
4

A more rigorous check to test if a given line $STRING is contained in a file FILE would be

awk '$0 == "'${STRING}'"' $FILE

This in particular addresses my concerns with @fstab's answer (which also applies to all other previous answers): It checks the complete line and not just for substring presence within a line (as done with the grep solutions from above).

The loop could be done as shown in the other answers.

Holger Brandl
  • 10,634
  • 3
  • 64
  • 63
0

I had to add ssh keys to the authorized_keys file from a script. I've found two options (the bad and the ugly :)):

for f in "$tmp/keys/"*; do
    line=`cat "$f"`
    dst_line=`fgrep "$line" "$home/.ssh/authorized_keys" || true`
    if [ "$line" != "$dst_line" ]; then
        echo "$line" >> "$home/.ssh/authorized_keys"
    fi
done
for f in "$tmp/keys/"*; do
    cat "$f" >> "$home/.ssh/authorized_keys"
done
cat "$home/.ssh/authorized_keys" | sort | uniq > "$home/.ssh/authorized_keys.new"
chmod 0600 "$home/.ssh/authorized_keys.new"
chown "$user:" "$home/.ssh/authorized_keys.new"
mv "$home"/.ssh/authorized_keys.new \
    "$home"/.ssh/authorized_keys

I thought with uniq it would be more concise, but it turned out that it's not. And the need to adjust the permissions... And the order of the lines changes... Doesn't look good.

x-yuri
  • 16,722
  • 15
  • 114
  • 161
0

Turning this comment into an answer since it seems to do what I expect (from what I understand of the original question):

cat <<EOF | grep -Fx "${search_term}"
hello
world
EOF
  • Searching for hello or world succeeds
  • Searching for ello or worl fails
Sean Allred
  • 3,558
  • 3
  • 32
  • 71
0

Another solution is to use the comm command with the -1 and -2 switches:

cat <<EOF > file.txt
line1
line2
line3
EOF

line_in_file=line1
[[ -n "$(comm -12 <(sort <<< "$line_in_file") <(sort "file.txt"))" ]] && echo 'line1 in file'

line_not_in_file=line4
[[ -n "$(comm -12 <(sort <<< "$line_not_in_file") <(sort "file.txt"))" ]] && echo 'line4 in file'

Here is the relevant section of the man page:

DESCRIPTION
       Compare sorted files FILE1 and FILE2 line by line.

       When FILE1 or FILE2 (not both) is -, read standard input.

       With no options, produce three-column output.  Column one contains lines unique to FILE1, column two contains lines unique to FILE2, and column three contains lines common to both files.

       -1     suppress column 1 (lines unique to FILE1)

       -2     suppress column 2 (lines unique to FILE2)
redxef
  • 492
  • 4
  • 12