93

I have a directory containing several files, some of which have spaces in their names:

Test workspace/
Another directory/
file1.ext
file2.ext
demo 2012-03-23.odp

I use GNU's $(wildcard) command on this directory, and then iterate over the result using $(foreach), printing everything out. Here's the code:

FOO := $(wildcard *)
$(info FOO = $(FOO))
$(foreach PLACE,$(FOO),$(info PLACE = $(PLACE)))

Here's what I would expect to see printed out:

Test workspace
Another directory
file1.ext
file2.ext
demo 2012-03-23.odp

Here's what I would actually get:

Test
workspace
Another
directory
file1.ext
file2.ext
demo
2012-03-23.odp

The latter is obviously of no use to me. The documentation for $(wildcard) flat-out states that it returns a "space-separated list of names" but completely fails to acknowledge the huge problems this raises. Nor does the documentation for $(foreach).

Is it possible to work around this? If so, how? Renaming every file and directory to remove the spaces is not an option.

qntm
  • 4,147
  • 4
  • 27
  • 41

5 Answers5

73

The bug #712 suggests that make does not handle names with spaces. Nowhere, never.

I found a blog post saying it's partially implemented by escaping the spaces with \ (\\ seems to be typo or formatting artefact), but:

  • It does not work in any functions except $(wildcard).
  • It does not work when expanding lists of names from variables, which includes the special variables $?, $^ and $+ as well as any user-defined variable. Which in turn means that while $(wildcard) will match correct files, you won't be able to interpret the result anyway.

So with explicit or very simple pattern rules you can get it to work, but beyond that you are out of luck. You'll have to look for some other build system that does support spaces. I am not sure whether jam/bjam does, scons, waf, ant, nant and msbuild all should work.

Jan Hudec
  • 73,652
  • 13
  • 125
  • 172
  • I was able to get `$?` and `$@`. If you are interested, see my answer below. and Louis, is wrong, it does work. Feel free to test it out yourself – Mr_Moneybags Oct 23 '15 at 22:23
28

GNU Make does very poorly with space-separated filenames.

Spaces are used as delimiters in word list all over the place.

This blog post summarizes the situation well, but WARNING: it incorrectly uses \\ rather than \

target: some\ file some\ other\ file

some\ file some\ other\ file:
    echo done

You can also use variables, so this would also work

VAR := some\ file some\ other\ file

target: $(VAR)

$(VAR):
    echo done

Only the wildcard function recognizes the escaping, so you can't do anything fancy without lots of pain.


But don't forget that your shell uses spaces as delimiters too.

If I wanted to change the echo done to touch $@, I'd have to add slash to escape it for my shell.

VAR := some\ file

target: $(VAR)

$(VAR):
    touch $(subst \,\\,$@)

or, more likely, use quotes

VAR := some\ file some\ other\ file

target: $(VAR)

$(VAR):
    touch '$@'

In the end, if you want to avoid a lot of pain, both in GNU make, and in your shell, don't put spaces in your filenames. If you do, hopefully the limited capabilities of Make will be sufficient.

Paul Draper
  • 78,542
  • 46
  • 206
  • 285
  • 9
    "don't put spaces in your filenames": I never do. But sometimes you have to write a make file that works with someone else's source code .... – Mars Jan 08 '16 at 19:18
  • @Mars, I understand. Just be prepared for the pain. – Paul Draper Jan 08 '16 at 19:20
  • No kidding, Paul. I think I spent a couple of hours trying different things. I was trying to test for a version by the existence of a file. I gave up. Everyone has to use the same version now. (Which isn't that hard in my case, but it's an inelegant solution.) – Mars Jan 08 '16 at 21:46
  • You *can* still use `$(shell ...)` to do initial manipulations, luckily. – o11c Jun 28 '16 at 05:51
  • 3
    I am using WSL so I have all the linux tools on Windows.... and all my binaries are in "Program Files". Daaaang. – Brian Bulkowski Aug 04 '18 at 22:45
15

This method will also allow use of listed file names such as $? and user variables that are lists of files.

The best way to deal with spaces in Make is to substitute spaces for other characters.

s+ = $(subst \ ,+,$1)

+s = $(subst +,\ ,$1)

$(call s+,foo bar): $(call s+,bar baz) $(call s+,bar\ baz2)
    # Will also shows list of dependencies with spaces.  
    @echo Making $(call +s,$@) from $(call +s,$?)

$(call s+,bar\ baz):

    @echo Making $(call +s,$@)

$(call s+,bar\ baz2):

    @echo Making $(call +s,$@)

Outputs

Making bar baz
Making bar baz2
Making foo bar from bar baz bar baz2

You can then safely manipulate lists of file names using all the GNU Make functions. Just be sure to remove the +'s before using these names in a rule.

SRCS := a\ b.c c\ d.c e\ f.c

SRCS := $(call s+,$(SRCS))

# Can manipulate list with substituted spaces
OBJS := $(SRCS:.c=.o)

# Rule that has object files as dependencies.
exampleRule:$(call +s,$(OBJS))
    # You can now use the list of OBJS (spaces are converted back).
    @echo Object files: $(call +s,$(OBJS))

a\ b.o:
    # a b.o rule commands go here...
    @echo in rule: a b.o

c\ d.o:

e\ f.o:

Outputs

in rule: a b.o
Object files: a b.o c d.o e f.o

This info is all from the blog that everyone else was posting.

Most people seem to be recommending using no spaces in paths or using Windows 8.3 paths, but if you must use spaces, escaping spaces and substitution works.

Mr_Moneybags
  • 3,927
  • 3
  • 19
  • 15
7

If you are willing to rely on your shell a bit more, this gives a list which can hold names with spaces just fine:

$(shell find | sed 's: :\\ :g')
Scott Weldon
  • 9,673
  • 6
  • 48
  • 67
benzaita
  • 12,869
  • 3
  • 20
  • 22
4

The original question said that "renaming is not an option", yet many commenters have pointed out that renaming is pretty much the only way Make can handle spaces. I suggest a middle way: Use Make to temporarily rename the files and then rename them back. This gives you all the power of Make with implicit rules and other goodness, but doesn't mess up your file naming scheme.

# Make cannot handle spaces in filenames, so temporarily rename them
nospaces:
    rename -v 's/ /%20/g' *\ *
# After Make is done, rename files back to having spaces
yesspaces:
    rename -v 's/%20/ /g' *%20*

You could call these targets by hand with make nospaces and make yesspaces, or you can have other targets depends on them. For example, you might want to have a "push" target which makes sure to put the spaces back in filenames before syncing files back with a server:

# Put spaces back in filenames before uploading
push: yesspaces
    git push

[Sidenote: I tried the answer which suggested using +s and s+ but it made my Makefile harder to read and debug. I gave up on it when it gave me guff over implicit rules likes: %.wav : %.ogg ; oggdec "$<".]

hackerb9
  • 1,545
  • 13
  • 14