5

I found an awesome answer on StackOverflow which explains how to pass an associative array to a function. Would someone be able to help me figuring out what the syntax of ${1#*=} in the code below specifies? (Borrowed from that answer by jaypal singh):

#!/bin/bash

declare -A weapons=(
  ['Straight Sword']=75
  ['Tainted Dagger']=54
  ['Imperial Sword']=90
  ['Edged Shuriken']=25
)

function print_array {
    eval "declare -A arg_array="${1#*=}
    for i in "${!arg_array[@]}"; do
       printf "%s\t%s\n" "$i ==> ${arg_array[$i]}"
    done
}

print_array "$(declare -p weapons)"

Here's my guess so far (correct me if I'm wrong on any of these):
- 1 means the first parameter passed to the function ($1 or ${1})
- # means the index of $1, which, since $1 is an associative array, makes # the keys of $1
- * means the values of of the # keys in associate array $1

That leaves the =. What does that signify? Is that like a way to show that you want # and * to mean the keys and values of the associate array?

Community
  • 1
  • 1
GreenRaccoon23
  • 3,603
  • 7
  • 32
  • 46
  • 2
    Just for the record it should be pointed out that this solution is not safe and allows for arbitrary code execution by the function from the argument string. – Etan Reisner Jul 09 '15 at 02:56

2 Answers2

12

The snippet ${1#*=} has nothing to do with associative arrays.* (Bash's syntax is super consistent, and not at all confusing)

This is a pattern match on the value of the first argument (${1}) of your function or script. Its syntax is

${variable#glob}

where

  • variable is any bash variable
  • glob is any glob pattern (subject to pathname expansion) to match against)

It deletes the shortest match, starting at the beginning of the line. There is also ## which deletes the longest match starting from the beginning of the variable, %, which deletes the shortest match starting from the end, and %%, which deletes the longest match starting from the end.


So, for example, the following code:

myVar="abc=llamas&disclaimer=true"
echo "${myVar#*=}"

will print llamas&disclaimer=true to the screen.

On the other hand,

myVar="abc=llamas&disclaimer=true"
echo ${myVar##*=}

will print true, and

myVar="foobar is bad"
echo "${myVar%%b*}"

will print foo


* This is fully explained in the bash man page; just search for the string ${parameter#word} to find it

jpaugh
  • 6,634
  • 4
  • 38
  • 90
  • 1
    This is weird: an excellent answer that gets it exactly wrong. `#` and `%` don't grab the specified glob, they strip it. `${myvar#*=}` will strip `abc=` from the example value, and print the rest. I have this as one of those where the intent is so clear that people just unthinkingly correct the mistakes internally without even noticing they're doing it. – jthill Nov 20 '19 at 20:40
  • @jthill Do you mean that I've swapped `#` and `%`? Honestly, I do that every time I sit at a terminal to use one or the other. I've given up on trying to remember it. Hopefully, I haven't done too much damage. – jpaugh Nov 21 '19 at 18:47
  • No, what I said: they don't grab the matched content, they strip it. `echo ${myvar#*=}` strips up to the first `=` and prints the rest. – jthill Nov 21 '19 at 19:00
  • @jthill Okay, now I get it. Thanks! You're right, of course; but I always use them for "grabbing" and will probably persist in thinking of them as "grab" operators. :-/ – jpaugh Nov 21 '19 at 19:08
6

It deletes the string matched (shortest match from start) by pattern *= in the string evaluated by $1.

$1 is the first positional parameter passed to the shell.

The general format can be written as ${var#patt} too, where patt is matched (shortest match from start) in $var and deleted.

Example:

var="first=middle=last"
echo "${var#*=}"

Output:

middle=last

If ## is used instead of # i.e ${var##pat}, then the pat is matched for the longest match (from start).

Example:

var="first=middle=last"
echo "${var##*=}"

Output:

last


From Bash Manual:

${parameter#word}

${parameter##word}

The word is expanded to produce a pattern just as in filename expansion (see Filename Expansion). If the pattern matches the beginning of the expanded 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. If parameter is an array variable subscripted with ‘@’ or ‘’, the pattern removal operation is applied to each member of the array in turn, and the expansion is the resultant list.

Jahid
  • 21,542
  • 10
  • 90
  • 108