In my Makefile deploy target I create environment variables and I want to reuse those in the following lines:
SHELL=/bin/sh
deploy:
export $(shell sh this-script-generate-key-values.sh | xargs)
echo ${VAR1} #there is no variable here
echo ${VAR2} #there is no variable here
Where:
this-script-generate-key-values.sh generates this output:
VAR1="somevalue"
VAR2="somevalue"
Why the variables are not set in subsequent lines? How can I make it work?
Notes:
This line works: sh this-script-generate-key-values.sh | xargs
The shell must be /bin/sh (no bash)
All lines in a Makefile recipe run in a separate shell. You need to run the lines in a single shell. Also you need to escape the dollar sign ($) so that variable substitution is not done by make but by the shell.
SHELL=/bin/sh
deploy:
export $$(this-script-generate-key-values.sh | xargs) ;\
echo $${VAR1} ;\
echo $${VAR2}
Just to expand on my comment -- you could output to a file, and use the file to generate your output as so:
vars.txt:
this-script-generate-key-values.sh > $#
deploy : vars.txt
echo VAR1=$$(sed -n 's|VAR1=\(.*\)|\1|p' vars.txt)
echo VAR2=$$(sed -n 's|VAR2=\(.*\)|\1|p' vars.txt)
note: you may have to generate dependencies for vars.txt or declare it .PHONY, otherwise, this will not run on every invocation of make.
If the .ONESHELL special target appears anywhere in the makefile then all recipe lines for each target will be provided to a single invocation of the shell. Newlines between recipe lines will be preserved.
.ONESHELL:
deploy:
export $$(this-script-generate-key-values.sh)
echo $${VAR1}
echo $${VAR2}
I have a single line Makefile that throws the following error: Makefile:1: *** missing separator. Stop. I know there are dozens of duplicate questions with the same error message but most of them suggest something to do with using not using tabs or mysterious special characters.
$ cat -e -v -t Makefile
set -e$
$ make
Makefile:1: *** missing separator. Stop.
As far as I can see, there are no mysterious special characters. Perhaps there are characters that cat -e -v -t doesn't show?
The following works, so I'm guessing it isn't an issue with my make installation:
$ cat -v -e -t Makefile
foo:$
^Iecho "Foo"$
$ make
echo "Foo"
Foo
Some relevant version and shell information.
$ make --version
GNU Make 3.81
$ echo $0
-bash
Edit: See comment by #AProgrammer
Note it throws the same error message regardless of what I have below the set -e.
$ cat -e -v -t Makefile
set -e$
$
foo:$
^Iecho "foo"$
$ make
Makefile:1: *** missing separator. Stop.
Edit 2:
Note adding #!/bin/bash throws the same error message.
$ cat -e -v -t Makefile
#!/bin/bash$
set -e$
$
foo:$
^Iecho "foo"$
$ make
Makefile:2: *** missing separator. Stop.
Edit 3:
Running set -e on my shell directly seems to work (it exits the failed make call as expected).
$ set -e
$ make
Makefile:2: *** missing separator. Stop.
Saving session...completed.
Deleting expired sessions...11 completed.
[Process completed]
There is absolutely no reason for which make would be able to interpret arbitrary shell commands outside of rules.
If you want to exit rules immediately when there is an error, there are several ways to do it, more or less portably.
First, each line in a rule is executed by a separate instance of the shell. This means that if you don't merge them manually (with \ and ;), you'll get the behavior you want if you have one command per line.
Then you can use set -e as part of the probably few rules which need it. For instance;
foo:
set -e; for i in a b c d; mkdir $$i; done
With GNU Make, you can change the flags used to call the shell, additionally passing -e:
.SHELLFLAGS=-ec
With POSIX Make, you can change the shell. I don't know if passing a flag is supported, but it seems so with GNU Make:
SHELL=/bin/sh -e
but you can always pass a wrapper which set the flags as you want:
SHELL=/path/to/mywrapper
with mywrapper being
#!/bin/sh
exec /bin/sh -e "$#"
https://www.youtube.com/watch?v=bu3_RzzEiVo
My intention is to experiment with shell scripts in a file. (FreeBSD 10.2)
I create a file named script.sh
cat > script.sh
set dir = `pwd`
echo The date today is `date`
echo The current directory is $dir
[Ctrl-d]
After giving it execution authority, I run the command
sh script.sh
I get
Why the directory is not displayed?
Then I make a change.
cat > script.sh
set dir = `pwd`
echo The date today is `date`
echo The current directory is `pwd`
[Ctrl-d]
This time , it works fine. The directory is shown successfully.
I would like to know why ? Could anyone tell me ?
The answer from TessellatingHeckler was on the right track.
From the man page for sh(1):
set [-/+abCEefIimnpTuVvx] [-/+o longname] [-c string] [-- arg ...]
The set command performs three different functions:
With no arguments, it lists the values of all shell variables.
If options are given, either in short form or using the long
``-/+o longname'' form, it sets or clears the specified options
as described in the section called Argument List Processing.
If you want a command for setting environment variables, that command would be setvar, which you'd use as follows:
setvar dir `pwd`
This is, however, uncommon usage. The more common synonym for this would be:
dir=`pwd`
or
dir=$(pwd)
Note that there are no spaces around the equals sign.
Note also that if you choose to use the setvar command, it's a good idea to put your value inside quotes. The following produces an error:
$ mkdir foo\ bar
$ cd foo\ bar
$ setvar dir `pwd`
Instead, you would need:
$ setvar dir "`pwd`"
Or more clearly:
$ dir="$(pwd)"
Note that you may also need to export your variables. The export command is used to mark a variable that should be passed along to sub shells that the running shell spawns. An example should make this more clear:
$ foo="bar"
$ sh -c 'echo $foo'
$ export foo
$ sh -c 'echo $foo'
bar
One other thing I'll add is that it's common and unnecessary to use date as you're doing in your script, since that command is able to produce its own formatted output. Try this:
$ date '+The date today is %+'
For date options, you can man date and man strftime.
Last tip: when using echo, put things in quotes. You'll produce less confusing and more reasonable output. Note:
$ foo="`printf 'a\nb\n'`"
$ echo $foo
a b
$ echo "$foo"
a
b
Hope this helps!
Is there a better way to source a script, which sets env vars, from within a makefile?
FLAG ?= 0
ifeq ($(FLAG),0)
export FLAG=1
/bin/myshell -c '<source scripts here> ; $(MAKE) $#'
else
...targets...
endif
Makefile default shell is /bin/sh which does not implement source.
Changing shell to /bin/bash makes it possible:
# Makefile
SHELL := /bin/bash
rule:
source env.sh && YourCommand
To answer the question as asked: you can't.
The basic issue is that a child process can not alter the parent's environment. The shell gets around this by not forking a new process when source'ing, but just running those commands in the current incarnation of the shell. That works fine, but make is not /bin/sh (or whatever shell your script is for) and does not understand that language (aside from the bits they have in common).
Chris Dodd and Foo Bah have addressed one possible workaround, so I'll suggest another (assuming you are running GNU make): post-process the shell script into make compatible text and include the result:
shell-variable-setter.make: shell-varaible-setter.sh
postprocess.py #^
# ...
else
include shell-variable-setter.make
endif
messy details left as an exercise.
If your goal is to merely set environment variables for Make, why not keep it in Makefile syntax and use the include command?
include other_makefile
If you have to invoke the shell script, capture the result in a shell command:
JUST_DO_IT=$(shell source_script)
the shell command should run before the targets. However this won't set the environment variables.
If you want to set environment variables in the build, write a separate shell script that sources your environment variables and calls make. Then, in the makefile, have the targets call the new shell script.
For example, if your original makefile has target a, then you want to do something like this:
# mysetenv.sh
#!/bin/bash
. <script to source>
export FLAG=1
make "$#"
# Makefile
ifeq($(FLAG),0)
export FLAG=1
a:
./mysetenv.sh a
else
a:
.. do it
endif
Using GNU Make 3.81 I can source a shell script from make using:
rule:
<tab>source source_script.sh && build_files.sh
build_files.sh "gets" the environment variables exported by source_script.sh.
Note that using:
rule:
<tab>source source_script.sh
<tab>build_files.sh
will not work. Each line is ran in its own subshell.
This works for me. Substitute env.sh with the name of the file you want to source. It works by sourcing the file in bash and outputting the modified environment, after formatting it, to a file called makeenv which is then sourced by the makefile.
IGNORE := $(shell bash -c "source env.sh; env | sed 's/=/:=/' | sed 's/^/export /' > makeenv")
include makeenv
Some constructs are the same in the shell and in GNU Make.
var=1234
text="Some text"
You can alter your shell script to source the defines. They must all be simple name=value types.
Ie,
[script.sh]
. ./vars.sh
[Makefile]
include vars.sh
Then the shell script and the Makefile can share the same 'source' of information. I found this question because I was looking for a manifest of common syntax that can be used in Gnu Make and shell scripts (I don't care which shell).
Edit: Shells and make understand ${var}. This means you can concatenate, etc,
var="One string"
var=${var} "Second string"
I really like Foo Bah's answer where make calls the script, and the script calls back to make. To expand on that answer I did this:
# Makefile
.DEFAULT_GOAL := all
ifndef SOME_DIR
%:
<tab>. ./setenv.sh $(MAKE) $#
else
all:
<tab>...
clean:
<tab>...
endif
--
# setenv.sh
export SOME_DIR=$PWD/path/to/some/dir
if [ -n "$1" ]; then
# The first argument is set, call back into make.
$1 $2
fi
This has the added advantage of using $(MAKE) in case anyone is using a unique make program, and will also handle any rule specified on the command line, without having to duplicate the name of each rule in the case when SOME_DIR is not defined.
If you want to get the variables into the environment, so that they are passed to child processes, then you can use bash's set -a and set +a. The former means, "When I set a variable, set the corresponding environment variable too." So this works for me:
check:
bash -c "set -a && source .env.test && set +a && cargo test"
That will pass everything in .env.test on to cargo test as environment variables.
Note that this will let you pass an environment on to sub-commands, but it won't let you set Makefile variables (which are different things anyway). If you need the latter, you should try one of the other suggestions here.
My solution to this: (assuming you're have bash, the syntax for $# is different for tcsh for instance)
Have a script sourceThenExec.sh, as such:
#!/bin/bash
source whatever.sh
$#
Then, in your makefile, preface your targets with bash sourceThenExec.sh, for instance:
ExampleTarget:
bash sourceThenExec.sh gcc ExampleTarget.C
You can of course put something like STE=bash sourceThenExec.sh at the top of your makefile and shorten this:
ExampleTarget:
$(STE) gcc ExampleTarget.C
All of this works because sourceThenExec.sh opens a subshell, but then the commands are run in the same subshell.
The downside of this method is that the file gets sourced for each target, which may be undesirable.
Depending on your version of Make and enclosing shell, you can implement a nice solution via eval, cat, and chaining calls with &&:
ENVFILE=envfile
source-via-eval:
#echo "FOO: $${FOO}"
#echo "FOO=AMAZING!" > $(ENVFILE)
#eval `cat $(ENVFILE)` && echo "FOO: $${FOO}"
And a quick test:
> make source-via-eval
FOO:
FOO: AMAZING!
An elegant solution found here:
ifneq (,$(wildcard ./.env))
include .env
export
endif
If you need only a few known variables exporting in makefile can be an option, here is an example of what I am using.
$ grep ID /etc/os-release
ID=ubuntu
ID_LIKE=debian
$ cat Makefile
default: help rule/setup/lsb
source?=.
help:
-${MAKE} --version | head -n1
rule/setup/%:
echo ID=${#F}
rule/setup/lsb: /etc/os-release
${source} $< && export ID && ${MAKE} rule/setup/$${ID}
$ make
make --version | head -n1
GNU Make 3.81
. /etc/os-release && export ID && make rule/setup/${ID}
make[1]: Entering directory `/tmp'
echo ID=ubuntu
ID=ubuntu
--
http://rzr.online.fr/q/gnumake
Assuming GNU make, can be done using a submake. Assuming that the shell script that exports the variables is include.sh in the current directory, move your Makefile to realmake.mk. Create a new Makefile:
all:
#. ./include.sh; \
$(MAKE) -f realmake.mk $(MAKECMDGOALS)
$(MAKECMDGOALS):
+#. ./include.sh; \
$(MAKE) -f realmake.mk $(MAKECMDGOALS)
Pay attention to the ./ preceding include.sh.
Another possible way would be to create a sh script, for example run.sh, source the required scripts and call make inside the script.
#!/bin/sh
source script1
source script2 and so on
make
target: output_source
bash ShellScript_name.sh
try this it will work, the script is inside the current directory.
Is there a way in the Fish Interactive shell for the full path to be displayed. Currently when I navigate to a directory I get the following shell.
millermj#Dodore ~/o/workspace
but I would rather see
millermj#Dodore ~/o-town/workspace
With the new fishshell (v2.3) you can do set -U fish_prompt_pwd_dir_length 0. And it will use the full path. I also use dartfish for my theme. See example below:
Here's my version of prompt_pwd that should display what you're looking for:
function prompt_pwd --description 'Print the current working directory, NOT shortened to fit the prompt'
if test "$PWD" != "$HOME"
printf "%s" (echo $PWD|sed -e 's|/private||' -e "s|^$HOME|~|")
else
echo '~'
end
end
This will display the tilde for the home directory, as usual, but removes the sed command that only pulls the first letter from each directory when you're a few directories deep.
To edit prompt_pwd use funced. It will allow you to interactively alter the function. From the command line type funced prompt_pwd. Once the prompt is displaying to your liking, use funcsave prompt_pwd to make the behavior persist in future sessions.
I personally don't like touching the shared/defaults. Fish has a great functions design, so leverage that.
Create ~/.config/fish/functions/prompt_long_pwd.fish with the contents:
function prompt_long_pwd --description 'Print the current working directory'
echo $PWD | sed -e "s|^$HOME|~|" -e 's|^/private||'
end
Then simply edit your ~/.config/fish/functions/fish_prompt.fish to use prompt_long_pwd. Here is the custom prompt that I use:
~/.config/fish/config.fish:
set -g __fish_git_prompt_show_informative_status 1
set -g __fish_git_prompt_hide_untrackedfiles 1
set -g __fish_git_prompt_color_branch magenta bold
set -g __fish_git_prompt_showupstream "informative"
set -g __fish_git_prompt_char_upstream_ahead "↑"
set -g __fish_git_prompt_char_upstream_behind "↓"
set -g __fish_git_prompt_char_upstream_prefix ""
set -g __fish_git_prompt_char_stagedstate "●"
set -g __fish_git_prompt_char_dirtystate "✚"
set -g __fish_git_prompt_char_untrackedfiles "…"
set -g __fish_git_prompt_char_conflictedstate "✖"
set -g __fish_git_prompt_char_cleanstate "✔"
set -g __fish_git_prompt_color_dirtystate blue
set -g __fish_git_prompt_color_stagedstate yellow
set -g __fish_git_prompt_color_invalidstate red
set -g __fish_git_prompt_color_untrackedfiles $fish_color_normal
set -g __fish_git_prompt_color_cleanstate green bold
~/.config/fish/functions/fish_prompt.fish
function fish_prompt --description 'Write out the prompt'
set -l last_status $status
if not set -q __fish_prompt_normal
set -g __fish_prompt_normal (set_color normal)
end
# PWD
set_color $fish_color_cwd
echo -n (prompt_long_pwd)
set_color normal
printf '%s ' (__fish_git_prompt)
if not test $last_status -eq 0
set_color $fish_color_error
end
echo -n '$ '
end
Create ~/.config/fish/functions/prompt_long_pwd.fish with:
function prompt_long_pwd --description 'Print the full working directory'
echo $PWD
end
In ~/.config/fish/functions/fish_prompt.fish change (prompt_pwd) (set_color normal) to (prompt_long_pwd) (set_color normal).
(prompt_pwd) replaces the directory name with the first letter of the directory, which I too sometimes find annoying. You can also probably modify the original command prompt_pwd, I have not tried it.
This answer is modified from the earlier answer and shows the full directory while keeping the other parts of the default prompt. The key change is in the expression in ~/.config/fish/functions/prompt_long_pwd.fish.
Just thought I'd give everyone a heads up if you stumbled on this and your fish is not responding to the fish_prompt_pwd_dir_length variable try upgrading omf, and then update your omf themes that you're using. Then do some combination of selecting a different theme, reopening the shell, and selecting a different theme again. This is what worked for me. Good luck!
The prompt_pwd function determines the function to be displayed. You should be able to write your own version to get what you want.