How to search an expression in a file from a bash script? - bash

I have a bash script.
I need to look if "text" exists in the file and do something if it exists.

If you need to execute a command on all files containing the text, you can combine grep with xargs. For example, this would remove all files containing "yourtext":
grep -l "yourtext" * | xargs rm
To search a single file, use if grep ...
if grep -q "yourtext" yourfile ; then
# Found
fi

Something like the following would do what you need.
grep -w "text" file > /dev/null
if [ $? -eq 0 ]; then
#Do something
else
#Do something else
fi

grep is your friend here

You can put the grep inside the if statement, and you can use the -q flag to silence it.
if grep -q "text" file; then
:
else
:
fi

cat <file> | grep <"text"> and check the return code with test $?
Check out the excellent:
Advanced Bash-Scripting Guide

just use the shell
while read -r line
do
case "$line" in
*text* )
echo "do something here"
;;
* ) echo "text not found"
esac
done <"file"

Related

Check if file has been modified

How can I validate that this replace command succeeded:
perl -pi -e 's/contoso/'"$hostname"'/g' /etc/inet/hosts
I have tried checking the return value:
if [ $? -eq 0 ]; then
echo "OK"
else
echo "Error."
fi
But the return value is not being set when the command fails.
Thanks
One option is to check, if file has been modified. You can achieve with adding extension of backup file to -i option:
perl -pi.orig -e 's/contoso/'"$hostname"'/g' /etc/inet/hosts
This command will store original content of /etc/inet/hosts into /etc/inet/hosts.orig. Then run the specified command. Then you can check if the files are different with, for example cmp command:
if ! cmp -s foo.txt foo.txt.orig; then
echo OK
else
echo ERROR
fi
Remove the .orig file after that.
The other option is to modify the script to read the content of the file, replace required entry, check is change actually happened and return proper status at the end to verify in the shell using $?. You have been given solution in this answer.
I don't know Perl, but you can manage multiple case of "error" (no match/no way to write in file) with a little Bash script like that :
#!/bin/bash
FILE="/etc/inet/hosts"
SEARCH="contoso"
REPLACE="$hostname"
NB=$(grep -c $SEARCH $FILE)
if [ $NB -ne 0 ]; then
perl -pi -e 's/${SEARCH}/'${REPLACE}'/g' "$FILE" && echo "${NB} replaced" || echo "Error (permission maybe)"
else
echo "No match in file"
fi
I think there is a better way by improving the Perl code or by using the sed command. But it should works.
If you expect your perl script to return a value that has some meaning, you will need to write your perl script to return a meaningful value. In your case, perhaps something as simple as:
perl -p -e 's/contoso/'"$hostname"'/g; $rv=1 if $&; END{ exit !$rv }'
Generally checksums is a very efficient way to detect changes in files.
md5sum [filename]
root#miaoulis:~# echo 'line 1' >>1.txt
root#miaoulis:~# md5sum 1.txt
5c2ce561e1e263695dbd267271b86fb8 1.txt
root#miaoulis:~# echo 'line 2' >>1.txt
root#miaoulis:~# md5sum 1.txt
c7253b64411b3aa485924efce6494bb5 1.txt
I guess the sum could be extracted from the output with AWK
root#miaoulis:~# echo $(md5sum 1.txt) | awk 'BEGIN{FS=" *"}{print "MD5:",$1}'
MD5: c7253b64411b3aa485924efce6494bb5
root#miaoulis:~# echo $(md5sum 1.txt) | awk 'BEGIN{FS=" *"}{print "filename:",$2}'
filename: 1.txt
FS=" *" instructs AWK to split the string on the occurrence of one or more spaces. $1 will be the MD5, $2 will be the filename.
MD5 checksum works fast for any size of file. The downside is that you don't really detect what exactly changed in the file, only the fact that it has changed. Should be good enough for most scenarios.

How to match a folder name and use it in an if condition using grep in bash?

