bash script with grep command understanding - bash

I posted earlier with something similar to this. I am trying to check if a user is online with the command $ ./user mburkhar which prints out mburkhar is logged on. My program works correctly but if I just type $ ./user mb is also states mb is logged on. What I have is fine, but is there a way to match what the user typed in exactly instead of slightly matching the first 2 characters..?
Here is my program so you can see what I did:
# Check if a user is logged on
if [ -z $1 ] ; then
read user
else
user=$1
fi
if [ `who | cut -d" " -f1 | grep $1` ] ; then
echo "$1 is logged on"
else
echo "$1 is either not valid or logged on"
fi

To complement the accepted answer:
-w works well and is widely supported (GNU Grep, BSD Grep), but it is not POSIX-compliant.
In your case, given that your output lines contain just a username each and nothing else, using -x - to match entire lines - would make sense too (and it is POSIX-compliant).
Also, since you're searching for a literal username, it's good practice to use grep's -F option to indicate just that.
Using [ $(...) ] (or [ `...` ]) to test for nonempty output from a command is somewhat fragile and inefficient; it's better and simpler to:
use commands directly
base the test on the exit code
and suppress stdout output, if needed
grep's -q option not only suppresses stdout, but also makes the search potentially more efficient by terminating once the first match is found (with exit code 0 to indicate success):
if who | cut -d' ' -f1 | grep -Fxq "$1"; then # ...
Similarly, [ -z $1 ] is fragile in that would break if an argument with embedded whitespace is passed - not likely in this case, but it's better to get in the habit of using [[ -z $1 ]] (or, if you must remain POSIX-compliant, [ -z "$1" ]).
Outside of [[ ... ]], it makes sense to habitually double-quote variable references, such as the $1 in the grep command.
If we put it all together:
# Check if a user is logged on
if [[ -z $1 ]]; then
read user
else
user=$1
fi
if who | cut -d' ' -f1 | grep -Fxq "$1"; then
echo "$1 is logged on"
else
echo "$1 is either not valid or logged on"
fi

You can use grep's -w (--word-regexp) option to match only entire words. Replacing grep $1 with grep -w $1 in your script should fix it.

