What do #, - and + do as prefixes to recipe lines in Make? - makefile

In the GNU Makefile manual, it mentions these prefixes.
If .ONESHELL is provided, then only the first line of the recipe will be checked for the special prefix characters (‘#’, ‘-’, and ‘+’).
What do these prefixes do, and where are they mentioned?

They control the behaviour of make for the tagged command lines:
# suppresses the normal 'echo' of the command that is executed.
- means ignore the exit status of the command that is executed (normally, a non-zero exit status would stop that part of the build).
+ means 'execute this command under make -n' (or 'make -t' or 'make -q') when commands are not normally executed. See also the POSIX specification for make and also §9.3 of the GNU Make manual.
The + notation is a (POSIX-standardized) generalization of the de facto (non-standardized) mechanism whereby a command line containing ${MAKE} or $(MAKE) is executed under make -n.
(# is discussed in §5.2 of the GNU Make manual; - is described in §5.5; and §5.7.1 mentions the use of +.)

# prevents the command line from echoing out to the console. You can do it globally with -s or --keep-silent
- tells make to keep going, even if the command fails for some reason. You can do it globally via the -i flag (or --ignore-errors).
+ I was not familar with before you asked. As near as I can tell, it negates the effect of -n, -t, and -q, all of which basically tell make to not actually run the commands. So a line with a + at the front would get run anyway.
If you read the official Gnu Make manual, they are all mentioned in Chapter 5. In my old copy of the manual that was the chapter on "commands", but term du jour now seems to be "recipes".

Related

why ‘&&’ disables errexit(set -e)?

consider script here:
set -e
make && make install
echo "SHOULD NOT BE HERE"
I expect that if make fails, the script will be aborted, but it's not:
make: *** No targets specified and no makefile found. Stop.
SHOULD NOT BE HERE
But, if I changed it like this:
set -e
make
make install
echo "SHOULD NOT BE HERE"
It works as expected:
make: *** No targets specified and no makefile found. Stop.
Why this happens?
Due to make && make install is commonly used in my build script, how should I use it correctly?
And please DO NOT link this question to Using set -e / set +e in bash with functions, it's not the same question.
Quoting from the answer to the question you linked:
Quoting sh(1) from FreeBSD, which explains this better than bash's man page:
-e errexit
Exit immediately if any untested command fails in non-interactive
mode. The exit status of a command is considered to be explicitly
tested if the command is part of the list used to control an if,
elif, while, or until; if the command is the left hand operand of
an “&&” or “||” operator; or if the command is a pipeline preceded
by the ! operator. If a shell function is executed and its exit
status is explicitly tested, all commands of the function are con‐
sidered to be tested as well.
errexit exits only if an untested command fails. Using && (or ||) will means that bash considers the command to the left of && to be explicitly tested (which in turn means that it will not be handled by errexit).
See also here (specifically the part about list constructs).
As far a I know, there is no way to achieve what you would like by setting bash options.

How to run csh script in bash shell

My default shell is bash in Ubuntu 14.04. I have a csh script file named clean.sh with the following make command:
#! /bin/csh -f
make -f commande.make del
And commande.make has
CKHOME=../CHEMKIN/DATA_BASES
LIN_DATA=${CKHOME}/LIN_FILES/
LINK_CKTP=${CKHOME}/LINK_CKTP_FILES/
#-----------------------------------------------------
include schema_cinetique.make
LINKFILE=${NAME}_LINK
LINKTPFILE=${NAME}_LINKTP
LINKFILE_OLD=${NAME_OLD}_LINK
LINKFILE_NEW=${NAME_NEW}_LINK
#-----------------------------------------------------
cplink :
${COPY} ${LINK_CKTP}${LINKFILE} LINK
cplink2 :
${COPY} ${LINK_CKTP}${LINKFILE} LINKZ1
tplink :
${COPY} ${LINK_CKTP}${LINKTPFILE} LINKTPZ1
calcul :
${COPY} jobtimp1 LJOBNZ1
${COPY} unsteadyf.dat1 DATZ1
del :
${DELETE} LINKZ1 LINKTPZ1 LJOBNZ1 DATZ1 SOLASUZ1
I opened the terminal and moved to the location and tried
./clean.sh
or
csh clean.sh &
or
csh -f clean.sh
Nothing worked.
I got the following line in the terminal,
LINKZ1 LINKTPZ1 LJOBNZ1 DATZ1 SOLASUZ1
make: LINKZ1: Command not found
make: *** [del] Error 127
So, how to run clean.sh file ?
You are confused. The Csh script contains a single command which actually runs identically in Bash.
#!/bin/bash
make -f commande.make del
Or, for that matter, the same with #!/bin/sh. Or, in this individual case, even sh clean.sh, since the shebang is then just a comment, and the commands in the file are available in sh just as well as in csh.
Once make runs, that is what parses and executes the commands in commande.make. make is not a "Fortran command", it is a utility for building projects (but the makefile named commande.make probably contains some instructions for how to compile Fortran code).
In the general case, Csh and Bash are incompatible, but the differences are in the shell's syntax itself (so, the syntax of loops and conditionals, etc, as well as variable assignments and various other shell builtins).
As an aside, Csh command files should probably not have a .sh extension, as that vaguely implies Bourne shell (sh) syntax. File extensions on Unix are just a hint to human readers, so not technically important; but please don't confuse them/us.
(As a further aside, nobody should be using Csh in 2022. There was a time when the C shell was attractive compared to its competition, but that was on the order of 40 years ago.)
The subsequent errors you are reporting seem to indicate that the makefile depends on some utilities which you have not installed. Figuring that out is a significant enough and separate enough question that you should probably ask a new question about that, probably with more debugging details. But in brief, it seems that make needs to be run with parameters to indicate what NAME and COPY (and probably some other variables) should be. Try with make -f commande.make COPY=cp DELETE=rm NAME=foobar for a start, but it's probably not yet anywhere near sufficient.
(I would actually assume that there will be a README file or similar to actually instruct you how to use commande.make since it seems to have some local conventions of its own.)
It seems the script is written having portability in mind, i.e. the name of the cp and rm binaries is kept in variables rather than hard-coding it. My best guess is that this has been done to make it possible to run the script on non UNIX systems, like Windows.
To make it work, export the respective variables before running the script. For the del action you are calling, only the DELETE variable is needed. It should be set to rm which is the command used to remove files on Linux:
export DELETE=rm
./clean.sh
Note: exporting the variable can also be done in one line when invoking the script, by prepending it to the command line:
DELETE=rm ./clean.sh
This behaviour is described in the bash manual:
The environment for any simple command or function may be augmented temporarily by prefixing it with parameter assignments, as described in Shell Parameters. These assignment statements affect only the environment seen by that command.

Why does printf behave differently when called from a Makefile?

The printf program can be used to print binary data, e.g.:
$ printf '%b' '\xff\xff'
��
If I put this in a Makefile on its own, it works the same:
all:
printf '%b' '\xff\xff'
$ make
printf '%b' '\xff\xff'
��
However, if I want to do anything else on the same shell invocation in the Makefile, for example to redirect it to a file, or just printing something else afterwards, then although the command printed by Make doesn't change (suggesting it's not an escaping issue), but the output changes to a backslash followed by an "x" followed by a double "f", twice:
all:
printf '%b' '\xff\xff'; printf 'wtf?\n'
make
printf '%b' '\xff\xff'; printf 'wtf?\n'
\xff\xffwtf?
What is going on here? Why do the two printfs in one line behave differently than a single printf?
#chepner is on the right track in their comment but the details are not quite right:
This is wild speculation, but I suspect there is some sort of
optimization being applied by make that causes the first example, as a
simple command, to be executing a third option, the actual binary
printf (found in /usr/bin, perhaps), rather than a shell. In your
second example, the ; forces make to use a shell to execute the shell
command line.
Make always uses /bin/sh as its shell, regardless of what the user is using as their shell. On some systems, /bin/sh is bash (which has a builtin printf) and on some systems /bin/sh is something different (typically dash which is a lightweight, POSIX-conforming shell) which probably doesn't have a shell built-in.
On your system, /bin/sh is bash. But, when you have a "simple command" that doesn't require a shell (that is, make itself has enough trivial quoting smarts to understand your command) then to be more efficient make will invoke that command directly rather than running the shell.
That's what's happening here: when you run the simple command (no ;) make will invoke the command directly and run /usr/bin/printf. When you run the more complex command (including a ;) make will give up running the command directly and invoke your shell... which is bash, which uses bash's built-in printf.
Basically, your script is not POSIX-conforming (there is no %b in the POSIX standard) and so what it does is not well-defined. If you want the SAME behavior always you should use /usr/bin/printf to force that always to be used. Forcing make to always run a shell and never use its fast path is much trickier; you'll need to include a special character like a trailing ; in each command.

using different shell with GNU Make recursively: why substituting a different /bin/sh as root does not seem to work?

I want to temporarily use a different shell than default /bin/sh with recursive GNU Make setup. Because of recursivity, I can't use the SHELL hack, or I would have to (is that right?) edit all the many makefiles (on each command line I would have to do
$(MAKE) SHELL=$(SHELL)
), which I don't want to do.
Since this is just temporary, I thought I would become root, back up /bin/sh and edit it to something what I want:
#!/bin/dash
echo dash shell
/bin/dash $#
It follows from the GNU Make manual that it runs the recipe lines with
execve("/bin/sh", "-c <recipe line>")
then based on the documentation of execve this should work. But it does not. When I run GNU Make, it completely ignores the above changed /bin/sh and works as if it was not changed.
Why and how to get this to work?
I don't know, this is just a hypothesis, supported by some evidence other than above. When the internal SHELL make variable is default, that is /bin/sh, the login shell, then GNU Make does not run recipes with
execve("/bin/sh", "-c recipe")
but instead, just uses
execve("recipe")
This would clearly contradict the following line from the GNU Make manual
"the program /bin/sh is used as the shell. The argument(s) passed to the shell are taken from the variable .SHELLFLAGS. The default value of .SHELLFLAGS is -c normally"