for d in */ ; do
cd $d
NUM = $(echo ${PWD##*/} | grep -q "*abc*");
if [[ "$NUM" -ne "0" ]]; then
pwd
fi
cd ..
done
Here I'm trying to match a folder name to some substring 'abc' in the name of the folder and check if the output of the grep is not 0. But it gives me an error which reads that NUM: command not found
An error was addressed in comments.
NUM = $(echo ${PWD##*/} | grep -q "*abc*"); should be NUM=$(echo ${PWD##*/} | grep -q "*abc*");.
To clarify, the core problem would be to be able to match current directory name to a pattern.
You can probably simply the code to just
if grep -q "*abc*" <<< "${PWD##*/}" 2>/dev/null; then
echo "$PWD"
# Your rest of the code goes here
fi
You can use the exit code of the grep directly in a if-conditional without using a temporary variable here ($NUM here). The condition will pass if grep was able to find a match. The here-string <<<, will pass the input to grep similar to echo with a pipeline. The part 2>/dev/null is to just suppress any errors (stderr - file descriptor 2) if grep throws!
As an additional requirement asked by OP, to negate the conditional check just do
if ! grep -q "*abc*" <<< "${PWD##*/}" 2>/dev/null; then

Executing code in bash only if a string is not found in a file

I'm trying to execute a block of code only if the string SVN_BRANCH is not found in /etc/profile. My current code looks like the following:
a = cat /etc/profile
b = `$a | grep 'SVN_BRANCH'`
not_if "$b"
{
....
...
...
}
This fails as given. How should it be done?
grep can take file as an argument, you don't need to cat the file and then pass the content to grep with pipe, that's totally unnecessary.
This is an example of if else block with grep:
if grep -q "pattern" filepath;then
echo "do something"
else
echo "do something else"
fi
Note:
-q option is for quite operation. It will hide the output of grep command (error will be printed).
If you want it to not print any errors too then use this:
if grep -sq "pattern" filepath;then
Or this:
if grep "pattern" filepath >/dev/null 2>&1;then
>/dev/null is to redirect the output to /dev/null
2>&1 redirects both stderr and stdout
you can use the exit code of the grep command to determine whether to execute your code block like this
cat /etc/profile | grep SVN_BRANCH 2>&1 >/dev/null || {
....
....
}

Redirect grep output to file

I am not sure as to why that redirection provided in the code does not work. Every time I run the script, the output file is always empty. Does anyone have an idea on that?
Thanks.
#!/bin/sh
LOOK_FOR="DefaultProblem"
FILES=`ls plugins/*source*.jar`
for i in $FILES
do
# echo "Looking in $i ..."
unzip -p $i | grep -i $LOOK_FOR > output #> /dev/null
if [ $? == 0 ]
then
echo ">>>> Found $LOOK_FOR in $i <<<<"
fi
done
You may want to use >> (append) instead of > (overwrite) for redirection as:
unzip -p $i | grep -iF "$LOOK_FOR" >> output
Since you're executing this command in a loop and overwriting file output every time, it might be blank in the end if very last command with grep doesn't find any matching line in unzip output.
You have three problems
Don't try to parse the output of ls. Instead just use for i in plugins/*source*.jar The major reason is that your script will completely and utterly break on any files that have spaces in their names. See this link for a litany of reasons why not to parse ls
You need to use >> instead of > as the latter will overwrite the output file on each iteration of the loop. The former will append to it
Use more quotes! You'll want to quote your variables to make sure they aren't subjected to word splitting
Also, you can inline the if test. So putting it all together we have:
#!/bin/sh
LOOK_FOR="DefaultProblem"
for i in plugins/*source*.jar
do
# echo "Looking in $i ..."
if unzip -p "$i" | grep -i "$LOOK_FOR" >> output #> /dev/null
then
echo ">>>> Found $LOOK_FOR in $i <<<<"
fi
done
You can redirect the output of the entire loop:
#!/bin/sh
LOOK_FOR="DefaultProblem"
FILES=`ls plugins/*source*.jar`
for i in $FILES ; do
# echo "Looking in $i ..." 1>&2
unzip -p $i | grep -i $LOOK_FOR
if [ $? == 0 ] ; then
echo ">>>> Found $LOOK_FOR in $i <<<<" 1>&2
fi
done > output
Note that I've redirected the diagnostic messages to stderr.
Instead of a for loop and an if conditional you can do everything in one find command
find /path/to/plugins -name "*source*.jar" -exec sh -c 'unzip -l "{}" | grep -q DefaultProblem' \; -print

In a unix box, I am taking a list of files as input. If it is found, return the path otherwise return a message "filename file not found"

I have used the find command for this, but it doesnt return any message when a file is not found.
And I want the search to be recursive and return a message "not found" when a file is not found.
Here's the code I have done so far. Here "input.txt" contains the list of files to be searched.
set `cat input.txt`
echo $#
for i in $#
do
find $HOME -name $i
done
Try this:
listfile=input.txt
exec 3>&1
find | \
grep -f <( sed 's|.*|/&$|' "$listfile" ) | \
tee /dev/fd/3 | \
sed 's|.*/\([^/]*\)$|\1|' | \
grep -v -f - "$listfile" | \
sed 's/$/ Not found/'
exec 3>&-
open file descriptor 3
find the files
see if they're on the list (use sed to
send a copy of the found ones to file descriptor 3
strip off the directory name
get a list of the ones that don't appear
add the "Not found" message
close file descriptor 3
Output looks like:
/path/to/file1
/path/somewhere/file2
foo Not found
bar Not found
No loops necessary.
Whats wrong with using a script. I hope this will do.
#!/bin/bash -f
for i in $#
do
var=`find $HOME -name $i`
if [ -z "$var"]
then
var="File not found"
fi
echo $var
done
You can use the shell builtin 'test' to test the existence of a file. There is also an alternative syntax using square brackets:
if [ -f $a ]; then # Don't forget the semicolon.
echo $a
else
echo 'Not Found'
fi
Here is one way - create a list of all the files to grep against. If your implementation supports
grep -q otherwise use grep [pattern] 2&>1 >/dev/null....
find $HOME -type f |
while read fname
do
echo "$(basename $fname) $fname"
done > /tmp/chk.lis
while read fname
do
grep -q "^$fname" /tmp/chk.lis
[ $? -eq 0 ] && echo "$fname found" || echo "$fname not found"
done < /tmp/chk.lis
All of this is needed because POSIX find does not return an error when a file is not found
perl -nlE'say-f$_?$_:"not found: $_"' file

Resources