8

It seems the basename function as interpreted by GNU make is not the same as bash's basename. The former strips the suffix, while the latter will also strip the path. How can I get the base name of a folder in my makefile?

Also, why in did they change it? (It took me 20 minutes to find the source of my error)

PeeHaa
  • 71,436
  • 58
  • 190
  • 262
ari
  • 4,269
  • 5
  • 24
  • 33

4 Answers4

4

I guess the basename(1) command has two orthogonal functionalities -- stripping suffices, stripping leading directory portions -- and the GNU make authors wanted to be able to invoke each functionality separately. And of course only one concept could get the name basename.

Certainly in a makefile it's useful to be able to convert foo/bar/baz.c to foo/bar/baz so that you can tack a new suffix on the end to construct a related filename in the same directory as a source file.

The comments on @ire_and_curses answer note that $(notdir $(CURDIR)) doesn't suffice for your purposes, as (being a directory) CURDIR may have been specified as

CURDIR = /foo/bar/

and the notdir strips the whole thing due to the trailing slash. To allow for this way of writing a directory path, you need to strip a trailing slash explicitly with e.g. $(notdir $(CURDIR:%/=%)).

John Marshall
  • 6,815
  • 1
  • 28
  • 38
  • Is there any way to amend this to take into account the fact that there may accidentally be *two* slashes at the end of a path? There is an `$(abspath ...)` function that would fix the path, but it cannot be used with the `:%/=%`. – Matt Feb 19 '15 at 01:44
  • 1
    `:%/=%` is just a shorthand for `patsubst`, so you could write `$(patsubst %/,%,$(abspath $(BOB)))`. However as `abspath` takes care of the trailing slashes itself, you won't need the `patsubst` there anyway. – John Marshall Feb 19 '15 at 08:57
3

Yeah, it's weird. You can get the behaviour you want by chaining notdir and basename:

$(notdir names...)
    Extracts all but the directory-part of each file name in names... For example,    
              $(notdir src/foo.c hacks)

    produces the result ‘foo.c hacks’. 

...

$(basename names...)
    Extracts all but the suffix of each file name in names. If the file name
    contains a period, the basename is everything starting up to (and not
    including) the last period... For example,

              $(basename src/foo.c src-1.0/bar hacks)

    produces the result ‘src/foo src-1.0/bar hacks’. 

So, for example, you could convert /home/ari/src/helloworld.c to helloworld.html by chaining functions like this:

SRC=/home/ari/src/helloworld.c
TARGET=$(addsuffix .html, $(notdir $(basename $(SRC))))
ire_and_curses
  • 68,372
  • 23
  • 116
  • 141
  • Ya, I thought of that--problem is, if your name is a directory (which it is in my case) , notdir strips the whole thing. So `$(notdir $(CURDIR))` yields a null value (or maybe a space). – ari Sep 14 '12 at 06:22
  • @ari - Hmm. Tricky. How about cheating, and using [shell](http://www.gnu.org/software/make/manual/html_node/Shell-Function.html#Shell-Function) to call the shell's `basename()`? – ire_and_curses Sep 14 '12 at 06:42
  • Ya, I may have to do that, but it's so aggravating to have to resort to a shell call, especially when they have a basename function that has no business operating in any way other than the shell's basename. It's just that since there's more than one way to do it in bash (eg `basename pwd`, `${PWD##*/}`), I figured there must be at _least_ one way to do it without resorting to a shell call. – ari Sep 14 '12 at 07:02
  • 2
    @ari: `notdir` is a text-based function so it doesn't know whether the name you give it refers to a directory or not. If however you present it with something like _/path/of/dir/_ then of course it will strip everything up to and including that final slash, as is documented. You want `$(notdir $(CURDIR:%/=%))` if you want to allow sloppy specification of directories with a trailing slash. – John Marshall Sep 14 '12 at 11:19
  • @john, if you write that as a separate answer I will accept it. – ari Aug 26 '13 at 01:46
2

You can still use bash's version though:

SHELL := /bin/bash
basename := $(shell basename /why/in/gods/name)
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
2

How about $(dir /path/to/file.txt)?

https://www.gnu.org/software/make/manual/html_node/File-Name-Functions.html

chacham15
  • 13,719
  • 26
  • 104
  • 207