grep statement is not working inside case statement

Viewed 1514

I wanted to make a shell script that accepts a vowel and prints the number of occurrences of that vowel inside the text file "abc.txt".

The following script works perfectly (a script to print the number of occurrences of the vowel "a" inside the text file "abc.txt"):

#!/bin/bash
grep -o [aA] abc.txt|wc -l

But I want to implement this for all the vowels so I did this:

#!/bin/bash
echo -n "Enter the desired vowel: "
read ch
case ch in
a) grep -o [aA] abc.txt|wc -l;;
A) grep -o [aA] abc.txt|wc -l;;
e) grep -o [eE] abc.txt|wc -l;;
.
.
.
U) grep -o [uU] abc.txt|wc -l;;
esac

The code executes but after I enter the desired vowel nothing is displayed. I've also tried this (but the results are the same as those of the code above):

#!/bin/bash
x=0
echo -n "Enter the desired vowel: "
read ch
case ch in
a) x=grep -o [aA] abc.txt|wc -l;echo $x;;
A) x=grep -o [aA] abc.txt|wc -l;echo $x;;
e) x=grep -o [eE] abc.txt|wc -l;echo $x;;
.
.
.
U) x=grep -o [uU] abc.txt|wc -l;echo $x;;
esac

I'm lost as to why the grep statements aren't displaying anything when I put them inside a case statement.

3 Answers

Multiple issues, but the one mostly causing your problem is not using a variable in the case construct. Use ch is just a constant and does not match any of the expressions below.

case "$ch" in
#    ^^^^^ This needs to be a variable used in read command

Also, to store the output of a command, you need to use command-substitution syntax of type $(cmd). Also instead of grep .. | wc -l you can just use the -c flag to return the total count of matched strings.

x=$(grep -oc '[aA]' abc.txt); echo "$x"

(or) an even improved grep command would be to enable case-insensitive match with the -i flag

x=$(grep -oci 'a' abc.txt); echo "$x"

Your problem is not the case statement, but that you're using the variable assignment in a wrong way

x=grep -o [aA] abc.txt|wc -l;echo $x

you're assigning grep to the variable x for running -o [aA] abc.txt, as var=something command assigns the variable just for running command.

This of course doesn't make sense, but you can be glad you didn't try something like x=something rm * which would have deleted your files.

The correct syntax is

x="`grep -o '[aA]' abc.txt|wc -l`"

which means execute grep|wc in a subshell and assign the result to the variable x. I added quotes, as you get a problem without quotes when your command does not return anything, as x= is a syntax error while x="" is perfectly fine.

In bash you have the nice syntax (which can be nested)

x="$(grep -o '[aA]' abc.txt|wc -l)"

which does the same. But be sure to begin your script with #!/bin/bash, as /bin/sh often is another shell than bash where the syntax may not work.

The $() should work on every fully POSIX compatible shell, but /bin/sh may not be fully compatible, so using a specific shell is a good idea anyway.

None of your options were "ch", so no lines executed. If you use "$ch" then put in something that matched one of your cases, it would have assigned "grep" to x, then tried to execute -o and likely thrown an error.

Others have already explained that you should be using a contruct like x="$(grep ...)", as well as the need for quotes around your character sets, so I won't beat you up about those any more. :)

If you are going to use grep, you might try -i instead of character classes for a case-insensitive match.

If possible, try to restructure the logic of your real-world problem to simplify the code when you can. it might be better to do this:

echo -n "Enter the desired vowel: "
read ch
grep -io "$ch" abc.txt | wc -l

That needs no case statement at all, or a separate echo to output the count. If you do decide you need the case statement, it is also sometimes helpful to simplify the code to reduce redundancies.

#!/bin/bash
typeset -l ch # forces value to lowercase to make case easier
file=abc.txt  # used more than once, so put in a var to ease maintenance
echo -n "Enter the desired vowel: "
read ch       # will be lowercase no matter what they enter
case "$ch" in # so we only have to match lower cases
[aeiou]) grep -io "$ch" | wc -l ;; # reads stdin, writes to stdout
*) echo "That's not a valid vowel" >&2 # write to STDERR to keep separate
   exit 1 ;;                           # handle invalid inputs
esac < $file > match.count # all case I/O managed in one place here
echo "There are $(<match.count) $ch's in $file"

BTW, I really like your use of -o with wc -l to count occurrances rather than matching lines. I almost suggested -c in grep to eliminate the extra command until I looked again. Nice. :)

Related