1

I want to grep stuff out of an output file. For example ab and cd.

The file looks like:

asdf
ab
hgr
cd
ab
asdsda

if I now grep for ab and cd after one another like

for i in $@
do
  grep $i file
done

I get

ab
ab
cd

What I want though is

ab
cd
ab

Is there an elegant solution?

  • 1
    You can put the patterns in another file, then do something like grep -F -f patterns file.txt. With GNU grep the patterns file can be stdin. Also consider investing two minutes in learning when and how to quote your variables, it will save you many an embarrassing posts to Unix & Linux. – Satō Katsura Oct 27 '17 at 8:01
3

You get this result because loop first execute grep ab file and return all occurances of ab in first iteration and after this loop execute grep cd file and return all cd occurances in file.

You do not need a for loop. Try this:

grep -e "^ab$" -e "^cd$" file

Or use -x option to select only those matches that exactly match the whole line (from man grep, thx to Kusalananda):

grep -x -e "ab" -e "cd" file

Output will be:

ab
cd
ab

Or (assuming GNU grep or compatible as \| is not a standard BRE operator):

grep "^\(ab\|cd\)$" file

The same with GNU sed:

sed '/^\(ab\|cd\)$/!d' file
  • 1
    Also see the -x flag for grep. – Kusalananda Oct 27 '17 at 8:06
  • @Kusalananda, thx! I corrected the message in accordance with your recommendation – Egor Vasilyev Oct 27 '17 at 8:15
  • 1
    Note that \| is a GNU extension. Standardly, you can use grep -xE 'ab|cd'. In sed: sed -e '/^ab$/b' -e '/^cd$/b' -e d – Stéphane Chazelas Oct 27 '17 at 8:15
  • @Stéphane Chazelas, thx! I rolled back my answer to your edition and complete it with own comments – Egor Vasilyev Oct 27 '17 at 8:22
1

Use:

IFS='
' # to join "$*" with newline
grep -e "$*" < file

In grep -- "$patterns" or grep -e "$patterns", each line of $patterns is a pattern to look for (ORed). (something you may need to bear in mind if any of the patterns in "$@" is itself multi-line)

In POSIX shells, "$*" is the concatenation of the positional parameters with the first character of $IFS.

Add the -x option if you want the patterns to match the lines exactly (as opposed to substrings within the line).

Note that lines that match both ab and cd (so without -x) will be printed only once, which would be another difference from your multi-pass approach. If you wanted them to be printed once for each matching pattern, you'd use awk instead:

awk '
  BEGIN {
    for (i = 1; i < ARGC; i++) pattern[ARGV[i]]
    ARGC = 1
  }
  {for (p in pattern) if ($0 ~ p) print}' "$@" < file

Note however that awk patterns are extended regular expressions (like in grep -E) as opposed to the basic regular expressions understood by grep without -E (for instance, x+ would match on x+ with grep, but on one or more xes with awk).

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.