192

In the clean section of my Makefile I am trying to check if the file exists before deleting permanently. I use this code but I receive errors.

What's wrong with it?

 if [ -a myApp ]
 then
     rm myApp
 fi

I get this error message

 if [ -a myApp ]
 /bin/sh: Syntax error: end of file unexpected (expecting "then")
 make: *** [clean] Error 2
user2226755
  • 12,494
  • 5
  • 50
  • 73
Abruzzo Forte e Gentile
  • 14,423
  • 28
  • 99
  • 173
  • Is myApp a variable or an actual filename? – BugFinder Apr 05 '11 at 14:21
  • myApp is for myApplication i.e. the filename by the build process. – Abruzzo Forte e Gentile Apr 05 '11 at 15:31
  • 7
    If you just want to avoid make stopping if the file does not exist, `rm -rf myApp` could be an alternative. Or preceding the command with a dash (`-rm myApp`) to make make ignore the error from rm (it will however print an ugly message). – thomasa88 Jan 06 '14 at 09:10
  • 3
    Your problem was that make treats each line in a rule as a separate command and sends them individually to the shell. It's like running just `if [ -a myApp ]' on its own. If you get this error you either need a solution which joins the lines into one (using \) or which ends up with each line independent of the other. There are now several of these below. – Michael Jan 26 '19 at 11:45
  • Where can I learn about the options like "-a". I couldn't find it in https://www.gnu.org/software/make/manual/make.html ? – zk9099 Jan 07 '23 at 15:28

16 Answers16

254

It's strange to see so many people using shell scripting for this. I was looking for a way to use native makefile syntax, because I'm writing this outside of any target. You can use the wildcard function to check if file exists:

 ifeq ($(UNAME),Darwin)
     SHELL := /opt/local/bin/bash
     OS_X  := true
 else ifneq (,$(wildcard /etc/redhat-release))
     OS_RHEL := true
 else
     OS_DEB  := true
     SHELL := /bin/bash
 endif 

Update:

I found a way which is really working for me:

ifneq ("$(wildcard $(PATH_TO_FILE))","")
    FILE_EXISTS = 1
else
    FILE_EXISTS = 0
endif
user2226755
  • 12,494
  • 5
  • 50
  • 73
