I thought I understood that simply-expanded variables got their value once, when the Makefile is read -- but I've got confused:
var := $$RANDOM
echo:
#echo v1: $(var)
#echo v2: $(var)
With result
$ make
v1: 11478
v2: 24064
So how come the shell env var is referenced twice?
I see that var := $(shell echo $$RANDOM) don't do the second value assignment -- how is this machinery different?
Simply-expanded variables are MAKE constructs. Indeed, that variable is only expanded one time, by make. But, it expands into a shell variable:
var := $$RANDOM
now the value of the var variable in make is the static string $RANDOM and make will never expand it again. You can determine this by doing something like:
var := $(info expanding RANDOM)$$RANDOM
and you'll see it only prints expanding RANDOM one time.
But, this rule:
echo:
#echo v1: $(var)
#echo v2: $(var)
invokes shell two times and each time passing the static string $RANDOM, which the shell expands, each time the shell is invoked, and you get different answers. Basically, make is running:
/bin/sh -c 'echo v1: $RANDOM'
/bin/sh -c 'echo v2: $RANDOM'
If you change it to this:
var := $(shell echo $$RANDOM)
Here, make is invoking the shell, one time, and assigning the result of that shell command to the variable var. So after this, var contains the literal string 12345 or whatever the result is, not $RANDOM.
BTW, you should add:
SHELL := /bin/bash
to your makefile, because $RANDOM is a bash feature that is not available in the POSIX shell, and make always invokes /bin/sh by default: on some systems /bin/sh is the same thing as bash but on other systems, it isn't.
Related
$ cat Makefile
all:
echo VAR is ${HOME}
echo VAR is $${HOME}
Gives
$ make
echo VAR is /home/abc
VAR is /home/abc
echo VAR is ${HOME}
VAR is /home/abc
Why does echo VAR is ${HOME} syntax work in Makefile? I thought, to use shell variables you have to use $${HOME}}
Yes and no. It is best to use $$ to be explicit. However, there is a special rule for environment variables:
Variables in make can come from the environment in which make is run. Every environment variable that make sees when it starts up is transformed into a make variable with the same name and value. But an explicit assignment in the makefile, or with a command argument, overrides the environment. (If the `-e' flag is specified, then values from the environment override assignments in the makefile. See section Summary of Options. But this is not recommended practice.)
I have a environment variable set with name $MY_ENV_VARIABLE.
How do I use this variable inside my makefile to (for example) include some source files?
LOCAL_SRC_FILES = $(MY_ENV_VARIABLE)/libDEMO.so
Something like above doesn't seem to work.
Note: in my case this is needed for building with the Android NDK but I guess this applies to make in general.
Just to add some information...
The syntax to access the environment variable in make is like other variables in make...
#export the variable. e.g. in the terminal,
export MY_ENV_VARIABLE="hello world"
...
#in the makefile (replace before call)
echo $(MY_ENV_VARIABLE)
This performs the substitution before executing the commmand. If you instead, want the substitution to happen during the command execution, you need to escape the $ (For example, echo $MY_ENV_VARIABLE is incorrect and will attempt to substitute the variable M in make, and append it to Y_ENV_VARIABLE)...
#in the makefile (replace during call)
echo $$MY_ENV_VARIABLE
Make sure you exported the variable from your shell. Running:
echo $MY_ENV_VARIABLE
shows you whether it's set in your shell. But to know whether you've exported it so that subshells and other sub-commands (like make) can see it try running:
env | grep MY_ENV_VARIABLE
If it's not there, be sure to run export MY_ENV_VARIABLE before running make.
That's all you need to do: make automatically imports all environment variables as make variables when it starts up.
I just had a similar issue (under Cygwin):
Running echo $OSTYPE on the shell prints the value, but
running env | grep OSTYPE doesn't give any output.
As I can't guarantee that this variable is exported on all machines I want to run that makefile on, I used the following to get the variable from within the makefile:
OSTYPE = $(shell echo $$OSTYPE)
Which of course can also be used within a condition like the following:
ifeq ($(shell echo $$OSTYPE),cygwin)
# ...do something...
else
# ...do something else...
endif
EDIT:
Some things I found after experimenting with the info from jozxyqk's answer, all from within the makefile:
If I run #echo $$OSTYPE or #echo "$$OSTYPE" in a recipe, the variable is successfully expanded into cygwin.
However, using that in a condition like ifeq ($$OSTYPE,cygwin) or ifeq ("$$OSTYPE","cygwin") doesn't expand it.
Thus it is logical that first setting a variable like TEST = "$$OSTYPE" will lead to echo $(TEST) printing cygwin (the expansion is done by the echo call) but that doesn't work in a condition - ifeq ($(TEST),cygwin) is false.
This question already has answers here:
What's a concise way to check that environment variables are set in a Unix shell script?
(14 answers)
Closed 9 years ago.
I am writing a shell script, where I have to check if environment variable is set, if not set then I have to set it. Is there any way to check in shell script, whether an environment variable is already set or not ?
The standard solution to conditionally assign a variable (whether in the environment or not) is:
: ${VAR=foo}
That will set VAR to the value "foo" only if it is unset.
To set VAR to "foo" if VAR is unset or the empty string, use:
: ${VAR:=foo}
To put VAR in the environment, follow up with:
export VAR
You can also do export VAR=${VAR-foo} or export VAR=${VAR:=foo}, but some older shells do not support the syntax of assignment and export in the same line. Also, DRY; using the name on both sides of the = operator is unnecessary repetition. (A second line exporting the variable violates the same principal, but feels better.)
Note that it is very difficult in general to determine if a variable is in the environment. Parsing the output of env will not work. Consider:
export foo='
VAR=var-value'
env | grep VAR
Nor does it work to spawn a subshell and test:
sh -c 'echo $VAR'
That would indicate the VAR is set in the subshell, which would be an indicator that VAR is in the environment of the current process, but it may simply be that VAR is set in the initialization of the subshell. Functionally, however, the result is the same as if VAR is in the environment. Fortunately, you do not usually care if VAR is in the environment or not. If you need it there, put it there. If you need it out, take it out.
[ -z "$VARIABLE" ] && VARIABLE="abc"
if env | grep -q ^VARIABLE=
then
echo env variable is already exported
else
echo env variable was not exported, but now it is
export VARIABLE
fi
I want to stress that [ -z $VARIABLE ] is not enough, because you can have VARIABLE but it was not exported. That means that it is not an environment variable at all.
What you want to do is native in bash, it is called parameter substitution:
VARIABLE="${VARIABLE:=abc}"
If VARIABLE is not set, right hand side will be equal to abc. Note that the internal operator := may be replaced with :- which tests if VARIABLE is not set or empty.
if [ -z "$VARIABLE" ]; then
VARIABLE=...
fi
This checks if the length of $VARIABLE is zero.
If I am writing a bash script, and I choose to use a config file for parameters. Can I still pass in parameters for it via the command line? I guess I'm asking can I do both on the same command?
The watered down code:
#!/bin/bash
source builder.conf
function xmitBuildFile {
for IP in "{SERVER_LIST[#]}"
do
echo $1#$IP
done
}
xmitBuildFile
builder.conf:
SERVER_LIST=( 192.168.2.119 10.20.205.67 )
$bash> ./builder.sh myname
My expected output should be myname#192.168.2.119 and myname#10.20.205.67, but when I do an $ echo $#, I am getting 0, even when I passed in 'myname' on the command line.
Assuming the "config file" is just a piece of shell sourced into the main script (usually containing definitions of some variables), like this:
. /etc/script.conf
of course you can use the positional parameters anywhere (before or after ". /etc/..."):
echo "$#"
test -n "$1" && ...
you can even define them in the script or in the very same config file:
test $# = 0 && set -- a b c
Yes, you can. Furthemore, it depends on your architecture of script. You can overwrite parametrs with values from config and vice versa.
By the way shflags may be pretty useful in writing such script.
In the manual:
The eval function is very special: it allows you to define new
makefile constructs that are not constant; which are the result of
evaluating other variables and functions. The argument to the eval
function is expanded, then the results of that expansion are parsed as
makefile syntax.
It’s important to realize that the eval argument is expanded twice;
first by the eval function, then the results of that expansion are
expanded again when they are parsed as makefile syntax. This means you
may need to provide extra levels of escaping for “$” characters when
using eval.
the "expanded twice" confuses me.
for example, i create a makefile :
define func
tmp = $(OBJPATH)/$(strip $1)
objs += $$(tmp)
$$(tmp) : $2
gcc $$^ -o $$#
endef
all : foo
$(eval $(call func, foo, 1.c))
how will the eval function be expanded ?
The easiest way to understand it is to replace the eval with info:
$(info $(call func, foo, 1.c))
That will display as output the result of the first expansion, so you can see what make will actually be parsing. You didn't provide the values for the OBJPATH variable, but if it was obj for example then in your case the first expansion (of the call function) results in:
tmp = obj/foo
objs += $(tmp)
$(tmp) : 1.c
gcc $^ -o $#
Then the make parser will evaluate this, and in the process it will expand it again, so things like $(tmp) are expanded.
This has been issue for me, but I found a nice workaround. In my case it was related to AWS docker login. I had in my shell script previously:
eval $(aws ecr get-login --region eu-west-1 --no-include-email --profile someprofile)
but when putting that into Makefile it didn't work. The workaround for this is to change the line into:
$$(aws ecr get-login --region eu-west-1 --no-include-email --profile someprofile)