5

I have a string contained in a variable, and I want to extract substrings based on position relative to another substring. My solution seems to work unless the string is sent to a function as an arg. I am using the bash shell.

#!/usr/bin/bash

var0="-a check one two three"
var1="check"

function getsubstr() {
echo ${*#*"${2}"} 
}

# this prints 'one two three' which is what I want
echo ${var0#*"${var1}"}

# this prints '-a one two three', not what I want.
getsubstr $var0

note that when I put echo $* in the getsubstr function it prints the same string as $var0 (-> '-a check one two three'), and that when I put echo $2 in the getsubstr function is prints the same string as $var1 (-> 'check'). So, it seems to me that I'm asking to print the same substring in both circumstances.

A further conundrum is that if, instead of echo ${*#*"${2}"} in the getsubstr function I use echo ${*%"${2}"*} , I get the exact same result.

Any help in understanding this behavior would be greatly appreciated.

BTW, I realize that ${*:3} inside the getsubstr function works to return the substring I want, but I'm trying to understand the #*<regexp> and %<regextp>* behavior.

2 Answers 2

6

Your getsubstr $var0 is passing 5 args to the function.
Also, $* and $@ tests each individual $1 $2 etc.. arg against the # patttern.

Regarding RegEx in bash: I've added some examples at the end, and btw, '*' is only a special regex char when it is used in a regex context, ie. when using =~ . In your first use of * in ${*, the asterisk's special use is as the (psuedo)name of a var which expands to a concatenation of all the vars: $1 $2 $...etc...
Your second use of an asterisk, in #*"${2}", means "$2" preceded by anything including nothing, is to be matched against each passed $1 etc arg separately/individually.

The following script may help with $@ and $* (by example)...

#!/bin/bash
#   
getsubstr() {
  echo -n " ${#@} args";
  [[ "$1$2$3$4$5$6" == *\ * ]] && echo " (with embedded spaces)" || echo " (no spaces)"
  echo '                  "${*}"          '\|"${*}"\|
  echo '                   ${*}           '\|${*}\|
  echo '                  "${@}"          '\|"${@}"\|
  echo '                   ${@}           '\|${@}\|
  echo '                  "${*#*"${2}}"   '\|"${*#*"${2}"}"\|
  echo '                   ${*#*"${2}}    '\|${*#*"${2}"}\|
  echo '                  "${@#*"${2}}"   '\|"${@#*"${2}"}"\|
  echo '                   ${@#*"${2}}    '\|${@#*"${2}"}\|
  echo '                        ${*#B}    '\|${*#B}\|
  echo '                       "${*#B}"   '\|"${*#B}"\|
  echo '                        ${@#B}    '\|${@#B}\|
  echo '                       "${@#B}"   '\|"${@#B}"\|
}
var0="a B c      "
echo
echo -n "Passing "; getsubstr "$var0" ; echo
echo -n "Passing "; getsubstr  $var0  ; echo
echo -n "Passing "; getsubstr "$var0" "$var0" ; echo
echo -n "Passing "; getsubstr  $var0   $var0  ; echo
echo
exit 
###################################################################

RegEx in bash

# Regex checks: "=~" uses extended regular expression
#+  Parenthesized subexpressions within the regular expression are saved
#+  in the array variable BASH_REMATCH
#+  $BASH_REMATCH / ${BASH_REMATCH[0]} is the string matching the entire regular expression. 
#+  ${BASH_REMATCH[n]} is the sub string matching the nth parenthesized subexpression

  [[ "abcdef" =~ (.)(.)(.) ]] && echo "# $BASH_REMATCH"
# abc

  [[ "abcdef" =~ (.)(.)(.) ]] && echo "# ${BASH_REMATCH[0]}"
# abc

  [[ "abcdef" =~ (.)(.)(.) ]] && echo "# ${BASH_REMATCH[2]}"
# b

  [[ "abcdef" =~ (.)(.)(.) ]] && echo "# ${BASH_REMATCH[@]}"
# abc a b c

1
  • bear - Thanks so much. Your answer is informative and (to me) complete. I especially appreciate your examples.
    – bev
    Commented Apr 9, 2011 at 3:34
4

Update with explanation

The reason you are seeing this type of behavior is because $* or even $@ expands to all the positional parameters: $1, $2 etc. When you attempt to do a Parameter Expansion (PE) on either one of those two special vars, you are applying the PE to each positional parameter and not a single string.

Excerpt from man bash

${parameter#word}
Remove matching prefix pattern. The word is expanded to produce a pattern just as in pathname expansion. If the pattern matches the beginning of the value of parameter, then the result of the expansion is the expanded value of parameter with the shortest matching pattern (the #'' case) or the longest matching pattern (the##'' case) deleted. If parameter is @ or *, the pattern removal operation is applied to each positional parameter in turn, and the expansion is the resultant list.

Essentially what you are doing is this:

getsubstr() { 
  tmp=$2
  for arg; do 
    printf "%s " ${1#*$tmp}
    shift
  done
}

The following function works by setting $* to a temp var $tmp because you are now applying the PE to a normal variable once.

getsubstr() {
  tmp=$*
  echo ${tmp#*$2}
}

P.S.

Don't use function as it's not POSIX and actually completely unnecessary if you are already using () after your function name.

P.P.S

This actually has nothing to do with regular expressions but rather globular expressions. More formally these are known as Parameter Expansions

5
  • thanks for your answer, and the extra info. I've looked around for more on the difference between regular expressions and globular expressions and have come up blank. Can you expand a little on what distinguished each? Thanks.
    – bev
    Commented Apr 9, 2011 at 3:13
  • @bev look at the term 'wildcard', it's the same as globular expressions.
    – SiegeX
    Commented Apr 9, 2011 at 8:37
  • This site (dartmouth.edu/~rc/classes/ksh/print_pages.shtml) says: "Wildcards are not full regular expressions. Sed, grep, awk etc. work with more flexible (and more complex) string matching operators." which seems to imply that wildcards are a subset of the full set of regexps, i.e. are regular expressions, but limited in the number of them as well as the commands that use them. Would you agree with that?
    – bev
    Commented Apr 11, 2011 at 2:17
  • I'm not being difficult. I just want to understand whether wildcards and regexps are different in kind or just the same kind of thing, but in the context of bash, used in different circumstances. For example, in the source code for bash, are wildcards implemented using regexps?
    – bev
    Commented Apr 11, 2011 at 2:18
  • @bev: Although a functionally equivalent regular expression can be made to match that of a globular expression, the reverse is not true. Globs are not a regular language and hence not a regular expression.
    – SiegeX
    Commented Apr 11, 2011 at 4:52

You must log in to answer this question.

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