J. Shin
J. Shin

Reputation: 79

How to delete all except a directory in Makefile?

The following command set is working on linux prompt.

%cd ${ADIR}/exe; shopt -s extglob; rm -rf !(BDIR)

But it is not working in Makefile

Linux command - works

%cd ${ADIR}/exe; shopt -s extglob; rm -rf !(BDIR)

Command in Makefile

        @cd ${ADIR}/exe; shopt -s extglob; rm -rf !\(BDIR\)

Make file message

rm: cannot remove `!(BDIR)': No such file or directory

Upvotes: 1

Views: 1045

Answers (2)

melpomene
melpomene

Reputation: 85837

The problem with your Makefile is that it escapes ( and ), which makes the shell interpret them literally.

The second issue,

/bin/sh: -c: line 0: syntax error near unexpected token `('

is caused by make using sh to execute commands, not bash.

The !(...) wildcard syntax (and extglob) are only supported by bash, not sh.

You could call bash explicitly:

        @bash -c 'cd ${ADIR}/exe; shopt -s extglob; rm -rf !(BDIR)'

But that doesn't work either, because extglob doesn't take effect until the next line of input has been read, so !( ) still throws a syntax error.

We need a way to run a multi-line command using a single invocation of the shell. Unfortunately make makes this unnecessarily complicated.

One possible solution:

SHELL = /bin/bash

...
        @bash -c $$'cd ${ADIR}/exe; shopt -s extglob\nrm -rf !(BDIR)'

This tells make to use bash to execute all recipes (not /bin/sh). We then run bash again manually, but using $'...' to quote the command string. This lets us write \n to embed a literal newline, which makes extglob / !( ... ) work.

We need double $$ to escape the $ for make, so $'...' becomes $$'...'.

I'm not very happy with this solution.

Upvotes: 1

MadScientist
MadScientist

Reputation: 100956

Unfortunately there's a weird behavior of bash in that the shopt setting won't take effect until the newline, so any globbing on the same line won't recognize it. Try this at your shell prompt:

$ shopt -s extglob; echo !(BDIR)
bash: !: event not found

Then try it in two lines:

$ shopt -s extglob
$ echo !(BDIR)
  ...works

Unfortunately this means it's almost impossible to use this with make.

You should use the POSIX-compatible version suggested in triplee's comment and avoid the need for special shells altogether.

Oh, it seems the answer was deleted. Anyway, do something like this instead:

foo:
        cd ${ADIR}/exe && for f in *; do \
            case $$f in (BDIR) : ok ;; (*) rm -rf "$$f" ;; esac; \
        done

Upvotes: 0

Related Questions