In one of my Makefile recipes, I want to create a temporary file, pass the name of that file to a shell command and assign the output of that command to a make variable so that I can use that subsequently. For the life of me, I cannot get it to work.
For the purpose of debugging I have tried to boil down the problem to the most simple target I could come up with:
.PHONY: foo
foo:
$(eval TMPFILE = $(shell mktemp -p ./))
dd if=/dev/random of=${TMPFILE} bs=1 count=512
$(eval FOO = $(shell wc -c ${TMPFILE}))
#echo FOO: ${FOO}
Here is what happens:
❯ make foo
dd if=/dev/random of=./tmp.K1au4WrZ76 bs=1 count=512
512+0 records in
512+0 records out
512 bytes copied, 0.00287818 s, 178 kB/s
FOO: 0 ./tmp.K1au4WrZ76
So somehow, wc thinks the file is empty. But when I check the TMPFILE it has 512 bytes, as expected:
❯ wc -c tmp.K1au4WrZ76
512 tmp.K1au4WrZ76
Can someone, please enlighten me what is going on here and how to do that correctly?
Thanks
Phil
Update: Based on the answer I put together this target which works as desired:
.PHONEY: foo
.ONESHELL:
foo:
set -e
TMPFILE=`mktemp -p ./`
dd if=/dev/random of=$$TMPFILE bs=1 count=512
FOO=`wc -c $$TMPFILE`
#echo FOO: $$FOO
Thanks!
Make always expands the entire recipe (all lines of the recipe) first, before it starts any shell commands. So all your eval, etc. operations are invoked before any shell command, such as dd, is run.
Can we use shell variables in AWK like $VAR instead of $1, $2? For example:
UL=(AKHIL:AKHIL_NEW,SWATHI:SWATHI_NEW)
NUSR=`echo ${UL[*]}|awk -F, '{print NF}'`
echo $NUSR
echo ${UL[*]}|awk -F, '{print $NUSR}'
Actually am an oracle DBA we get lot of import requests. I'm trying to automate it using the script. The script will find out the users in the dump and prompt for the users to which dump needs to be loaded.
Suppose the dumps has two users AKHIL, SWATHI (there can be may users in the dump and i want to import more number of users). I want to import the dumps to new users AKHIL_NEW and SWATHI_NEW. So the input to be read some think like AKHIL:AKHIL_NEW,SWATHI:SWATHI_NEW.
First, I need to find the Number of users to be created, then I need to get new users i.e. AKHIL_NEW,SWATHI_NEW from the input we have given. So that I can connect to the database and create the new users and then import. I'm not copying the entire code: I just copied the code from where it accepts the input users.
UL=(AKHIL:AKHIL_NEW,SWATHI:SWATHI_NEW) ## it can be many users like USER1:USER1_NEW,USER2_USER2_NEW,USER3:USER_NEW..
NUSR=`echo ${UL[*]}|awk -F, '{print NF}'` #finding number of fields or users
y=1
while [ $y -le $NUSR ] ; do
USER=`echo ${UL[*]}|awk -F, -v NUSR=$y '{print $NUSR}' |awk -F: '{print $2}'` #getting Users to created AKHIL_NEW and SWATHI_NEW and passing to SQLPLUS
if [[ $USER = SCPO* ]]; then
TBS=SCPODATA
else
if [[ $USER = WWF* ]]; then
TBS=WWFDATA
else
if [[ $USER = STSC* ]]; then
TBS=SCPODATA
else
if [[ $USER = CSM* ]]; then
TBS=CSMDATA
else
if [[ $USER = TMM* ]]; then
TBS=TMDATA
else
if [[ $USER = IGP* ]]; then
TBS=IGPDATA
fi
fi
fi
fi
fi
fi
sqlplus -s '/ as sysdba' <<EOF # CREATING the USERS in the database
CREATE USER $USER IDENTIFIED BY $USER DEFAULT TABLESPACE $TBS TEMPORARY TABLESPACE TEMP QUOTA 0K on SYSTEM QUOTA UNLIMITED ON $TBS;
GRANT
CONNECT,
CREATE TABLE,
CREATE VIEW,
CREATE SYNONYM,
CREATE SEQUENCE,
CREATE DATABASE LINK,
RESOURCE,
SELECT_CATALOG_ROLE
to $USER;
EOF
y=`expr $y + 1`
done
impdp sysem/manager DIRECTORY=DATA_PUMP DUMPFILE=imp.dp logfile=impdp.log SCHEMAS=AKHIL,SWATHI REMPA_SCHEMA=${UL[*]}
In the last impdp command I need to get the original users in the dumps i.e AKHIL,SWATHI using the variables.
Yes, you can use the shell variables inside awk. There are a bunch of ways of doing it, but my favorite is to define a variable with the -v flag:
$ echo | awk -v my_var=4 '{print "My var is " my_var}'
My var is 4
Just pass the environment variable as a parameter to the -v flag. For example, if you have this variable:
$ VAR=3
$ echo $VAR
3
Use it this way:
$ echo | awk -v env_var="$VAR" '{print "The value of VAR is " env_var}'
The value of VAR is 3
Of course, you can give the same name, but the $ will not be necessary:
$ echo | awk -v VAR="$VAR" '{print "The value of VAR is " VAR}'
The value of VAR is 3
A note about the $ in awk: unlike bash, Perl, PHP etc., it is not part of the variable's name but instead an operator.
Awk and Gawk provide the ENVIRON associative array that holds all exported environment variables. So in your awk script you can use ENVIRON["VarName"] to get the value of VarName, provided that VarName has been exported before running awk.
Note ENVIRON is a predefined awk variable NOT a shell environment variable.
Since I don't have enough reputation to comment on the other answers I have to include them here!
The earlier answer showing $ENVIRON is incorrect - that syntax would be expanded by the shell, and probably result in expanding to nothing.
Further earlier comments about C not being able to access environment variable is wrong. Contrary to what is said above, C (and C++) can access environment variables using the getenv("VarName") function. Many other languages provide similar access (e.g., Java: System.getenv(), Python: os.environ, Haskell System.Environment, ...). Note in all cases access to environment variables is read-only, you cannot change an environment variable in a program and get that value back to the calling script.
There are two ways to pass variables to awk: one way is defining the variable in a command line argument:
$ echo ${UL[*]}|awk -F, -v NUSR=$NUSR '{print $NUSR}'
SWATHI:SWATHI_NEW
Another way is converting the shell variable to an environment variable using export, and reading the environment variable from the ENVIRON array:
$ export NUSR
$ echo ${UL[*]}|awk -F, '{print $ENVIRON["NUSR"]}'
SWATHI:SWATHI_NEW
Update 2016: The OP has comma-separated data and wants to extract an item given its index. The index is in the shell variable NUSR. The value of NUSR is passed to awk, and awk's dollar operator extracts the item.
Note that it would be simpler to declare UL as an array of more than one element, and do the extraction in bash, and take awk out of the equation completely. This however uses 0-based indexing.
UL=(AKHIL:AKHIL_NEW SWATHI:SWATHI_NEW)
NUSR=1
echo ${UL[NUSR]} # prints SWATHI:SWATHI_NEW
There is another way, but it could cause immense confusion:
$ VarName="howdy" ; echo | awk '{print "Just saying '$VarName'"}'
Just saying howdy
$
So you are temporarily exiting the single quote environment (which would normally prevent the shell from interpreting '$') to interpret the variable and then going back into it. It has the virtue of being relatively brief.
Not sure if i understand your question.
But lets say we got a variable number=3 and we want to use it istead of $3, in awk we can do that with the following code
results="100 Mbits/sec 110 Mbits/sec 90 Mbits/sec"
number=3
speed=$(echo $results | awk '{print '"\$${number}"'}')
so the speed variable will get the value 110.
Hope this helps.
No. You can pass the value of a shell variable to an awk script just like you can pass the value of a shell variable to a C program but you cannot access a shell variable in an awk script any more than you could access a shell variable in a C program. Like C, awk is not shell. See question 24 in the comp.unix.shell FAQ at cfajohnson.com/shell/cus-faq-2.html#Q24.
One way to write your code would be:
UL="AKHIL:AKHIL_NEW,SWATHI:SWATHI_NEW"
NUSR=$(awk -F, -v ul="$UL" 'BEGIN{print gsub(FS,""); exit}')
echo "$NUSR"
echo "$UL" | awk -F, -v nusr="$NUSR" '{print $nusr}' # could have just done print $NF
but since your original starting point:
UL=(AKHIL:AKHIL_NEW,SWATHI:SWATHI_NEW)
was declaring UL as an array with just one entry, you might want to rethink whatever it is you're trying to do as you may have completely the wrong approach.
first post here, but been a lurker for ages. i have googled for ages, but cant find what i want (many abigious topic subjects which dont request what the topic suggests it does ...). not new to awk or scripting, just a little rusty :)
i'm trying to write an awk script which will set shell env values as it runs - for another bash script to pick up and use later on. i cannot simply use stdout from awk to report this value i want setting (i.e. "export whatever=awk cmd here"), as thats already directed to a 'results file' which the awkscript is creating (plus i have more than one variable to export in the final code anyway).
As an example test script, to demo my issue:
echo $MYSCRIPT_RESULT # returns nothing, not currently set
echo | awk -f scriptfile.awk # do whatever, setting MYSCRIPT_RESULT as we go
echo $MYSCRIPT_RESULT # desired: returns the env value set in scriptfile.awk
within scriptfile.awk, i have tried (without sucess)
1/) building and executing an adhoc string directly:
{
cmdline="export MYSCRIPT_RESULT=1"
cmdline
}
2/) using the system function:
{
cmdline="export MYSCRIPT_RESULT=1"
system(cmdline)
}
... but these do not work. I suspect that these 2 commands are creating a subshell within the shell awk is executing from, and doing what i ask (proven by touching files as a test), but once the "cmd"/system calls have completed, the subshell dies, unfortunatley taking whatever i have set with it - so my env setting changes dont stick from "the caller of awk"'s perspective.
so my question is, how do you actually set env variables within awk directly, so that a calling process can access these env values after awk execution has completed? is it actually possible?
other than the adhoc/system ways above, which i have proven fail for me, i cannot see how this could be done (other than writing these values to a 'random' file somewhere to be picked up and read by the calling script, which imo is a little dirty anyway), hence, help!
all ideas/suggestions/comments welcomed!
You cannot change the environment of your parent process. If
MYSCRIPT_RESULT=$(awk stuff)
is unacceptable, what you are asking cannot be done.
You can also use something like is described at
Set variable in current shell from awk
unset var
var=99
declare $( echo "foobar" | awk '/foo/ {tmp="17"} END {print "var="tmp}' )
echo "var=$var"
var=
The awk END clause is essential otherwise if there are no matches to the pattern declare dumps the current environment to stdout and doesn't change the content of your variable.
Multiple values can be set by separating them with spaces.
declare a=1 b=2
echo -e "a=$a\nb=$b"
NOTE: declare is bash only, for other shells, use eval with the same syntax.
You can do this, but it's a bit of a kludge. Since awk does not allow redirection to a file descriptor, you can use a fifo or a regular file:
$ mkfifo fifo
$ echo MYSCRIPT_RESULT=1 | awk '{ print > "fifo" }' &
$ IFS== read var value < fifo
$ eval export $var=$value
It's not really necessary to split the var and value; you could just as easily have awk print the "export" and just eval the output directly.
I found a good answer. Encapsulate averything in a subshell!
The comand declare works as below:
#Creates 3 variables
declare var1=1 var2=2 var3=3
ex1:
#Exactly the same as above
$(awk 'BEGIN{var="declare "}{var=var"var1=1 var2=2 var3=3"}END{print var}')
I found some really interesting uses for this technique. In the next exemple I have several partitions with labels. I create variables using the labels as variable names and the device name as variable values.
ex2:
#Partition data
lsblk -o NAME,LABEL
NAME LABEL
sda
├─sda1
├─sda2
├─sda5 System
├─sda6 Data
└─sda7 Arch
#Creates a subshell to execute the text
$(\
#Pipe lsblk to awk
lsblk -o NAME,LABEL | awk \
#Initiate the variable with the text for the declare command
'BEGIN{txt="declare "}'\
#Filters devices with labels Arch or Data
'/Data|Arch/'\
#Concatenate txt with itself plus text for the variables(name and value)
#substr eliminates the special caracters before the device name
'{txt=txt$2"="substr($1,3)" "}'\
#AWK prints the text and the subshell execute as a command
'END{print txt}'\
)
The end result of this is 2 variables: Data with value sda6 and Arch with value sda7.
The same exemple in a single line.
$(lsblk -o NAME,LABEL | awk 'BEGIN{txt="declare "}/Data|Arch/{txt=txt$2"="substr($1,3)" "}END{print txt}')
For example, I have a perl script p.pl that writes "5" to stdout. I'd like to assign that output to a variable like so:
$ x = perl p.pl ! not working code
$ ! now x would be 5
The PIPE command allows you to do Unix-ish pipelining, but DCL is not bash. Getting the output assigned to a symbol is tricky. Each PIPE segment runs in a separate subprocess (like Unix) and there's no way to return a symbol from a subprocess. AFAIK, there's no bash equivalent of assigning stdout to a variable.
The typical approach is to write (redirect) the output to a file and then read it back:
$ PIPE perl p.pl > temp.txt
$ open t temp.txt
$ read t x
$ close t
Another approach is to assign the return value as a JOB logical which is shared by all subprocesses. This can be done as a one-liner using PIPE:
$ PIPE perl p.pl | DEFINE/JOB RET_VALUE #SYS$PIPE
$ x = f$logical("RET_VALUE")
Since the "RET_VALUE" is shared by all processes in the job, you have to be careful of side-effects.
Look up the PIPE command. It lets you do unix like things.
I wanted to identify a particular ACE from a file's ACL and then assign the value to a variable I could refer to later in the script. I wanted to avoid the overhead of writing to/reading from a file as I had 1000s of files to iterate over. This method worked for me.
$ PIPE DIR/SEC filename | SEARCH SYS$PIPE variable | (READ SYS$PIPE variable && DEFINE/JOB/NOLOG variable &variable)
$ SHOW LOGICAL variable
I want to create a makefile variable that is a multi-line string (e.g. the body of an email release announcement). something like
ANNOUNCE_BODY="
Version $(VERSION) of $(PACKAGE_NAME) has been released
It can be downloaded from $(DOWNLOAD_URL)
etc, etc"
But I can't seem to find a way to do this. Is it possible?
Yes, you can use the define keyword to declare a multi-line variable, like this:
define ANNOUNCE_BODY
Version $(VERSION) of $(PACKAGE_NAME) has been released.
It can be downloaded from $(DOWNLOAD_URL).
etc, etc.
endef
The tricky part is getting your multi-line variable back out of the makefile. If you just do the obvious thing of using "echo $(ANNOUNCE_BODY)", you'll see the result that others have posted here -- the shell tries to handle the second and subsequent lines of the variable as commands themselves.
However, you can export the variable value as-is to the shell as an environment variable, and then reference it from the shell as an environment variable (NOT a make variable). For example:
export ANNOUNCE_BODY
all:
#echo "$$ANNOUNCE_BODY"
Note the use of $$ANNOUNCE_BODY, indicating a shell environment variable reference, rather than $(ANNOUNCE_BODY), which would be a regular make variable reference. Also be sure to use quotes around your variable reference, to make sure that the newlines aren't interpreted by the shell itself.
Of course, this particular trick may be platform and shell sensitive. I tested it on Ubuntu Linux with GNU bash 3.2.13; YMMV.
Another approach to 'getting your multi-line variable back out of the makefile' (noted by Eric Melski as 'the tricky part'), is to plan to use the subst function to replace the newlines introduced with define in your multi-line string with \n. Then use -e with echo to interpret them. You may need to set the .SHELL=bash to get an echo that does this.
An advantage of this approach is that you also put other such escape characters into your text and have them respected.
This sort of synthesizes all the approaches mentioned so far...
You wind up with:
define newline
endef
define ANNOUNCE_BODY
As of $(shell date), version $(VERSION) of $(PACKAGE_NAME) has been released.
It can be downloaded from $(DOWNLOAD_URL).
endef
someTarget:
echo -e '$(subst $(newline),\n,${ANNOUNCE_BODY})'
Note the single quotes on the final echo are crucial.
Assuming you only want to print the content of your variable on standard output, there is another solution :
do-echo:
$(info $(YOUR_MULTILINE_VAR))
Yes. You escape the newlines with \:
VARIABLE="\
THIS IS A VERY LONG\
TEXT STRING IN A MAKE VARIABLE"
update
Ah, you want the newlines? Then no, I don't think there's any way in vanilla Make. However, you can always use a here-document in the command part
[This does not work, see comment from MadScientist]
foo:
echo <<EOF
Here is a multiple line text
with embedded newlines.
EOF
Not completely related to the OP, but hopefully this will help someone in future.
(as this question is the one that comes up most in google searches).
In my Makefile, I wanted to pass the contents of a file, to a docker build command,
after much consternation, I decided to:
base64 encode the contents in the Makefile (so that I could have a single line and pass them as a docker build arg...)
base64 decode the contents in the Dockerfile (and write them to a file)
see example below.
nb: In my particular case, I wanted to pass an ssh key, during the image build, using the example from https://vsupalov.com/build-docker-image-clone-private-repo-ssh-key/ (using a multi stage docker build to clone a git repo, then drop the ssh key from the final image in the 2nd stage of the build)
Makefile
...
MY_VAR_ENCODED=$(shell cat /path/to/my/file | base64)
my-build:
#docker build \
--build-arg MY_VAR_ENCODED="$(MY_VAR_ENCODED)" \
--no-cache \
-t my-docker:build .
...
Dockerfile
...
ARG MY_VAR_ENCODED
RUN mkdir /root/.ssh/ && \
echo "${MY_VAR_ENCODED}" | base64 -d > /path/to/my/file/in/container
...
Why don't you make use of the \n character in your string to define the end-of-line? Also add the extra backslash to add it over multiple lines.
ANNOUNCE_BODY=" \n\
Version $(VERSION) of $(PACKAGE_NAME) has been released \n\
\n\
It can be downloaded from $(DOWNLOAD_URL) \n\
\n\
etc, etc"
Just a postscript to Eric Melski's answer: You can include the output of commands in the text, but you must use the Makefile syntax "$(shell foo)" rather than the shell syntax "$(foo)". For example:
define ANNOUNCE_BODY
As of $(shell date), version $(VERSION) of $(PACKAGE_NAME) has been released.
It can be downloaded from $(DOWNLOAD_URL).
endef
You should use "define/endef" Make construct:
define ANNOUNCE_BODY
Version $(VERSION) of $(PACKAGE_NAME) has been released.
It can be downloaded from $(DOWNLOAD_URL).
etc, etc.
endef
Then you should pass value of this variable to shell command. But, if you do this using Make variable substitution, it will cause command to split into multiple:
ANNOUNCE.txt:
echo $(ANNOUNCE_BODY) > $# # doesn't work
Qouting won't help either.
The best way to pass value is to pass it via environment variable:
ANNOUNCE.txt: export ANNOUNCE_BODY:=$(ANNOUNCE_BODY)
ANNOUNCE.txt:
echo "$${ANNOUNCE_BODY}" > $#
Notice:
Variable is exported for this particular target, so that you can reuse that environment will not get polluted much;
Use environment variable (double qoutes and curly brackets around variable name);
Use of quotes around variable. Without them newlines will be lost and all text will appear on one line.
This doesn't give a here document, but it does display a multi-line message in a way that's suitable for pipes.
=====
MSG = this is a\\n\
multi-line\\n\
message
method1:
#$(SHELL) -c "echo '$(MSG)'" | sed -e 's/^ //'
=====
You can also use Gnu's callable macros:
=====
MSG = this is a\\n\
multi-line\\n\
message
method1:
#echo "Method 1:"
#$(SHELL) -c "echo '$(MSG)'" | sed -e 's/^ //'
#echo "---"
SHOW = $(SHELL) -c "echo '$1'" | sed -e 's/^ //'
method2:
#echo "Method 2:"
#$(call SHOW,$(MSG))
#echo "---"
=====
Here's the output:
=====
$ make method1 method2
Method 1:
this is a
multi-line
message
---
Method 2:
this is a
multi-line
message
---
$
=====
With GNU Make 3.82 and above, the .ONESHELL option is your friend when it comes to multiline shell snippets. Putting together hints from other answers, I get:
VERSION = 1.2.3
PACKAGE_NAME = foo-bar
DOWNLOAD_URL = $(PACKAGE_NAME).somewhere.net
define nwln
endef
define ANNOUNCE_BODY
Version $(VERSION) of $(PACKAGE_NAME) has been released.
It can be downloaded from $(DOWNLOAD_URL).
etc, etc.
endef
.ONESHELL:
# mind the *leading* <tab> character
version:
#printf "$(subst $(nwln),\n,$(ANNOUNCE_BODY))"
Make sure, when copying and pasting the above example into your editor, that any <tab> characters are preserved, else the version target will break!
Note that .ONESHELL will cause all targets in the Makefile to use a single shell for all their commands.
GNU `make' manual, 6.8: Defining Multi-Line Variables
In the spirit of .ONESHELL, it's possible to get pretty close in .ONESHELL challenged environments:
define _oneshell_newline_
endef
define oneshell
#eval "$$(printf '%s\n' '$(strip \
$(subst $(_oneshell_newline_),\n, \
$(subst \,\/, \
$(subst /,//, \
$(subst ','"'"',$(1))))))' | \
sed -e 's,\\n,\n,g' -e 's,\\/,\\,g' -e 's,//,/,g')"
endef
An example of use would be something like this:
define TEST
printf '>\n%s\n' "Hello
World\n/$$$$/"
endef
all:
$(call oneshell,$(TEST))
That shows the output (assuming pid 27801):
>
Hello
World\n/27801/
This approach does allow for some extra functionality:
define oneshell
#eval "set -eux ; $$(printf '%s\n' '$(strip \
$(subst $(_oneshell_newline_),\n, \
$(subst \,\/, \
$(subst /,//, \
$(subst ','"'"',$(1))))))' | \
sed -e 's,\\n,\n,g' -e 's,\\/,\\,g' -e 's,//,/,g')"
endef
These shell options will:
Print each command as it is executed
Exit on the first failed command
Treat use of undefined shell variables as an error
Other interesting possibilities will likely suggest themselves.
I like alhadis's answer best. But to keep columnar formatting, add one more thing.
SYNOPSIS := :: Synopsis: Makefile\
| ::\
| :: Usage:\
| :: make .......... : generates this message\
| :: make synopsis . : generates this message\
| :: make clean .... : eliminate unwanted intermediates and targets\
| :: make all ...... : compile entire system from ground-up\
endef
Outputs:
:: Synopsis: Makefile
::
:: Usage:
:: make .......... : generates this message
:: make synopsis . : generates this message
:: make clean .... : eliminate unwanted intermediates and targets
:: make all ...... : compile entire system from ground-up
Not really a helpful answer, but just to indicate that 'define' does not work as answered by Ax (did not fit in a comment):
VERSION=4.3.1
PACKAGE_NAME=foobar
DOWNLOAD_URL=www.foobar.com
define ANNOUNCE_BODY
Version $(VERSION) of $(PACKAGE_NAME) has been released
It can be downloaded from $(DOWNLOAD_URL)
etc, etc
endef
all:
#echo $(ANNOUNCE_BODY)
It gives an error that the command 'It' cannot be found, so it tries to interpret the second line of ANNOUNCE BODY as a command.
It worked for me:
ANNOUNCE_BODY="first line\\nsecond line"
all:
#echo -e $(ANNOUNCE_BODY)
GNU Makefile can do things like the following. It is ugly, and I won't say you should do it, but I do in certain situations.
PROFILE = \
\#!/bin/sh.exe\n\
\#\n\
\# A MinGW equivalent for .bash_profile on Linux. In MinGW/MSYS, the file\n\
\# is actually named .profile, not .bash_profile.\n\
\#\n\
\# Get the aliases and functions\n\
\#\n\
if [ -f \$${HOME}/.bashrc ]\n\
then\n\
. \$${HOME}/.bashrc\n\
fi\n\
\n\
export CVS_RSH="ssh"\n
#
.profile:
echo -e "$(PROFILE)" | sed -e 's/^[ ]//' >.profile
make .profile creates a .profile file if one does not exist.
This solution was used where the application will only use GNU Makefile in a POSIX shell environment. The project is not an open source project where platform compatibility is an issue.
The goal was to create a Makefile that facilitates both setup and use of a particular kind of workspace. The Makefile brings along with it various simple resources without requiring things like another special archive, etc. It is, in a sense, a shell archive. A procedure can then say things like drop this Makefile in the folder to work in. Set up your workspace enter make workspace, then to do blah, enter make blah, etc.
What can get tricky is figuring out what to shell quote. The above does the job and is close to the idea of specifying a here document in the Makefile. Whether it is a good idea for general use is a whole other issue.
I believe the safest answer for cross-platform use would be to use one echo per line:
ANNOUNCE.txt:
rm -f $#
echo "Version $(VERSION) of $(PACKAGE_NAME) has been released" > $#
echo "" >> $#
echo "It can be downloaded from $(DOWNLOAD_URL)" >> $#
echo >> $#
echo etc, etc" >> $#
This avoids making any assumptions of on the version of echo available.
Use string substitution:
VERSION := 1.1.1
PACKAGE_NAME := Foo Bar
DOWNLOAD_URL := https://go.get/some/thing.tar.gz
ANNOUNCE_BODY := Version $(VERSION) of $(PACKAGE_NAME) has been released. \
| \
| It can be downloaded from $(DOWNLOAD_URL) \
| \
| etc, etc
Then in your recipe, put
#echo $(subst | ,$$'\n',$(ANNOUNCE_BODY))
This works because Make is substituting all occurrences of | (note the space) and swapping it with a newline character ($$'\n'). You can think of the equivalent shell-script invocations as being something like this:
Before:
$ echo "Version 1.1.1 of Foo Bar has been released. | | It can be downloaded from https://go.get/some/thing.tar.gz | | etc, etc"
After:
$ echo "Version 1.1.1 of Foo Bar has been released.
>
> It can be downloaded from https://go.get/some/thing.tar.gz
>
> etc, etc"
I'm not sure if $'\n' is available on non-POSIX systems, but if you can gain access to a single newline character (even by reading a string from an external file), the underlying principle is the same.
If you have many messages like this, you can reduce noise by using a macro:
print = $(subst | ,$$'\n',$(1))
Where you'd invoke it like this:
#$(call print,$(ANNOUNCE_BODY))
Hope this helps somebody. =)
As an alternative you can use the printf command. This is helpful on OSX or other platforms with less features.
To simply output a multiline message:
all:
#printf '%s\n' \
'Version $(VERSION) has been released' \
'' \
'You can download from URL $(URL)'
If you are trying to pass the string as an arg to another program, you can do so like this:
all:
/some/command "`printf '%s\n' 'Version $(VERSION) has been released' '' 'You can download from URL $(URL)'`"