7
$ bash -version
GNU bash, version 4.3.11(1)-release (x86_64-pc-linux-gnu)

Consider the following shell script:

#!/bin/bash

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

I'm attempting to do the equivalent in a Makefile:

SHELL := /bin/bash
.PHONY: foo
foo:
  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

I don't really want to touch the files; I'm doing this because I can't @echo -- the @ won't be seen as being at the beginning of the line because I'm in a loop. Or that's what seems to be happening.

Anyway, the point is that the loop doesn't appear to be running at all, hence the touch/echo business. The content of the shell script above is exactly what make echoes to the terminal. I added the shebang and ran it as a sanity check -- works like a charm.

Using a regular array works fine:

for prov in NL PE NS NB QC ON MB SK AB BC YK NT NU ; do \

However, I need those codes (10, 11, etc.) as well.

Anyone have insight to this?

Although I don't require it, I'd also like to know how (or if it's possible) to assign the PROVS variable at the top of the file while also using "declare -A".

EDIT: I'd somehow messed up the Makefile example so that it was just some inline shell commands, and no longer a recipe. I've added back the "foo:" target to clarify.

jimmij
  • 46,064
  • 19
  • 123
  • 136
brian
  • 73
  • 1
  • 1
  • 4

1 Answers1

8

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.)

schily
  • 18,806
  • 5
  • 38
  • 60
tripleee
  • 7,506
  • 2
  • 32
  • 42
  • Though it often makes more sense to have `make` itself manage your files -- that way, you get the dependency tracking etc. which is why you are usually using `make` in the first place. – tripleee Sep 28 '15 at 08:38
  • @schily It is valid in GNU Make; since the OP is using this syntax, I assume that's what they are running. See also https://www.gnu.org/software/make/manual/html_node/Setting.html – tripleee Sep 28 '15 at 09:24
  • Advising people to use nonstandard and vendor specific "enhancements" where the official standardized methods do the same causes an unneeded vendor lock in. The important advantage of UNIX is that there is no vendor lock in as long as you write code follows the standard., so please avoid supporting a vendor lock in. – schily Sep 28 '15 at 09:37
  • 1
    @schily Seriously? I can add a caveat to this answer but I don't think it adds any particular value here. Gripe on the OP for using GNU Make if you really want to pick this particular battle. – tripleee Sep 28 '15 at 09:39
  • Yes, of course! There are already too many makefiles in the public that prevent using a standard make program without editing makefiles. This can be seen as "embrace and extend" by gmake. Given that gmake does not work properly on various platforms, such makefiles are a major cause for non-portable OpenSource projects. – schily Sep 28 '15 at 09:43
  • Thanks, tripleee. My problem was that i was missing the backslash after declare. Works a treat now. – brian Sep 28 '15 at 17:40
  • @schilly, i only wanted to use @echo in an attempt to figure out whether the loop was running or not. My Makefile doesn't actually contain a `touch` command. I figured it was simpler to replace the contents of the recipe with that in order to simplify the problem. Your points about nonstandard syntax are duly noted; i'm all for standards. However,this Makefile is for my own personal use and is not meant for distribution. – brian Sep 28 '15 at 17:46
  • @schilly, i'm not sure what you meant by my shell commands needing to be in a target. They are inside a target. Perhaps you thought i'd posted the entirety of the Makefile; I did not. I only included the one target ("foo") to keep things simple but realise that i should have posted the entirety of a simpler Makefile, as you did, for purposes of clarity. Thanks again for your help. – brian Sep 28 '15 at 17:51
  • I meant @triplee. And, oh, bugger! I've just taken another look at what i'd posted. I must have screwed things up when i edited the question. There was, in fact, a make target ("foo:") before the declare line when i first posted it. Sorry for the confusion! I feel like a tool but at least i now understand what you were going on about. I assure you that i am working with a valid Makefile. – brian Sep 28 '15 at 17:55
  • 1
    @schily Unless you want to propose a standards-compliant replacement for the associative array in the recipe as well, there's no point in complaining about the lack of portability in this Makefile. – chepner Sep 28 '15 at 18:41
  • Bash is a bloated and slow shell implementation, but it is still much more portable than gmake. Please understand that it is a problem when an advise contains non-portable constructs without mentioning that vendor specific features are used without good reason. – schily Sep 28 '15 at 20:24