I've a makefile where all the environment variables are defined (top directory) and I want to use some of these variables in a shell script present at innermost level.
How should I access those variables in the script? Do I need to IPC for passing the variables or is there any other method to do the same?
If make exports the variables, you can use them right away (but note that changes to shell variables will not change the make variables). If you use GNU make, you can use the export directive.
If make does not export the variable, you can use the shell's one-shot assignment, like in
UNEXPORTED_VAR = foo
all:
UNEXPORTED_VAR='$(UNEXPORTED_VAR)' OTHERVAR='$(OTHERVAR)' script.sh
Like Jens said is correct, export make variables can be used. But his solution is not working for me.
So what you can also do is just export them in the makefile:
export UNEXPORTED_VAR
Related
I'm looking how to export from a Makefile environment variables to be exposed in the userland environment so exporting these variables from the Makefile should be accessible from the user shell.
I have tried make's export but as I understand and have tried does not export to outside of Makefile.
The idea of this is to populate Docker Compose environment variables in a elegant way and have these variables ready to use in the user shell also.
This is a fragment of what I've tried with make's export:
include docker.env
export $(shell sed -n '/=/p' docker.env)
SHELL := /bin/bash
run:
#docker-compose -f my-service.yml up -d
According with ArchWiki, each process of Bash...
Each process stores their environment in the /proc/$PID/environ file.
so once Make execute a source, export or any other command to set a new environment variable it will be applied only for that process.
As workaround I've written in the bash startup file so the variables will be in the global environment as soon as a new bash shell is loaded:
SHELL := /bin/bash
RC := ~/.bashrc
ENV := $(shell sed -n '/=/p' docker.env)
test:
#$(foreach e,$(ENV),echo $(e) >> $(RC);) \
EDIT completely reworked the answer after the OP explained in a comment that he wants the environment variables to be defined for any user shell.
If your goal is to have a set of environment variables defined for any user shell (I assume this means interactive shell), you can simply add these definitions to the shell's startup file (.bashrc for bash). From GNU make manual:
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.
However, 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 Summary of Options. But this is not recommended practice.)
Example:
$ cat .bashrc
...
export FOOBAR=foobar
export BARFOO="bar foo"
...
$ cat Makefile
all:
#printf '$$(FOOBAR)=%s\n' '$(FOOBAR)'
#printf 'FOOBAR='; printenv FOOBAR
#printf '$$(BARFOO)=%s\n' '$(BARFOO)'
#printf 'BARFOO='; printenv BARFOO
$ make
$(FOOBAR)=foobar
FOOBAR=foobar
$(BARFOO)=bar foo
BARFOO=bar foo
If you want to keep these definitions separate, you can just source the file from .bashrc:
$ cat docker.env
export FOOBAR=foobar
export BARFOO="bar foo"
$ cat .bashrc
...
source <some-path>/docker.env
...
And finally, if you don't want to add the export bash command to your file, you can parse the file in your .bashrc:
$ cat docker.env
FOOBAR=foobar
BARFOO="bar foo"
$ cat .bashrc
...
while read -r line; do
eval "export $$line"
done < <(sed -n '/=/p' <some-path>/docker.env)
...
Of course, there are some constraints for the syntax of your docker.env file (no unquoted special characters, no spaces in variable names, properly quoted values...) If your syntax is not bash-compatible it is time to ask another question about parsing this specific syntax and converting it into bash-compatible syntax.
Make cannot change the calling shell's environment without its cooperation. Of course, if you are in control, you can make the calling shell cooperate.
In broad terms, you could replace the make command with a shell alias or function which runs the real make and also sets the environment variables from the result. I will proceed to describe in more detail one way to implement this.
Whether you call this alias or function of yours make or e.g. compose is up to you really. To wrap the real make is marginally harder -- inside the function, you need to say command make, because just make would cause an infinite loop with the alias or function calling itself recursively -- so I will demonstrate this. Let's define a function (aliases suck);
make () {
# run the real make, break out on failure
command make "$#" || return
# if there is no env for us to load, we are done
test -f ./docker.env || return 0
# still here? load it
. ./docker.env
}
If you want even stricter control, maybe define a variable in the function and check inside the Makefile that the variable is set.
$(ifneq '${_composing}','function_make')
$(error Need to use the wrapper function to call make)
$(endif)
The error message is rather bewildering if you haven't read this discussion, so maybe it needs to be improved, and/or documented in a README or something. You would change the make line in the function above into
_composing='function_make' \
command make "$#" || return
The syntax var=value cmd args sets the variable var to the string value just for the duration of running the command line cmd args; it then returns to its previous state (unset, or set to its previous value).
For this particular construction, the name of the variable just needs to be reasonably unique and transparent to a curious human reader; and the value is also just a reasonably unique and reasonably transparent string which the function and the Makefile need to agree on.
Depending on what you end up storing in the environment, this could introduce complications if you need this mechanism for multiple Makefiles. Running it in directory a and then switching to a similar directory b will appear to work, but uses the a things where the poor puny human would expect the b things. (If the variables you set contain paths, relative paths fix this scenario, but complicate others.)
Extending this to a model similar to Ruby's rvm or Python's virtualenv might be worth exploring; they typically add an indicator to the shell prompt to remind you which environment is currently active, and have some (very modest) safeguards in place to warn you when your current directory and the environment disagree.
Another wart: Hardcoding make to always load docker.env is likely to produce unwelcome surprises one day. Perhaps hardcode a different file name which is specific to this hook - say, .compose_post_make_hook? It can then in turn contain something like
. ./docker.env
in this particular directory.
I am trying to create something like a global variable that I will use in order to make my project easy to deploy for other developers.
I would like to have an .sh file where there is a variable defining the location of the project.
Later on I want to export this variable and make it accessable in every makefile that I am creating so that I can use this design to keep everything constant and in one place.
This is an example of what I am trying to build:
Creating and exporting the variables in script.sh:
#!/bin/bash
DIRECTORY='some path value here'
Importing the values in multiple Makefiles:
# start script and fetch the value
VAR := $(shell ./script.sh | sed -n '/^result: /s/^.*: //p')
all:
#echo VAR=$(VAR)
I would like to see how other people are dealing with the same problem.
Being a better developer is my goal here. :)
Feedback always welcomed.
Environment variables exported in the shell are visible from make, so in a shell script like this:
#!/bin/sh
VAR=value
export VAR
make $*
The Makefile will start with VAR defined to value. That's one way to get variables from a shell script into make.
If you don't want the shell script to run make, you can have a user source it:
$ source script.sh
$ make
The variables set in the script will be visible to make this way too.
Or course there doesn't seem to be any reason you need a shell script here. Stick your configuration into a fragment of a Makefile (which would look almost exactly like your shell script, but not use quotes for multiple word values) and then include Makefile.inc in your main makefile.
Also note that syntax like this:
#!/bin/sh or another commment
VAR=value
export VAR
It equally valid included in a Makefile or sourced into a shell script. So sometimes it's possible to use the same include file in both places!
I'm trying to access a variable declared by previous command (inside a Makefile).
Here's the Makefile:
all:
./script1.sh
./script2.sh
Here's the script declaring the variable I want to access,script1.sh:
#!/usr/bin/env bash
myVar=1234
Here's the script trying to access the variable previously defined, script2.sh:
#!/usr/bin/env bash
echo $myVar
Unfortunately when I run make, myVar isn't accessible. Is there an other way around? Thanks.
Make will run each shell command in its own shell. And when the shell exits, its environment is lost.
If you want variables from one script to be available in the next, there are constructs which will do this. For example:
all:
( . ./script1.sh; ./script2.sh )
This causes Make to launch a single shell to handle both scripts.
Note also that you will need to export the variable in order for it to be visible in the second script; unexported variables are available only to the local script, and not to subshells that it launches.
UPDATE (per Kusalananda's comment):
If you want your shell commands to populate MAKE variables instead of merely environment variables, you may have options that depend on the version of Make that you are running. For example, in BSD make and GNU make, you can use "variable assignment modifiers" including (from the BSD make man page):
!= Expand the value and pass it to the shell for execution and
assign the result to the variable. Any newlines in the result
are replaced with spaces.
Thus, with BSD make and GNU make, you could do this:
$ cat Makefile
foo!= . ./script1.sh; ./script2.sh
all:
#echo "foo=${foo}"
$
$ cat script1.sh
export test=bar
$
$ cat script2.sh
#!/usr/bin/env bash
echo "$test"
$
$ make
foo=bar
$
Note that script1.sh does not include any shebang because it's being sourced, and is therefore running in the calling shell, whatever that is. That makes the shebang line merely a comment. If you're on a system where the default shell is POSIX but not bash (like Ubuntu, Solaris, FreeBSD, etc), this should still work because POSIX shells should all understand the concept of exporting variables.
The two separate invocations of the scripts create two separate environments. The first script sets a variable in its environment and exits (the environment is lost). The second script does not have that variable in its environment, so it outputs an empty string.
You can not have environment variables pass between environments other than between the environments of a parent shell to its child shell (not the other way around). The variables passed over into the child shell are only those that the parent shell has export-ed. So, if the first script invoked the second script, the value would be outputted (if it was export-ed in the first script).
In a shell, you would source the first file to set the variables therein in the current environment (and then export them!). However, in Makefiles it's a bit trickier since there's no convenient source command.
Instead you may want to read this StackOverflow question.
EDIT in light of #ghoti's answer: #ghoti has a good solution, but I'll leave my answer in here as it explains a bit more verbosely about environment variables and what we can do and not do with them with regards to passing them between environments.
I created a tcsh script, which I want to call through a Makefile environment. Therefore
I want to reference variables inside this tcsh script, which I have defined in the Makefile environment.
I tried to reference environment variables as $?VARIABLE, but this does not work on variables from the Makefile environment.
How can I do this?
Any dollar sign needs to be represented as a $$ in a make rule if it is to be used in commands. Note that it is extremely unwise to program in tcsh. In addition, make uses /bin/sh to execute commands, so you cannot use tcsh syntax in make rules.
All you could do, if you must, is something along
MAKEVAR = foo
target:
tcsh -c 'echo $$HOME $(MAKEVAR)'
What everybody else does is
MAKEVAR = foo
ANOTHERVAR = bar
target:
MAKEVAR=$(MAKEVAR) ANOTHERVAR=$(ANOTHERVAR) script.sh
This works for all make versions, not just GNU make.
If you're using GNU make, you need to export the variables.
Is there a way to detect whether a variable has been set from the environment vs. on the command line?
I would like to distinguish between someone invoking make with make LIB=mylib vs. make and $LIB being defined.
Yes. You can use the origin function to determine where a variable was defined.
ifneq (,$(findstring environment,$(origin LIB)))
# LIB was defined by the environment
else
# LIB was defined some other way
endif
With non-Gnu make, you could run the export command and grep for the variable in question. This works only in rules and only as long as the variable is not set as a one-shot (like in LIB=foo make).