holms
  • 9,112
  • 14
  • 65
  • 95
  • I've got actually alternative variant recently. I'm discovering package managers with this `yum := $(shell { type yum; } 2>/dev/null)` and with `ifdef` you can check if variable empty or not, and then set something like `RHEL:=true` – holms Feb 26 '14 at 03:16
  • 4
    tried this, but I just keep getting ```Makefile:133: *** unterminated call to function `wildcard': missing `)'. Stop.``` – Ant6n Dec 08 '14 at 05:29
  • @arsenbonbon Your comment really helps. Appreciated. – tom_mai78101 Jan 27 '15 at 18:52
  • @Ant6n You might be missing a parentheses. Please check. – tom_mai78101 Jan 27 '15 at 18:53
  • This fails if $(PATH_TO_FILE) evaluates to a filename containing parentheses. – Christopher Aug 10 '16 at 19:31
  • 1
    Great use of wildcard, so it can be done with makefile itself. Neat :) – anoop Aug 03 '17 at 21:48
  • 4
    Helps to also understand what `$(wildcard pattern)` actually does. [See link](https://www.gnu.org/software/make/manual/html_node/Wildcard-Function.html). – Dr. Dan Nov 05 '18 at 22:43
  • Is there a way to make this work within a make rule (when "indented")? – Danijel Nov 08 '18 at 10:32
  • 2
    More concise: `FILE_EXISTS := $(or $(and $(wildcard $(PATH_TO_FILE)),1),0)` – cmaster - reinstate monica Nov 16 '18 at 14:57
  • 5
    It's worth noting if you're running on Windows under cygwin, using wildcard in this manner does a case sensitive match on the filename, so if the file exists but with different case than the path it won't be found. There doesn't seem to be any way to override this behaviour within the makefile. – Roger Sanders Nov 27 '18 at 10:26
  • 1
    `directives only work if they're not indented` with **tabs**. Plus, they run at makefile parsing stage, not in "runtime" (target execution) stage. – Victor Sergienko Jan 25 '19 at 19:08
  • Running the latter example, I'm getting `/bin/sh: -c: line 0: syntax error near unexpected token `"",""'` – Joe Flack Aug 03 '23 at 21:52
103

The second top answer mentions ifeq, however, it fails to mention that this ifeq must be at the same indentation level in the makefile as the name of the target, e.g., to download a file only if it doesn't currently exist, the following code could be used:

download:
ifeq (,$(wildcard ./glob.c))
    curl … -o glob.c
endif

# THIS DOES NOT WORK!
download:
    ifeq (,$(wildcard ./glob.c))
        curl … -o glob.c
    endif
user2226755
  • 12,494
  • 5
  • 50
  • 73
cnst
  • 25,870
  • 6
  • 90
  • 122
  • did not work until I added backslash `\` after if fi – taras Dec 29 '18 at 10:01
  • 6
    This answer will be a bit weird; the file check happens when the makefile is processed but the action will happen later when the target is built by make. If you delete the file in the meantime then the file won't be created. I have put in an edit to make it clearer. – Michael Jan 26 '19 at 10:58
  • Thanks much! This point was not clear from reading the manual. – Kevin Buchs May 09 '19 at 05:22
77

The problem is when you split your command over multiple lines. So, you can either use the \ at the end of lines for continuation as above or you can get everything on one line with the && operator in bash.

Then you can use a test command to test if the file does exist, e.g.:

test -f myApp && echo File does exist

-f file True if file exists and is a regular file.

-s file True if file exists and has a size greater than zero.

or does not:

test -f myApp || echo File does not exist
test ! -f myApp && echo File does not exist

The test is equivalent to [ command.

[ -f myApp ] && rm myApp   # remove myApp if it exists

and it would work as in your original example.

See: help [ or help test for further syntax.

user2226755
  • 12,494
  • 5
  • 50
  • 73
kenorb
  • 155,785
  • 88
  • 678
  • 743
  • 5
    I would have upvoted, but you did not warn that `-s` is a special case for `exists and has a size greater than zero`. The question as written is size-agnostic, so existence should be checked using `test -e` for a file or `-d` for a directory. Empty files can be especially useful as (for want of a better term) indicators/sentinels, which might be quite relevant for `make`. – underscore_d Sep 14 '15 at 22:07
  • Thanks for the suggestion. Changed `-f` by default, as it's more common to use. – kenorb Sep 14 '15 at 22:59
  • How can I get `test` on Windows? – thowa Feb 04 '16 at 08:49
  • This I could get to work on one line in make file like this: @ test -f file && echo file exist -- however I could not get the other examples to work – serup May 27 '16 at 07:16
  • 4
    In order for this to work you need to add `|| true` at the end so the command return true when file don't exists. – jcubic Jan 21 '18 at 14:17
  • This answer should be upvoted as it is easy, simple, one-line, and solves the problem. – alexpanter May 02 '18 at 18:27
  • @Alexander: No, this relies on OS-specific shell trickery, where the wildcard function is a built-in GNU Make means of achieving the same thing without OS-specific calls. – antred Sep 24 '18 at 12:07
  • I agree with @Alexander - this is a good answer though it needs to be clear this works because it's a one liner using && rather than multiline with if. I put in an edit to improve it. – Michael Jan 26 '19 at 11:13
  • In the "or does not:" section your first echo is wrong, should be "does exist". i.e. Your two echos are the same for opposite cases. (Copy & Paste error I guess) – Andrew Mackenzie Jan 27 '19 at 17:49
  • 2
    @AndrewMackenzie `test -f myApp || CMD`, notice the `||`, so if `-f` will fail - does not exist (`||`), then run the command. Does it make sense? – kenorb Jan 27 '19 at 18:58
55

It may need a backslash on the end of the line for continuation (although perhaps that depends on the version of make):

if [ -a myApp ] ; \
then \
     rm myApp ; \
fi;
       
user2226755
  • 12,494
  • 5
  • 50
  • 73
Mark Wilkins
  • 40,729
  • 5
  • 57
  • 110
  • 3
    seems to be not a Makefile syntax? http://sunsite.ualberta.ca/Documentation/Gnu/make-3.79/html_chapter/make_7.html – holms Dec 13 '13 at 12:39
  • @holms its a bash syntax. By escaping the new lines it allows it to be handled as a single bash command. By default in `make` a new line would be a new bash command. The major caveat of this, other than the annoyance of having lots of `\ ` at the end of lines is that every command must be terminated with the `;` which would otherwise be implicit in bash. – flungo Dec 10 '14 at 08:51
  • 1
    The right answer is by @holms. Neither '\' nor wildcard is exactly intended for this purpose. The reason why you would use wildcard is for code clarity. Not only is this method less readable, it's more prone to syntax errors. – Maitreya Apr 25 '15 at 00:15
  • 1
    the link @holms provided does not work anymore, use http://www.gnu.org/software/make/manual/make.html#Conditionals instead – fero May 05 '16 at 09:19
  • this is a great answer because it matches what is wrong with what the original questioner did and it will work depending on whether the file exists at the time the target is build rather than at the time the Makefile is started which is what most people would expect and want most of the time. In a few weird cases the answer from @cnst would be better. – Michael Jan 26 '19 at 11:05
  • The original question, and this answer are attacking it at the `inside an action command` phase, whereas the @holms answer is attacking it at the `makefile parsing` phase. So which answer is best depends on what you need. – Jesse Chisholm Apr 04 '19 at 19:23
37

Or just put it on one line, as make likes it:

if [ -a myApp ]; then rm myApp; fi;
user2226755
  • 12,494
  • 5
  • 50
  • 73
Jeroen Ooms
  • 31,998
  • 35
  • 134
  • 207
24

One line solution:

   [ -f ./myfile ] && echo exists

One line solution with error action:

   [ -f ./myfile ] && echo exists || echo not exists

Example used in my make clean directives:

clean:
    @[ -f ./myfile ] && rm myfile || true

And make clean works without error messages!

user2226755
  • 12,494
  • 5
  • 50
  • 73
  • 3
    just do @rm -f myfile. Because of the "-f" flag, "rm" will exit with 0 regardless of whether the file exists or not. – Alexey Polonsky Nov 15 '16 at 14:21
  • Or, `-rm myfile` the lead dash telling make to ignore any error. – Jesse Chisholm Apr 04 '19 at 19:28
  • 1
    In my case, leaving off the || caused problems. Your final example, where you returned true if the file did not exist, addressed this nicely. Thank you! – Flic Apr 10 '19 at 01:34
  • For me path to myfile needs to be with apostrophe('): `@[ -f 'myfile' ] && rm myfile` – Sten Oct 14 '19 at 16:06
16
FILE1 = /usr/bin/perl
FILE2 = /nofile

ifeq ($(shell test -e $(FILE1) && echo -n yes),yes)
    RESULT1=$(FILE1) exists.
else
    RESULT1=$(FILE1) does not exist.
endif

ifeq ($(shell test -e $(FILE2) && echo -n yes),yes)
    RESULT2=$(FILE2) exists.
else
    RESULT2=$(FILE2) does not exist.
endif

all:
    @echo $(RESULT1)
    @echo $(RESULT2)

execution results:

bash> make
/usr/bin/perl exists.
/nofile does not exist.
user2226755
  • 12,494
  • 5
  • 50
  • 73
Robin Hsu
  • 4,164
  • 3
  • 20
  • 37
  • This helped me, I think some missed that the OP was asking about makefile. I didn't understand why "&& echo -n yes" is necessary. Explanation: if test -e returns 1 (not found) then shell won't execute the echo command, and therefore won't match the yes in ifeq – Brad Dre Mar 11 '19 at 23:29
  • I think you understand it correctly in your explanation statement. – Robin Hsu Mar 12 '19 at 02:30
  • @BradDre -- The `echo -n yes` changes the success of `test` into the string `yes` without NL. The `ifeq` can then compare it with the hard coded `yes`. All because `ifeq` wants a string to compare to, not a success status from a shell command. – Jesse Chisholm Apr 04 '19 at 19:27
  • I had to remove the `-n` part to get it to work... – Rick Sep 14 '22 at 04:37
14

Missing a semicolon

if [ -a myApp ];
then
  rm myApp
fi

However, I assume you are checking for existence before deletion to prevent an error message. If so, you can just use rm -f myApp which "forces" delete, i.e. doesn't error out if the file didn't exist.

user2226755
  • 12,494
  • 5
  • 50
  • 73
drysdam
  • 8,341
  • 1
  • 20
  • 23
  • 1
    ThHat's exactly what I wanted to do. Thanks a lot. – Abruzzo Forte e Gentile Apr 05 '11 at 15:31
  • 1
    this won't work in a Makefile because the if is still spread over multiple lines - you need to either put this on one line or use \es. and even if you added the \ es you are still missing some semi-colons. – Michael Jan 26 '19 at 11:16
4
ifneq ("$(wildcard $(PATH_TO_FILE))","")
    FILE_EXISTS = 1
else
    FILE_EXISTS = 0
endif

This solution posted above works best. But make sure that you do not stringify the PATH_TO_FILE assignment E.g.,

PATH_TO_FILE = "/usr/local/lib/libhl++.a" # WILL NOT WORK

It must be

PATH_TO_FILE = /usr/local/lib/libhl++.a
user2226755
  • 12,494
  • 5
  • 50
  • 73
1
test ! -f README.md || echo 'Support OpenSource!' >> README.md

"If README.md does not exist, do nothing (and exit successfully). Otherwise, append text to the end."

If you use && instead of || then you generate an error when the file doesn't exist:

Makefile:42: recipe for target 'dostuff' failed
make: *** [dostuff] Error 1
Matt Janssen
  • 1,505
  • 13
  • 14
1

Use test command to check if the file exists or not and then use rm to delete it.\

Syntax for the file command is -

test -f FILENAME && echo exists || echo not exists

Syntax for deleting the file is -

rm -rf FILENAME

So now we need a command to delete the file only if it exists so we will only use OR || with the test command

test -f FILENAME || rm -rf FILENAME

use can use multiple commands by using and && within the parenthesis ()

test -f FILENAME || (rm -rf FILENAME && echo "file deleted as it exists")
user2226755
  • 12,494
  • 5
  • 50
  • 73
Alex
  • 37
  • 2
  • This does not work in a makefile. It ill always run the `rm -rf FILENAME` regardless of the first `test` result – nathanchere Dec 07 '21 at 15:36
1

I wanted to command above, but reputation :)

You can have multi-line commands in gnu make targets by adding the .ONESHELL: directive:

all-in-one-shell:
    if [ -a MyApp ] ; then
        echo "here"
    fi

.ONESHELL: all-in-one-shell

This eliminates trying to come up with creative one-liners or backslash everything.

user2226755
  • 12,494
  • 5
  • 50
  • 73
0

The answers like the one from @mark-wilkins using \ to continue lines and ; to terminate them in the shell or like the ones from @kenorb changing this to one line are good and will fix this problem.

there's a simpler answer to the original problem (as @alexey-polonsky pointed out). Use the -f flag to rm so that it won't trigger an error

rm -f myApp

this is simpler, faster and more reliable. Just be careful not to end up with a slash and an empty variable

rm -f /$(myAppPath) #NEVER DO THIS

you might end up deleting your system.

Michael
  • 367
  • 2
  • 10
  • 1
    This is a good answer for deleting the file that the OP had, but I'm pretty sure most people who find this question aren't actually looking for deleting any files; this is actually evidenced by the fact that most answers don't even mention `rm` at all; BTW, I'm pretty sure that `rm -f /$(myAppPath)` won't do any damage, either, because `/` is a directory, and the `-r` is missing. – cnst Jan 26 '19 at 18:13
  • This is not a simpler solution. Like @cnst said, there may be reasons why the original poster does not simply want to do an `rm -f`, e.g. they may want to `rm` a variable. – dancow Nov 03 '19 at 01:42
  • 1
    I like this simple answer. if the file exists delete, if it doesn't exist, don't complain! – Chan Kim Jul 14 '21 at 08:38
0

I was trying:

[ -f $(PROGRAM) ] && cp -f $(PROGRAM) $(INSTALLDIR)

And the positive case worked but my ubuntu bash shell calls this TRUE and breaks on the copy:

[ -f  ] && cp -f  /home/user/proto/../bin/
cp: missing destination file operand after '/home/user/proto/../bin/'

After getting this error, I google how to check if a file exists in make, and this is the answer...

0

Slightly different from the question, but in case you have a variable containing a list of files which you want to delete you can do

targets: filename1 filename2

clean_targets:
    @$(foreach file, $(targets), test -f $(file) && rm -v $(file) || echo No $(file);)

The basically you loop over the filenames defined by the targets variable and check with 'test' if the target exists. If yes, delete the file, if not, report it is not there. The last check (reporting it is not there) is necessary because otherwise an error is raised in case there is no target at all

Eelco van Vliet
  • 1,198
  • 12
  • 18
0

We can also take advantages from GNU Coreutils's test builtin (manual), which means we can check file existence like this:

check_exist:
    test -f file && echo yes || echo no 

or more neatly, like this:

check_exist:
    [ -f file ] && echo yes || echo no 
Michael Lee
  • 197
  • 8