If your code excerpt is properly representative, it seems that you are typing Bash commands directly in your Makefile and expecting Make to execute them with Bash. That's not how it works. The syntax of a Makefile is entirely different. Within a recipe, you can type Bash commands; each separate line in a recipe will be executed in a separate sub-shell. So you need at least two changes:
- Your shell commands need to be in a target.
- The
declare
needs to run in the same shell as the loop; otherwise you declare
in one Bash instance, then exit that, then run the loop in a separate instance which knows nothing about the now-lost declare
.
Here is a simple refactoring of your Makefile with these changes.
SHELL=/bin/bash # This is the standard compliant method
.PHONY: all
all:
declare -A PROVS=( ["NL"]=10 ["PE"]=11 ["NS"]=12 ["NB"]=13 \
["QC"]=24 ["ON"]=35 ["MB"]=46 ["SK"]=47 ["AB"]=48 \
["BC"]=59 ["YK"]=60 ["NT"]=61 ["NU"]=62 )\
; for key in "$${!PROVS[@]}" ; do \
touch "foo_$${key}_$${PROVS[$${key}]}" ; \
done
Demo: http://ideone.com/t94AOB
The @
convention to run a command silently applies to the entire command line. Thus, you can put it before declare
above, in which case it will be stripped off before the entire command line is submitted to Bash. Anywhere else, it will not be stripped or understood, and it will obviously cause a Bash syntax error in the called shell.
(The obsession with @
rules is an anti-pattern anyway. Run with make -s
if you don't want to see the output; shutting up make
will only make it harder to debug your rules.)
I think you're asking two different things there.
Is there a way to make bash print this info without the loop?
Yes, but they are not as good as just using the loop.
Is there a cleaner way to get/print only the key=value portion of the output?
Yes, the for
loop. It has the advantages that it doesn't require external programs, is straightforward, and makes it rather easy to control the exact output format without surprises.
Any solution that tries to handle the output of declare -p
(typeset -p
)
has to deal with a) the possibility of the variables themselves containing parenthesis or brackets, b) the quoting that declare -p
has to add to make it's output valid input for the shell.
For example, your expansion b="${a##*(}"
eats some of the values, if any key/value contains an opening parenthesis. This is because you used ##
, which removes the longest prefix. Same for c="${b%% )*}"
. Though you could of course match the boilerplate printed by declare
more exactly, you'd still have a hard time if you didn't want all the quoting it does.
This doesn't look very nice unless you need it.
$ declare -A array=([abc]="'foobar'" [def]='"foo bar"')
$ declare -p array
declare -A array='([def]="\"foo bar\"" [abc]="'\''foobar'\''" )'
With the for
loop, it's easier to choose the output format as you like:
# without quoting
$ for x in "${!array[@]}"; do printf "[%s]=%s\n" "$x" "${array[$x]}" ; done
[def]="foo bar"
[abc]='foobar'
# with quoting
$ for x in "${!array[@]}"; do printf "[%q]=%q\n" "$x" "${array[$x]}" ; done
[def]=\"foo\ bar\"
[abc]=\'foobar\'
From there, it's also simple to change the output format otherwise (remove the brackets around the key, put all key/value pairs on a single line...). If you need quoting for something other than the shell itself, you'll still need to do it by yourself, but at least you have the raw data to work on. (If you have newlines in the keys or values, you are probably going to need some quoting.)
With a current Bash (4.4, I think), you could also use printf "[%s]=%s" "${x@Q}" "${array[$x]@Q}"
instead of printf "%q=%q"
. It produces a somewhat nicer quoted format, but is of course a bit more work to remember to write. (And it quotes the corner case of @
as array key, which %q
doesn't quote.)
If the for loop seems too weary to write, save it a function somewhere (without quoting here):
printarr() { declare -n __p="$1"; for k in "${!__p[@]}"; do printf "%s=%s\n" "$k" "${__p[$k]}" ; done ; }
And then just use that:
$ declare -A a=([a]=123 [b]="foo bar" [c]="(blah)")
$ printarr a
a=123
b=foo bar
c=(blah)
Works with indexed arrays, too:
$ b=(abba acdc)
$ printarr b
0=abba
1=acdc
Best Answer
Build the new array from the old:
Comments on your code:
Your first suggested code does not work as it copies the values from the associative array to the new array. The values automatically gets the keys 0, 1 and 2 but the original keys are not copied. You need to copy the array key by key as I have shown above. This way you assign the wanted value to the correct key.
Your second suggested code contains a syntax error in that it has spaces around
=
in an assignment. This is where the errors that you see come from.variable = value
is interpreted as "the commandvariable
executed with the operands=
andvalue
".If you wish to iterate over a set of pathnames, don't use
ls
. Instead just dofor pathname in "$BAMFILES"/*bam; do
.Quote you variable expansions.
Consider using
printf
instead ofecho
to output variable data.Related: