2

I'm trying to print only a matching word that are found through a regular expression. Below, I want all the OPENSSL_NO_* options that are present in the source code:

$ grep -IR OPENSSL_NO
fuzz/asn1.c:#ifndef OPENSSL_NO_RFC3779
fuzz/asn1.c:#ifndef OPENSSL_NO_RFC3779
fuzz/asn1.c:#ifndef OPENSSL_NO_CMS
fuzz/asn1.c:#ifndef OPENSSL_NO_DH
fuzz/asn1.c:#ifndef OPENSSL_NO_EC
fuzz/asn1.c:#ifndef OPENSSL_NO_RFC3779
fuzz/asn1.c:#ifndef OPENSSL_NO_OCSP
fuzz/asn1.c:#ifndef OPENSSL_NO_TS
fuzz/asn1.c:#ifndef OPENSSL_NO_DH
fuzz/asn1.c:#ifndef OPENSSL_NO_DSA
...

When I try to trim the output by only printing the full word:

$ grep -oIR "OPENSSL_NO*"
fuzz/asn1.c:OPENSSL_NO
fuzz/asn1.c:OPENSSL_NO
fuzz/asn1.c:OPENSSL_NO
fuzz/asn1.c:OPENSSL_NO
fuzz/asn1.c:OPENSSL_NO
...

And when I try awk it prints the whole line:

$ grep -IR OPENSSL_NO | awk '/OPENSSL_NO[_A-Z0-9_]/{ print $0 }'
fuzz/asn1.c:#ifndef OPENSSL_NO_RFC3779
fuzz/asn1.c:#ifndef OPENSSL_NO_RFC3779
fuzz/asn1.c:#ifndef OPENSSL_NO_CMS
fuzz/asn1.c:#ifndef OPENSSL_NO_DH
fuzz/asn1.c:#ifndef OPENSSL_NO_EC
...

And:

$ grep -IR OPENSSL_NO | awk '/\<OPENSSL_NO\>'
awk: line 1: runaway regular expression /\<OPENSSL_ ...

And:

$ grep -Eo -IR 'OPENSSL_NO_[A-Z0-9_]'
fuzz/asn1.c:OPENSSL_NO_R
fuzz/asn1.c:OPENSSL_NO_R
fuzz/asn1.c:OPENSSL_NO_C
fuzz/asn1.c:OPENSSL_NO_D
fuzz/asn1.c:OPENSSL_NO_E

And:

$ grep -IR OPENSSL_NO | sed -n 's/.*\(OPENSSL_NO\).*/\1/p'
OPENSSL_NO
OPENSSL_NO
OPENSSL_NO
OPENSSL_NO
OPENSSL_NO
...

How does one match a word and then only print the word?


This is apparently a painful task given how many questions there are about it. Here are various question I have not been able to adapt to my [simple?] problem:

3 Answers 3

6

* in regular expressions means 0 or more of the preceding atom. You're confusing it with the * shell wildcard operator where it means 0 or more characters.

OPENSSL_NO_* means OPENSSL_NO followed by 0 or more underscores.

You'd want:

grep -o 'OPENSSL_NO_.*'

Where . is the regexp operator to match a single character.

Or:

grep -o 'OPENSSL_NO_[[:alnum:]]*'

for 0 or more alphanumerical characters (in any alphabetic script supported by the locale).

Extended regular expressions (as in grep -E) also have + for 1 or more of the preceding atom. With Basic regular expressions (without -E), you can use \{1,\} instead.

Some grep implementations also have \w which means any alphanumeric character or underscore though note that in some versions of some implementations, it's limited to the A-Za-z0-9 one.

In any case, note that -o/-R are not standard options. POSIXly, you may want:

sed -n 's/.*\(OPENSSL_NO_[[:alnum:]_]\{1,\}\).*/\1/p' < file

(allowing only one occurrence per line; if there are more than one, only the rightmost one will be displayed).

That won't print the file names. For that, you could use awk instead:

find . -name '*.[hc]' -type f -exec awk 'match($0, /OPENSSL_NO_[[:alnum:]_]+/) {
  print FILENAME": "substr($0, RSTART, RLENGTH)}' {} +
1
  • Perfect, grep -o 'OPENSSL_NO_[[:alnum:]]*' worked as expected. Thanks. I thought I was having problems with a colorizer breaking something, or greedy matches.
    – user56041
    Jan 29, 2018 at 16:33
3

The * operator in regular expressions means "zero or more", so grep is perfectly happy to satisfy that condition by using "zero" additional characters.

I would extend the regular expression in some way so that grep is forced to include the rest of the term:

grep -o 'OPENSSL_NO_.*$' input

or

grep -o 'OPENSSL_NO_.*\b' input

(where in both cases, I added an additional underscore).

0
-1

I have used awk command to achieve the same

for i in {1..2}; do awk -v i="$i" '$i ~/^OPENSSL/ {print $i}' example.txt; done

output

OPENSSL_NO_RFC3779
OPENSSL_NO_RFC3779
OPENSSL_NO_CMS
OPENSSL_NO_DH
OPENSSL_NO_EC
OPENSSL_NO_RFC3779
OPENSSL_NO_OCSP
OPENSSL_NO_TS
OPENSSL_NO_DH
OPENSSL_NO_DSA
1
  • Kindly clarify why downvoted it provides the expected anwer . Also tested other user answer which was upvoted it also provides same answer as mine Jan 30, 2018 at 14:05

You must log in to answer this question.