Makefile: check if multiple variables are set - makefile

In my Makefile I want to check if multiple environments variables are set.
But I don't want to write multiple ifndef for each one. I just want an array of variables to make it reusable.
check-variables:
for var in var1 var2 var3 ; do \
if [ -z "$$var" ] ; then \
echo "$$var is not set"; exit 1; \
fi \
done
But it's not working...

This isn't a make issue, it's a shell issue. If you ran that script at your shell prompt (changing $$ back to $ as make will do, of course) it wouldn't work either. Until you can get the command to work at your shell prompt, you can't get it to work in a makefile.
The shell command would be:
for var in var1 var2 var3 ; do \
if [ -z "$var" ] ; then \
echo "$var is not set"; exit 1; \
fi \
done
You can see why this doesn't do what you want: you're checking the shell variable var every time. What you're trying to do is check the value of the variable which is named by the value of $var. To do this you need eval (the shell's eval, not make's eval function):
for var in var1 var2 var3 ; do \
eval test -n \"\$$var\" \
|| { echo "$var is not set"; exit 1; }; \
done
You should discover that the above will work in the shell, then you need to put it back into the makefile (and double all the $).

Related

In bash, either exit script without exiting the shell or export/set variables from within subshell

I have a function that runs a set of scripts that set variables, functions, and aliases in the current shell.
reloadVariablesFromScript() {
for script in "${scripts[#]}"; do
. "$script"
done
}
If one of the scripts has an error, I want to exit the script and then exit the function, but not to kill the shell.
reloadVariablesFromScript() {
for script in "${scripts[#]}"; do
{(
set -e
. "$script"
)}
if [[ $? -ne 0 ]]; then
>&2 echo $script failed. Skipping remaining scripts.
return 1
fi
done
}
This would do what I want except it doesn't set the variables in the script whether the script succeeds or fails.
Without the subshell, set -e causes the whole shell to exit, which is undesirable.
Is there a way I can either prevent the called script from continuing on an error without killing the shell or else set/export variables, aliases, and functions from within a subshell?
The following script simulates my problem:
test() {
{(
set -e
export foo=bar
false
echo Should not have gotten here!
export bar=baz
)}
local errorCode=$?
echo foo="'$foo'". It should equal 'bar'.
echo bar="'$bar'". It should not be set.
if [[ $errorCode -ne 0 ]]; then
echo Script failed correctly. Exiting function.
return 1
fi
echo Should not have gotten here!
}
test
If worst comes to worse, since these scripts don't actually edit the filesystem, I can run each script in a subshell, check the exit code, and if it succeeds, run it outside of a subshell.
Note that set -e has a number of surprising behaviors -- relying on it is not universally considered a good idea. That caveat being give, though: We can shuffle environment variables, aliases, and shell functions out as text:
envTest() {
local errorCode newVars
newVars=$(
set -e
{
export foo=bar
false
echo Should not have gotten here!
export bar=baz
} >&2
# print generate code which, when eval'd, recreates our functions and variables
declare -p | egrep -v '^declare -[^[:space:]]*r'
declare -f
alias -p
); errorCode=$?
if (( errorCode == 0 )); then
eval "$newVars"
fi
printf 'foo=%q. It should equal %q\n' "$foo" "bar"
printf 'bar=%q. It should not be set.\n' "$bar"
if [[ $errorCode -ne 0 ]]; then
echo 'Script failed correctly. Exiting function.'
return 1
fi
echo 'Should not have gotten here!'
}
envTest
Note that this code only evaluates either export should the entire script segment succeed; the question text and comments appear to indicate that this is acceptable if not desired.

How do I get a variable into a $(shell) command in a makefile?

❯ make --version
GNU Make 3.81
❯ bash --version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin18)
How can I pass a variable from inside a for loop to $(shell)? I can access the var outside of $(shell) but I can't figure out how to pass it in:
A_LIST:= one two
.PHONY: loop
loop:
#for iii in $(A_LIST) ; do \
echo inside recipe loop with sh command: $$iii ; \
export SAVED_OUTPUT=$(shell echo $$iii) ; \
echo $$SAVED_OUTPUT ; \
done
This is the output I get:
inside recipe loop with sh command: one
<blank line here>
inside recipe loop with sh command: two
<blank line here>
The last line in the loop echo $$SAVED_OUTPUT should output one and two because it is echoing the var and storing it in another var. But it is a blank line. I suspect it's because it's looking for an env var $iii but that doesn't exist- so how to I pass the value of iii into the shell?
Here is a bad way of doing this I don't like. I don't want to have to write a local file just to access a variable like this:
.PHONY: loop
loop:
#for iii in $(A_LIST) ; do \
echo inside recipe loop with sh command: $$iii ; \
echo $$iii > scratch ; \
export SAVED_OUTPUT=$(shell echo $$(cat scratch)) ; \
echo $$SAVED_OUTPUT ; \
done
The for loop is already being executed by a shell - in this case, there's no reason to bring $(shell ...) into it too. Just use normal $() shell command substitution syntax (Doubling up the $ to make make happy, like with the variable names):
A_LIST:= one two
.PHONY: loop
loop:
#for iii in $(A_LIST) ; do \
echo "inside recipe loop with sh command: $$iii" ; \
SAVED_OUTPUT="$$(somecommand "$$iii")" ; \
echo "$$SAVED_OUTPUT" ; \
done

$$ in shell , Linux

