0

Is there any hope of using $(<D) here?
Any hope of avoiding the mystery dots?

$ cat Makefile
D=$(HOME)/Downloads
test: $D/DreamHost\ Web\ Panel\ _\ Mail\ _\ Message\ Filters.html
    : mv "$<" "$(<D)/old_$(<F)"
    : $(<)
    : $(<D)
    : $(<F)
    : $(dir $<)
    : $(notdir $<)
$ make test
: mv "/home/jidanni/Downloads/DreamHost Web Pan
  el _ Mail _ Message Filters.html" "/home/jidanni/Downloa
  ds . . . . . . ./old_DreamHost Web Panel _ Mail _ Messag
  e Filters.html"
: /home/jidanni/Downloads/DreamHost Web Panel _ Mail _ Message Filters.html
: /home/jidanni/Downloads . . . . . . .
: DreamHost Web Panel _ Mail _ Message Filters.html
: /home/jidanni/Downloads/ ./ ./ ./ ./ ./ ./ ./
: DreamHost Web Panel _ Mail _ Message Filters.html

(I folded the output for easy reading.)

Certainly the dots are a bug, as there is no spaces in $D, and $(<F) already contains the spaces part, unscathed too, I should say. GNU Make 4.4.1. Yes, I prefixed the mv(1) line with a colon just to print it instead, as it is not going to work anyway.

I suppose I'll have to fall back to basename(1) and dirname(1). Oh well.

Yes, we all know that make(1) has a hard time with spaces in file names.

Wait, adding more commands,

    echo "$$(dirname $<)" |od -c #has dots
    echo "$<"|od -c #no dots

Where does dirname(1) get its dots?
Oh, I see,

$ dirname a b c d e f
.
.
.
.
.
.

OK. So we still need to get in an do some more quoting to avoid it seeming like many filenames. (Dot is just the current directory. $PWD .)

2 Answers 2

1

If you look at the code of GNU make, except on Microsoft platforms

  • $(<D) is an alias for $(patsubst %/,%,$(dir $<))
  • $(<F) is an alias for $(notdir $<)

$(dir) and $(notdir) take one argument, which is taken as a space separated list of file paths and they respectively expand to a space separated list of their dirname and basename.

If you have a:

target: dir/A\ B dir2/C
    : $(dir $<)
    : $(notdir $<)

recipe, $< expands to dir/A B so it's the same as

target: dir/A\ B dir2/C
    : $(dir dir/A B)
    : $(notdir dir/A B)

So, $(dir dir/A B) expands to the dirname of dir/A (dir/) a space and the dirname of B (./), so dir/ ./ and similarly A B for notdir (not the basename of dir/A B, but the basename of dir/A, a space and the basename of B).

Even if you made it : $(dir dir/A\ B), that would still be the dirname of dir/A\, followed by the dirname of B, there is no handling of backslash in there.

In your case $(dir /home/jidanni/Downloads/DreamHost Web Panel _ Mail _ Message Filters.html) results in /home/jidanni/Downloads/ ./ ./ ./ ./ ./ ./ ./ which becomes /home/jidanni/Downloads . . . . . . . after $(patsubst %/,%,...) is applied to it for $(<D).

In any case, make ultimately runs commands not directly by passing them a list of arguments, but via a shell, to which it passes a single command line string.

It has no such thing as array variables; the way it achieves some form of list is by joining and splitting strings on spaces.

So it should become obvious that you can't really reliably deal with file paths containing spaces.

And as ultimately the contents of make variables are expanded into the shell code, any character that is special in the syntax of the shell would also be a problem.

With make you should only use very tame file names as is typically the case in the source distribution of software it is used to build.

Here, doing:

target:
    printf '<%s>\n' "$$(dirname -- '$(FILE)')" "$$(basename -- '$(FILE)')"

And using it in:

$ make target FILE='-dir-/a * b'
printf '<%s>\n' "$(dirname -- '-dir-/a * b')" "$(basename -- '-dir-/a * b')"
<-dir->
<a * b>

Would work. Pay close attention to the shell code that is interpreted and in particular what kind of quotes are used in there: '...' which in POSIX shells are the strong quotes inside which no character is special, and "..." which allows expansions including command substitutions and prevents split+glob on them, and of course the usual -- to avoid problems with arguments starting with -.

But even that does not work in:

$ make target FILE="dir/a'b"
printf '<%s>\n' "$(dirname -- 'dir/a'b')" "$(basename -- 'dir/a'b')"
<dir/ab)" "$(basename -- dir>

(the ' within $FILE ends up being taken as closing quote in the shell syntax. It would be worse with FILE="'\$(reboot)'" for instance).

Nor:

$ make target FILE=' dir/a b'
printf '<%s>\n' "$(dirname -- 'dir/a b')" "$(basename -- 'dir/a b')"
<dir>
<a b>

(leading spaces removed from the start of $FILE somehow).

Nor:

$ make target FILE=$'a\nb'
printf '<%s>\n' "$(dirname -- 'a
/bin/sh: 1: Syntax error: Unterminated quoted string
make: *** [Makefile:2: target] Error 2

That was broken into several shell invocations as you can confirm with:

$ make target FILE=$'a\nb' SHELL=echo
printf '<%s>\n' "$(dirname -- 'a
-c printf '<%s>\n' "$(dirname -- 'a
b')" "$(basename -- 'a
-c b')" "$(basename -- 'a
b')"
-c b')"

Nor:

$ make target FILE='$(SHELL)'
printf '<%s>\n' "$(dirname -- '/bin/sh')" "$(basename -- '/bin/sh')"
</bin>
<sh>

That particular case can be addressed by using $(value FILE) in place of $(FILE).

And of course that $(FILE) cannot be used in any sort of pseudo-list.

0

Try this in your makefile. It lets you get inside and quote $< which you can't do using $(<D). Sorry it relies on external dirname(1). Perhaps others here could use an internal make text function to do the same. Note it should also be able to deal with directories with spaces in the name, which fortunately we don't have here.

     mv -- '$<' "$$(dirname -- '$<')"/old_'$(<F)'
2
  • 1
    Should probably be mv -- '$<' "$$(dirname -- '$<')"/old_'$(<F)' and hope $< doesn't contain '. I would avoid make and use more modern alternatives if your file names are going to have unusual characters. Commented Nov 29, 2025 at 12:32
  • OK, I put your version there. Thanks. Commented Nov 30, 2025 at 7:10

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.