0

I need to extract a word from a line in a shell script, I've seen similar examples though none of which worked.

Take the example sentence

word1 word2 word3/pattern/word4.word5 word6

Given pattern it should return word3/pattern/word4.word5 without the whitespace on either side.

In addition it should constrain that the line can't start with certain characters, #.

I managed to find a grep command to isolate the line

grep "^[^#].*pattern" $FILE

will return all the lines which don't start with # and contain pattern. How can I then extract the word from this line?

edit: using ubuntu 20.04

Also regarding the sentence, the example is more or less exactly how it should look. The pattern will appear at most once per line, and will be separated from other words by whitespace. Given a line I want to return the word containing the pattern, taking everything left and right until you hit a whitespace, not including the whitespace.

1
  • 2
    Please edit your question and tell us what operating system you are using so we can know what tools and which implementations of those tools you have available. Do you have GNU grep for example? Is this Linux? And how exactly do you define "word"? Do you just want the longest string that contains pattern and has no whitespace characters?
    – terdon
    Mar 14 at 11:48

3 Answers 3

2

As long as the pattern doesn't match any whitespace, just grep -o '[^[:space:]]*pattern[^[:space:]]*' should do. ([^[:space:]]* matches any number of non-whitespace characters.)

Regex engines usually look for matches starting from the leftmost position, and the matches are greedy, meaning they match as long as a part of the string as possible. So all the non-whitespace to both sides of the pattern should get picked up here.

To ignore lines starting with hash signs, you can just pipe through grep -v "^#".

So e.g. if hello.txt contains

# my pattern
word1 word2 word3/pattern/word4.word5 word6

then:

% < hello.txt grep -v "^#" | grep -o '[^[:space:]]*pattern[^[:space:]]*'
word3/pattern/word4.word5
0
0

Another GNU grep approach:

grep -oP '^[^#].*\K\S*pattern\S*" "$file"

The -o flag makes grep only return the matched portion of the line, and the -P enables Perl Compatible Regular Expressions (PCRE) which give us -K for "forget everything matched up to this point". That allows us to use ^[^#] for "match any character except a # at the beginning of the line" but not include everything from the beginning in the results. PCREs also give us \S for "non-whitespace".

Taken all together this will match lines that do not begin with a #, and which contain the pattern and then will print the longest string of non-whitespace that contains the pattern.

Note that if there are multiple occurrences of pattern on the same line, this will return the right-most match because of the .* before the \K. To return the left-most match, use:

grep -oP '^[^#].*?\K\S*pattern\S*' file
0

With GNU grep:

grep -oP '^(?<!#).*?\K(\w+[/\.])+\w+' file
word3/pattern/word4.word5

The regular expression matches as follows:

Node Explanation
^ the beginning of the string
(?<! look behind to see if there is not:
# #
) end of look-behind
.*? any character except \n (0 or more times (matching the least amount possible))
\K resets the start of the match (what is Kept) as a shorter alternative to using a look-behind assertion: look arounds and Support of K in regex
( group and capture to \1 (1 or more times (matching the most amount possible)):
\w+ word characters (a-z, A-Z, 0-9, _) (1 or more times (matching the most amount possible))
[/\.] any character of: '/', '.'
)+ end of \1 (NOTE: because you are using a quantifier on this capture, only the LAST repetition of the captured pattern will be stored in \1)
\w+ word characters (a-z, A-Z, 0-9, _) (1 or more times (matching the most amount possible))
1
  • Thanks! I still don't quite understand why you are assuming that there will only be word-characters though. The OP didn't say so, and using \w means that even things like pattern1 will not be found. And now that you removed the [/.], you're not actually finding the example: this now would only return pattern and not word3/pattern/word4.word5. I would either use \S for non-whitespace or even \b for word boundary maybe.
    – terdon
    Mar 14 at 16:49

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.