Execute a line in Bash without aborting if it fails?

Is there a generic way in a bash script to "try" something but continue if it fails? The analogue in other languages would be wrapping it in a try/catch and ignoring the exception.
Specifically I am trying to source an optional satellite script file:
. $OPTIONAL_PATH
But when executing this, if $OPTIONAL_PATH doesn't exist, the whole script screeches to a halt.
I realize I could check to see if the file exists before sourcing it, but I'm curious if there is a generic reusable mechanism I can use that will ignore the error without halting.
Update: Apparently this is not normal behavior. I'm not sure why this is happening. I'm not explicitly calling set -e anywhere ($- is hB), yet it halts on the error. Here is the output I see:
./script.sh: line 36: projects/mobile.sh: No such file or directory
I added an echo "test" immediately after the source line, but it never prints, so it's not anything after that line that is exiting. I am running Mac OS 10.9.
Update 2: Nevermind, it was indeed shebanged as #!/bin/sh instead of #!/bin/bash. Thanks for the informative answer, Kaz.
Failed commands do not abort the script unless you explicitly configure that mode with set -e.
With regard to Bash's dot command, things are tricky. If we invoke bash as /bin/sh then it bails the script if the . command does not find the file. If we invoke bash as /bin/bash then it doesn't fail!
$ cat source.sh
#!/bin/sh
. nonexistent
echo here
$ ./source.sh
./source.sh: 3: .: nonexistent: not found
$ ed source.sh
35
1s/sh/bash/
wq
37
$ ./source.sh
./source.sh: line 3: nonexistent: No such file or directory
here
It does respond to set -e; if we have #!/bin/bash, and use set -e, then the echo is not reached. So one solution is to invoke bash this way.
If you want to keep the script maximally portable, it looks like you have to do the test.
The behavior of the dot command aborting the script is required by POSIX. Search for the "dot" keyword here. Quote:
If no readable file is found, a non-interactive shell shall abort; an interactive shell shall write a diagnostic message to standard error, but this condition shall not be considered a syntax error.
Arguably, this is the right thing to do, because dot is used for including pieces of the script. How can the script continue when a whole chunk of it has not been found?
Otherwise arguably, this is braindamaged behavior inconsistent with the treatment of other commands, and so Bash makes it consistent in its non-POSIX-conforming mode. If programmers want a command to fail, they can use set -e.
I tend to agree with Bash. The POSIX behavior is actually more broken than initially meets the eye, because this also doesn't work the way you want:
if . nonexistent ; then
echo loaded
fi
Even if the command is tested, it still aborts the script when it bails.
Thank GNU-deness we have alternative utilities, with source code.
You have several options:
Make sure set -e wasn't used, or turn it off with set +e. Your bash script should not exit by default simply because the . command failed.
Test that the file exists prior to sourcing.
[ -f "$OPTIONAL_PATH" ] && . "$OPTIONAL_PATH"
This option is complicated by the fact that if $OPTIONAL_PATH does not contain
any slashes, . will still try to find the file in your path.
If you want to keep set -e on, "hide" the failure like this:
. "$OPTIONAL_PATH" || true
Even if the source fails, the exit status of the command list as a whole will be 0, due to the || true.
(Much of this is covered [better] by Kaz's answer, especially the references to the POSIX standard, but I wasn't sure when or if he would undelete his answer.)
This is not the default behavior. Did you set -e or use #!/bin/bash -e anywhere in your script, to make it automatically exit on failure?
If so, you can use
. $OPTIONAL_PATH || true
to continue anyways.

Resources