8

I want to extract portion of a string matching a regex. Consider the following code that works correctly:

regex="ss"
string="blossom"
echo $string | sed "s/^.*\($regex\).*$/\1/"

Output is:

ss

However if the regex matches nothing the whole string is returned.

regex="aa"

Output:

blossom

This is incorrect. When there is no match, nothing should be returned. How can this be accomplished?

1
  • 4
    sed default action is to print the pattern space unless the -n switch is used. Nov 6, 2015 at 14:03

5 Answers 5

8

As choroba said, sed will always print the line, by default, with any substitutions that matched. You could do what you want with:

regex="ss"
string="blossom"
echo $string | sed -n "s/^.*\($regex\).*$/\1/p"

The -n tells sed not to print the line, then the p at the end of the s/ command tells sed to print the line, with replacements, if it matched anything.

2

Your interpretation is wrong. You told sed to replace something by something else; it it didn't find anything to replace, why should it delete anything? In other words, substitution replaces what matches, it doesn't touch non-matching lines. Or, yet another reformulation: sed correctly replaced all lines containing aa by aa.

0

the behavior of sed is correct , it print out the input string EDITED . obviously if nothing match on the input string with the given token , the result is the input string as it is . when regex="aa" the evaluated token do not match and print out = "blossom".

0

sed has an implicit p command that is executed at the end of each cycle. This means that it will print the current contents of the "pattern space" (the buffer that holds the data and to which you apply most sed commands) every time it comes to the end of the editing script. You may turn off this behavior with the -n option.

Let's take you description of what should happen, and turn that into a sed command:

I want to extract portion of a string matching a regex.

When there is no match, nothing should be returned.

sed -e '/.*\('"$regex"'\).*/!d' \
    -e 's//\1/'

The first expression says "if the line does not match the regular expression, it should be deleted". When sed executes the d command, the current cycle ends and the next line is immediately read, if there is one. The second expression says "replace the line with the bit that matches the capture group" (an empty regular expression means "use the most recent expression"). sed will only execute this substitution if the d command in the first expression didn't trigger.

After the substitution, the implicit p command at the end will print the modified pattern space.

As others have said, this could be compressed down into

sed -n 's/.*\('"$regex"'\).*/\1/p'

Note that there is no need to anchor the expression.

but I would probably prefer

grep -o -e "$regex"

or

grep -o -F -e "$regex"

if the regular expression is really just a string that you want to do an exact match with, as in the question. I would prefer grep over sed here as the command is so much simpler, and therefore easier to understand and to maintain, but also because there is no code injection vulnerability in it, as in the sed code.

The -o option to grep will cause grep to output only the bits of data that matches the given expression. The option is non-standard, but commonly implemented.

-2

The following should work if you want to go without using 'sed':

[[ $string == *$regex*  ]] && echo $regex
1
  • The issue with this is that it works only for the cases when $regex is a string containing no filename globbing characters. If it's a regular expression (such as s\{2\}), then the matching against $string may not succeed (as == does a filename globbing pattern match, at least in the bash shell). There would also be issue with the final echo statement if $regex contains a pattern that matches existing filenames, e.g. .*.
    – Kusalananda
    Feb 5, 2021 at 17:54

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .