Extracting substring from inside bracketed string, where the substring may have spaces

Viewed 34

I've got an application that has no useful api implemented, and the only way to get certain information is to parse string output. This is proving to be very painful...

I'm trying to achieve this in bash on SLES12.


Given I have the following strings:

QMNAME(QMTKGW01)                                          STATUS(Running)
QMNAME(QMTKGW01)                                          STATUS(Ended normally)

I want to extract the STATUS value, ie "Ended normally" or "Running". Note that the line structure can move around, so I can't count on the "STATUS" being the second field.

The closest I have managed to get so far is to extract a single word from inside STATUS like so

echo "QMNAME(QMTKGW01)  STATUS(Running)" | sed "s/^.*STATUS(\(\S*\)).*/\1/"

This works for "Running" but not for "Ended normally"

I've tried switching the \S* for [\S\s]* in both "grep -o" and "sed" but it seems to corrupt the entire regex.

3 Answers

This is purely a regex issue, by doing \S you requested to match non-white space characters within (..) but the failing case has a space between which does not comply with the grammar defined. Make it simple by explicitly calling out the characters to match inside (..) as [a-zA-Z ]* i.e. zero or more upper & lower case characters and spaces.

sed 's/^.*STATUS(\([a-zA-Z ]*\)).*/\1/'

Or use character classes [:alnum:] if you want numbers too

sed 's/^.*STATUS(\([[:alnum:] ]*\)).*/\1/'
sed 's/.*STATUS(\([^)]*\)).*/\1/' file

Output:

Running
Ended normally

Extracting a substring matching a given pattern is a job for grep, not sed. We should use sed when we must edit the input string. (A lot of people use sed and even awk just to extract substrings, but that's wasteful in my opinion.)

So, here is a grep solution. We need to make some assumptions (in any solution) about your input - some are easy to relax, others are not. In your example the word STATUS is always capitalized, and it is immediately followed by the opening parenthesis (no space, no colon etc.). These assumptions can be relaxed easily. More importantly, and not easy to work around: there are no nested parentheses. You will want the longest substring of non-closing-parenthesis characters following the opening parenthesis, no mater what they are.

With these assumptions:

$ grep -oP '\bSTATUS\(\K[^)]*(?=\))' << EOF
> QMNAME(QMTKGW01)                                     STATUS(Running)
> QMNAME(QMTKGW01)                                     STATUS(Ended normally)
> EOF

Running
Ended normally

Explanation:

Command options: o to return only the matched substring; P to use Perl extensions (the \K marker and the lookahead). The regexp: we look for a word boundary (\b) - so the word STATUS is a complete word, not part of a longer word like SUBSTATUS; then the word STATUS and opening parenthesis. This is required for a match, but \K instructs that this part of the matched string will not be returned in the output. Then we seek zero or more non-closing-parenthesis characters ([^)]*) and we require that this be followed by a closing parenthesis - but the closing parenthesis is also not included in the returned string. That's a "lookahead" (the (?= ... ) construct).

Related