I have the following code from a make file, I know this creates bin folder in Home if that doesn't exist... but I couldn't understand what $$HOME/bin mean...
I googled and found $$ is to get the processid of the bash... but couldn't understand what $$HOME/bin mean... can someone please explain ?
.PHONY: home_bin
home_bin: ## Create home bin if not created
# if [[ ! -d "$$HOME/bin" ]]; then \
echo "Creating $$HOME/bin"; \
mkdir $$HOME/bin; \
echo "✔︎ $$HOME/bin created"; \
else \
echo "✔︎ $$HOME/bin already created"; \
fi
Thank you.
make itself performs expansion of $-prefixed characters; the $$ is expanded to a single literal $ to pass to the shell.
Consider a simple Makefile:
x=f
all:
xoo=3 && echo $xoo
which will output foo, because
make expands $x to the single character f.
make passes the string xoo=3 && echo foo to the shell for execution
Compare with
x=f
all:
xoo=3 && echo $$xoo
which outputs 3, because
make expands $$ to $
make passes the string xoo=3 && echo $xoo to the shell for execution

Bash Programming Passing Argument

I am currently learning bash programming and dont really understand why the passing argument for me is not working.
i have a script like this
#!/bin/bash
# the following environment variables must be set before running this script
# SIM_DIR name of directory containing armsim
# TEST_DIR name of the directory containing this script and the expected outputs
# LOG_DIR name of the directory that your output is written to by the run_test2 script
# ARMSIM_VERBOSE set to "-v" for verbose logging or leave unset
# First check the environment variables are set
giveup=0
if [[ ${#SIM_DIR} -eq 0 || ${#TEST_DIR} -eq 0 || ${#LOG_DIR} -eq 0 ]] ; then
echo One or more of the following environment variables must be set:
echo SIM_DIR, TEST_DIR, LOG_DIR
giveup=1
fi
# Now check the verbose flag
if [[ ${#ARMSIM_VERBOSE} != 0 && "x${ARMSIM_VERBOSE}" != "x-v" ]] ; then
echo ARMSIM_VERBOSE must be unset, empty or set to -v
giveup=1
fi
# Stop if environment is not set up
if [ ${giveup} -eq 1 ] ; then
exit 0
fi
cd ${TEST_DIR}
for i in test2-*.sh; do
echo "**** Running test ${i%.sh} *****"
./$i > ${LOG_DIR}/${i%.sh}.log
done
When I run the .sh file and pass in 3 example argument as below:-
$ ./run_test2 SIM_DIR TEST_DIR LOG_DIR
It still show: One or more of the following environment variables must be set:
SIM_DIR, TEST_DIR, LOG_DIR
Can anyone guide me on this? Thank you.
That's not how it's intended to work. The environment variables must be set beforehand either in the script or in the terminal like
export SIM_DIR=/home/someone/simulations
export TEST_DIR=/home/someone/tests
export LOG_DIR=/home/someone/logs
./run_test2
If you use these variables frequently, you might want to export them in ~/.bashrc. The syntax is identical to the exports in the above example.
Environment variables aren't really arguments in the sense I understand from your question/example. It sounds to me like you want to give arguments to a function/script, if you do that you can find your arguments in $1-9 (I think bash supports even more, unsure), the number of arguments are stored in $#
Example function that expects two arguments:
my_func() {
if [ $# -ne 2 ]; then
printf "You need to give 2 arguments\n"
return
fi
printf "Your first argument: %s\n" "$1"
printf "Your second argument: $s\n" "$2"
}
# Call the functionl like this
my_func arg1 arg2

How to use declare -x in bash

Can some one give an example where declare -x would be useful ?
declare -x FOO is the same as export FOO. It "exports" the FOO variable as an environment variable, so that programs you run from that shell session would see it.
Declare -x can be used instead of eval to allow variables to be set as arguments to the shell. For example, you can replace the extremely insecure:
# THIS IS NOT SAFE
while test $# -gt 0; do
eval export $1
shift
done
with the safer:
while test $# -gt 0; do
declare -x $1
shift
done
As an aside, this construct allows the user to invoke the script as:
$ ./test-script foo=bar
rather than the more idiomatic (but confusing to some):
$ foo=bar ./test-script
Use declare -x when you want to pass a variable to a different program, but don't want the variable to be used in global scope of the parent shell (i.e. when declaring inside a function).
From the bash help:
When used in a function, declare makes NAMEs local, as with the local
command. The -g option suppresses this behavior.
-x to make NAMEs export
Using + instead of - turns off the given attribute.
So declare -gx NAME=X will effectively behave the same as export NAME=X, but declare -x does not when the declare statements are inside functions.
The accepted solution is not accurate, as commented by #sampablokuper.
However both export variable a to subshells (below within ()).
test.sh:
#/bin/bash
foo() {
declare -x a="OK"
([ "$a" = "OK" ]) && echo "foo exported a locally"
}
bar() {
export a="OK"
([ "$a" = "OK" ]) && echo "bar exported a locally"
}
foo
echo "Global value of a by foo: ${a}"
([ "$a" = "OK" ]) && echo "foo exported a globally" \
|| echo 'foo did not export a globally'
bar
echo "Global value of a by bar: ${a}"
([ "$a" = "OK" ]) && echo "bar exported a globally" \
|| echo 'bar did not export a globally'
Runs as follows:
$ ./test.sh
foo exported a locally
Global value of a by foo:
foo did not export a globally
bar exported a locally
Global value of a by bar: OK
bar exported a globally

Resources