Specify the start and end of string anchors like this:
if [ -z $1 ] ; then
read user
else
user="^$1$"
fi
if [ $(who | cut -d" " -f1 | grep $user | uniq ) ] ; then
echo "$1 is logged on"
else
echo "$1 is either not valid or logged on"
fi
I also added the uniq to remove duplicate matches, in case an user got more than one tty, or [ will exit with: [: too many arguments

Related

failing bash variable matching

I have been driving myself mad on this.
I just don't see why it doesn't work when bash variables are untyped.
There should be no reason why not.
pat1=`. $NVM_DIR/nvm.sh && nvm ls | sed 's/\x1b\[[^\x1b]*m//g' | sed -e 's/[[:alpha:]|\/\)\-\>\(|[:space:]]//g' | sed 's/[-|\*]//g' | sed '/^$/d'`
pat2=`. $NVM_DIR/nvm.sh && nvm ls node | head -1 | awk '{print $2}' | cut -d v -f2`
these expand to:
~$ echo $pat1
9.5.0
9.5.0
9.5
9.5.0
4.8.7
6.12.3
8.9.4
~$ echo $pat2
9.5.0
So I want to check if the string found in $pat2 is in $pat1 string.
however it does not find it even though it clearly is in the pattern 3 times at least.
Checking for the pattern with a test like:
case "$pat1" in
*$pat2*)
echo 'match'
;;
*)
echo 'nomatch'
;;
esac
this gives:
~$ ./test.sh
nomatch
another test:
if [[ "$pat1" =~ "$pat2" ]]; then
echo 'match'
else
echo 'nomatch'
fi
again fails:
~$ ./test.sh
nomatch
Even tried some janky hacky method to no avail,
echo $pat1 | grep "$pat2" > /dev/null
if [ $? -eq 0 ]; then
echo "matched"
else
echo "nomatch"
fi
gives the result:
~$ ./test.sh
grep: brackets ([ ]) not balanced
nomatch
Been pulling my hair out today working on this.
This must be due to the fact that both variables are command substitutions, it leads me to believe it is comparing the actual commands themselves rather than their output stored in the variables.
So even though they echo the output, i think it may be comparing the string of the commands itself.
am I wrong? can anyone explain why this fails to match and if its possible with bash to compare the output of two commands in a string/sub-string type of comparison?
I put your pat1 and pat2 values in files and didn't need to use | anywhere.
#!/bin/bash
pat1=$(cat pat1|tr '\012' ' ')
pat2=$(<pat2)
#tst pat2="10.1"
echo "$pat1"
case "$pat1" in
*$pat2*)
echo 'match'
;;
*)
echo 'nomatch'
;;
esac
Produces
#dbg:pat2=9.5.0 pat1=9.5.0 9.5.0 9.5 9.5.0 4.8.7 6.12.3 8.9.4
match
If I un-comment the #tst pat2 line, the output is
#dbg:pat2=10.1 pat1=9.5.0 9.5.0 9.5 9.5.0 4.8.7 6.12.3 8.9.4
nomatch
Is there any chance your data was created for MS Windows? If so, check for Windows line endings with
head -10 file | cat -vet
if you see ^M$ at the end of each line, then run dos2unix file .... (multiple file can be processed in one invocation, and the original file is overwritten with the same name.
IHTH
nomatch
The grep version is almost right, but you need to quote $pat1, otherwise the newlines will be converted to spaces and everything will be on one line.
if echo "$pat1" | grep -F -q "$pat2"
then
echo "matched"
else
echo "nomatch"
fi
I've also added the -F option to grep so it will treat $pat2 as a fixed string rather than a regular expression.
DEMO

grep, else print message for no matches

In a bash script, I have a list of lines in a file I wish to grep and then display on standard out, which is easiest done with a while read:
grep "regex" "filepath" | while read line; do
printf "$line\n"
done
However, I would like to inform the user if no lines were matched by the grep. I know that one can do this by updating a variable inside the loop but it seems like a much more elegant approach (if possible) would be to try to read a line in an until loop, and if there were no output, an error message could be displayed.
This was my first attempt:
grep "regex" "filepath" | until [[ -z ${read line} ]]; do
if [[ -z $input ]]; then
printf "No matches found\n"
break
fi
printf "$line\n"
done
But in this instance the read command is malformed, and I wasn't sure of another way the phrase the query. Is this approach possible, and if not, is there a more suitable solution to the problem?
You don't need a loop at all if you simply want to display a message when there's no match. Instead you can use grep's return code. A simple if statement will suffice:
if ! grep "regex" "filepath"; then
echo "no match" >&2
fi
This will display the results of grep matches (since that's grep's default behavior), and will display the error message if it doesn't.
A popular alternative to if ! is to use the || operator. foo || bar can be read as "do foo or else do bar", or "if not foo then bar".
grep "regex" "filepath" || echo "no match" >&2
John Kugelman's answer is the correct and succinct one and you should accept it. I am addressing your question about syntax here just for completeness.
You cannot use ${read line} to execute read -- the brace syntax actually means (vaguely) that you want the value of a variable whose name contains a space. Perhaps you were shooting for $(read line) but really, the proper way to write your until loop would be more along the lines of
grep "regex" "filepath" | until read line; [[ -z "$line" ]]; do
... but of course, when there is no output, the pipeline will receive no lines, so while and until are both wrong here.
It is worth amphasizing that the reason you need a separate do is that you can have multiple commands in there. Even something like
while output=$(grep "regex filepath"); echo "grep done, please wait ...";
count=$(echo "$output" | wc -l); [[ $count -gt 0 ]]
do ...
although again, that is much more arcane than you would ever really need. (And in this particular case, you would want probably actually want if , not while.)
As others already noted, there is no reason to use a loop like that here, but I wanted to sort out the question about how to write a loop like this for whenever you actually do want one.
As mentioned by #jordanm, there is no need for a loop in the use case you mentioned.
output=$(grep "regex" "file")
if [[ -n $output ]]; then
echo "$output"
else
echo "Sorry, no results..."
fi
If you need to iterate over the results for processing (rather than just displaying to stdout) then you can do something like this:
output=$(grep "regex" "file")
if [[ -n $output ]]; then
while IFS= read -r line; do
# do something with $line
done <<< "$output"
else
echo "Sorry, no results..."
fi
This method avoids using a pipeline or subshell so that any variable assignments made within the loop will be available to the rest of the script.
Also, i'm not sure if this relates to what you are trying to do at all, but grep does have the ability to load patterns from a file (one per line). It is invoked as follows:
grep search_target -f pattern_file.txt

shell script grep to grep a string

The output is blank fr the below script. What is it missing? I am trying to grep a string
#!/bin/ksh
file=$abc_def_APP_13.4.5.2
if grep -q abc_def_APP $file; then
echo "File Found"
else
echo "File not Found"
fi
In bash, use the <<< redirection from a string (a 'Here string'):
if grep -q abc_def_APP <<< $file
In other shells, you may need to use:
if echo $file | grep -q abc_def_APP
I put my then on the next line; if you want your then on the same line, then add ; then after what I wrote.
Note that this assignment:
file=$abc_def_APP_13.4.5.2
is pretty odd; it takes the value of an environment variable ${abc_def_APP_13} and adds .4.5.2 to the end (it must be an env var since we can see the start of the script). You probably intended to write:
file=abc_def_APP_13.4.5.2
In general, you should enclose references to variables holding file names in double quotes to avoid problems with spaces etc in the file names. It is not critical here, but good practices are good practices:
if grep -q abc_def_APP <<< "$file"
if echo "$file" | grep -q abc_def_APP
Yuck! Use the shell's string matching
if [[ "$file" == *abc_def_APP* ]]; then ...

Command not acting properly in script

The command:
value=${value%?}
will remove the last character from a variable.
Is there any logical reason why it would not work from within a script?
In my script it has no effect whatsoever.
if [[ $line =~ "What I want" ]]
then
if [[ $CURRENT -eq 3 ]]
then
echo "line is " $line
value=`echo "$line" | awk '{print $4}'`
echo "value = "$value
value=${value%?}
echo "value = $value "
break
fi
fi
I cant post the whole script, but this is the piece I refer to. The loop is being entered properly, but the 2 echo $value lines return the same thing.
Edit - this question still stands. The code works fine line bu line in a terminal, but all together in a script it fails.
Echo adds an extra line character to $value in this line:
value=`echo "$line" | awk '{print $4}'`
And afaik that extra char is removed with %?, so it seems it does not change anything at all.
Try echo -n instead, which does not add \n to the string:
value=`echo -n "$line" | awk '{print $4}'`
Since you have provided only the relevant part in the code and not the whole file, I'm going to assume that the first line of your file reads `#!/bin/sh'. This is your problem. What you are trying to do (parameter expansion) is specific to bash, so unless /bin/sh points to bash via a symlink, then you are running the script in a shell which does not understand bash parameter expansion.
To see what /bin/sh really is you can do: ls -l /bin/sh. And to remedy the situation, force the script to run in bash by changing the `shebang' at the top to read `#!/bin/bash'

Error using [[ ]] and -eq

Shell script snippet:
tagSearch= $(grep '^\#ctags$' ./"$1" | wc -l)
if [[ $tagSearch -ne "0" ]]
then
...
fi
Results in:
line 2: /bb/bin/1: Permission denied
Context:
I'm trying to detect whether a particular pattern exists in a file so I can take a particular action.
I understand the error I'm getting, the detection is working but the script is trying to evaluate the result '1' and run the program '1' in my path. This isn't what I want. How do I get the behavior I'm looking for?
The problem is
tagSearch= $(grep '^\#ctags$' ./"$1" | wc -l)
----------^
You can't use spaces around the equal sign; what you're actually doing here is to temporarily set tagSearch to the empty string in the environment, then invoking grep '^\#ctags$' ./"$1" | wc -l, then trying to run that as a command since the $() will have inserted the result into the command line.
tagSearch=$(grep '^\#ctags$' ./"$1" | wc -l)
Variable assignments in the bash shell should not have a space after the equals. Actually it should never have whitespace in it at all. See below.
tagSearch=$(grep '^\#ctags$' "./$1" | wc -l)
if [[ $tagSearch -ne 0 ]]
then
...
fi
Not important to your error but also of note, when using the double bracket syntax, you don't need to quote that zero any more than the variable you are comparing it with.
Actually your whole code could be re-factored using grep's quite mode and evaluating the return code to see if you got any matches:
if grep '^\#ctags$' "./$1"
then
...
fi
Actually you can have that simpler, because the return code of grep will be 0 if something is found (1 otherwise), so you don't need wc -l. And you can just write:
if `grep -q pattern file`; then echo "yes"; else echo "no"; fi;

Resources