Super User is a question and answer site for computer enthusiasts and power users. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I often find this in scripts (and, I have to admit, write it myself):

a=$(echo "$x" | sed "s/foo/bar/")

or

if echo "$x" | grep -q foo
then
    ...
fi

Consider "foo" to include some regex stuff.

I feel that there should be - and most likely is - a better way to phrase this, one that does not involve two commands and a pipe but wraps the thing into some more compact expression.

I just can't find it. Anybody?

share|improve this question
    
i expect this expression is used frequently due to a combination of ignorance (not knowing alternatives) and maintainability (knowing alternatives but choosing this as the simpler to understand). i can tell at a glance what your examples do, but i need a shell reference to figure out the alternatives in grawity's and Dan McG's answers. – quack quixote Feb 4 '10 at 13:29
3  
By the way, the preferred method of doing command substitution is with $() rather than backticks. – Dennis Williamson Feb 4 '10 at 15:47
2  
It is also a good idea to quote expansions so that whitespace is protected: a="$(echo "$x" | sed "s/foo/bar/")" and if echo "$x" | grep foo; …. – Chris Johnsen Feb 4 '10 at 16:17
    
Good remarks on the $() vs. ``. I see my bash skills are not that great yet. – DevSolar Feb 4 '10 at 16:44
up vote 10 down vote accepted

Unless you assume a specific shell, there is no better way to do this than “pipe echo to tool” (or just a “tool” itself like expr); it is all you can really count on with the traditional Bourne shell and/or the POSIX shell. If you consider other shells, then there are some other built-in possibilities.

ksh has

  • extra patterns: ?(pattern-list), *(pattern-list), {n}(pattern-list), {n,m}(pattern-list), @(pattern-list), !(pattern-list);
  • the %P printf specifier to convert a extended regular expression into a pattern (and %R for extended regular expression to pattern);
  • the expr == pattern condition in [[ expr ]] tests;
  • the ${param/pattern/replacement} parameter expansion.

bash has

  • the extglob option to enable most of the extra patterns of ksh (no {n} and {n,m});
  • the expr == pattern condition (in [[ expr ]] tests);
  • the ${param/pattern/replacement} parameter expansion;
  • (in newer versions) the expr =~ extregexp condition (in [[ expr ]] tests) that can match against extended regular expressions
    • with parenthesized subexpressions and the BASH_REMATCH parameter, sed-style replacements could be done.

zsh has

  • its own extended patterns with the EXTENDED_GLOB option;
  • ksh-like extended patterns with the KSH_GLOB option;
  • the expr == pattern condition (in [[ expr ]] tests);
  • the ${pattern/pattern/replacement} parameter expansion;
  • the expr =~ extregexp condition (in [[ expr ]] tests) that can match against extended regular expressions,
    • it can use PCRE instead of plain extended regular expressions if the RE_MATCH_PCRE option is set,
    • with parenthesized subexpressions, the MATCH parameter, and the match parameter (or BASH_REMATCH with the BASH_REMATCH option set), sed-style replacements could be done;
  • the zsh/pcre module that offers pcre_compile, pcre_study, and pcre_match commands and the -pcre-match test condition (in [[ expr ]] tests);
  • the zsh/regex module that offers the -regex-match test condition (in [[ expr ]] tests).
share|improve this answer
    
Wow. As complete as one could wish for. Definitely a winner. – DevSolar Feb 4 '10 at 16:46

To replace the sed line, do something like

${a/foo/bar} or ${a//foo/bar}

In the first form, only the first instance is replaced. The second form is a global search & replace.

In your case, it would be

Instead of:

if echo $x | grep foo
then
    ...
fi

Consider using:

if [ $x =~ foo ]
then
    ...
fi

Where foo is a regular expression.

share|improve this answer
    
In which shells does this work? – Peltier Feb 4 '10 at 10:46
1  
if [ $x =~ foo ] gives an error message here. if [[ $x =~ foo ]], however, works great. Thanks! – DevSolar Feb 4 '10 at 12:07
1  
All these are bashisms, they require bash. In addition, =~ requires bash >= 3 iirc. – ℝaphink Feb 4 '10 at 13:35
1  
Iirc also, foo is not a regular expression (as in PCRE) in these examples, but uses wildcards. There's more of these bash goodness in the bash manpage, section "Parameter Expansion". I particularly appreciate things like ${a##foo} and ${a%%bar}. – ℝaphink Feb 4 '10 at 13:38
3  
The match operator =~ does use regular expressions. This is true, for example: [[ "abcdddde" =~ ^a.*d+.g* ]] (that's zero-or-more g's rather than g-wildcard, for example). – Dennis Williamson Feb 4 '10 at 15:43
sed 's/foo/bar/' <<< "foobie"

grep 'this' <<< "this or that"

While Dan McG's answers are good, they won't give exactly the same regexp support if you need it.

share|improve this answer
1  
I'm not sure if I like <<< better than the echo-pipe, but I didn't know about <<< before. +1, and thanks. – DevSolar Feb 4 '10 at 13:15
2  
That's a "here string". There's also a multi-line version called a "here document". – Dennis Williamson Feb 4 '10 at 15:46

A good posix compatible way for testing if a variable contains a pattern is:

test ${var##*foo*} || <do something>;

The syntax of the parameter expansion is:

 ${parameter##pattern}

where pattern is a shell pattern.

share|improve this answer

You must log in to answer this question.

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