10

How do I use the GNU C Library version of basename() and dirname()?.

If you

#include <libgen.h>

for dirname You're already getting the POSIX, not the GNU, version of basename(). (Even if you

#define _GNU_SOURCE

As far as I know there is no conditional importing in C. Is there a gcc specific trick?

PeeHaa
  • 71,436
  • 58
  • 190
  • 262
Roman A. Taycher
  • 18,619
  • 19
  • 86
  • 141
  • `basename with dirname` means nothing to me – sehe Apr 27 '11 at 09:55
  • You don't want to combine GNU basename and POSIX dirname. There exists no GNU-specific version of dirname, so calling POSIX dirname and GNU basename on a path such as "/home/user/" (with a trailing slash) will give you a dirname of "/home" and an empty string for the basename, completely dropping the "/user" component of the path. For this reason, I think it's best to avoid the GNU version of basename altogether. – Adrian Lopez Aug 26 '22 at 16:48
  • FYI: You can find the source code for GLibC impls here: `dirname()`: https://sourceware.org/git/?p=glibc.git;a=blob;f=misc/dirname.c;hb=HEAD , `__xpg_basename()`: https://sourceware.org/git/?p=glibc.git;a=blob;f=stdlib/xpg_basename.c;hb=HEAD , `__basename()`: https://sourceware.org/git/?p=glibc.git;a=blob;f=string/basename.c;hb=HEAD , `libgen.h`: https://sourceware.org/git/?p=glibc.git;a=blob;f=misc/libgen.h;hb=HEAD – kevinarpe Nov 20 '22 at 15:05

5 Answers5

10

Just write it yourself and give it a different name than basename. This GNU insistence on creating alternate non-conforming versions of standard functions that can be written in 1-3 lines is completely batty.

char *gnu_basename(char *path)
{
    char *base = strrchr(path, '/');
    return base ? base+1 : path;
}

This way, your program will also be more portable.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • 4
    And your trivial implementation will have problems with "/" and "/home/mine/". – Jonathan Leffler Apr 27 '11 at 13:33
  • 7
    That is the behavior specified for the GNU version of the function! See http://www.kernel.org/doc/man-pages/online/pages/man3/basename.3.html "The GNU version never modifies its argument, and returns the empty string when path has a trailing slash, and in particular also when it is "/"." If you don't want the broken GNU behavior, don't ask for it... – R.. GitHub STOP HELPING ICE Apr 27 '11 at 13:34
  • 1
    OK - there had better be a good reason for the GNU to go off on its own tangent. I'll make a note of that peculiarity. I can sympathize with 'not modifying the argument'. I have my own prefixed version that gets a sized buffer to copy the basename into, and it has a `const char *` argument for the input. – Jonathan Leffler Apr 27 '11 at 13:40
  • 2
    For functions like this, it really just makes the most sense to write your own with the exact semantics you expect. – R.. GitHub STOP HELPING ICE Apr 27 '11 at 13:46
  • 1
    @Jonathan Leffler Forgive me if I've misunderstood, but I think Roman is asking how to ensure the GNU version of these functions are used. I'm having a similar issue -- in that path is being modified even though libgen.h is not included. As R.. mentioned, it's simple to write your own, however I don't understand why I would do this if the GNU versions do what I need? – J. Andrew Laughlin Jun 17 '11 at 14:36
  • @J.AndrewLaughlin In cases like this you write your own one because if the source changes, the compiler may include the wrong _basename()_ without giving you any notice, breaking the code that worked and did not change. – 18446744073709551615 Jul 23 '14 at 09:01
  • As I understand, there is some complex history behind `basename()`. I think most first-time users of the C function `basename()` expect it to behave similar to GNU coreutils command tool `/usr/bin/basename`. Even then, the treatment of leading `//` is a bit weird! – kevinarpe Nov 20 '22 at 15:12
4

According to the man page you should do

#define _GNU_SOURCE
#include <string.h>

If you get the POSIX version, libgen.h is probably already included before that point. You may want to include -D_GNU_SOURCE in the CPPFLAGS for compilation:

gcc -D_GNU_SOURCE ....

Compare: POSIX Version vs GNU Version on Compiler Explorer.

sehe
  • 374,641
  • 47
  • 450
  • 633
  • simplified more specific answer – sehe Apr 27 '11 at 10:01
  • 3 lines gives me __xpg_basename@@GLIBC_2.0, no libgen gives me basename@@GLIBC_2.0 (courtesy of nm). not defining GNU_SO URCE gives me basename@@GLIBC_2.0 but warns of initialization makes pointer from integer without a cast – Roman A. Taycher Apr 27 '11 at 10:23
  • I think -D_GNU_SOURCE just does the same as defining _GNU_SOURCE and it warns if I do both. – Roman A. Taycher Apr 27 '11 at 10:28
  • 1
    @Roman: -D_GNU_SOURCE does the same _BUT_ at a different time. This is exactly the difference I'm talking about. The pointer conversion warning, is just the pointer conversion warning. You can fix that by provining the expected argument type. – sehe Apr 27 '11 at 10:36
  • This doesn't work. I've tested exactly this code (trying both #define _GNU_SOURCE and -D_GNU_SOURCE) and I get only the POSIX version. – Adrian Lopez Aug 25 '22 at 19:40
  • @AdrianLopez then you're likely doing something wrong: https://godbolt.org/z/44GW63Wb9 vs https://godbolt.org/z/TW9baTbqe – sehe Aug 25 '22 at 20:40
  • Your examples don't match the code you posted in your answer. Including both and like in your answer will cause the POSIX version of basename() to be declared whether you define _GNU_SOURCE or not (see links to examples below). Also, your examples' output saying basename("/") "should be empty on POSIX" is incorrect. It's the other way around: POSIX basename should return a single slash and GNU basename should return an empty string. https://godbolt.org/z/9d7oefT15 https://godbolt.org/z/od5eYM7T9 https://godbolt.org/z/1x7YKnGar – Adrian Lopez Aug 25 '22 at 22:34
  • 1
    @AdrianLopez you're quite right. Fixed both. – sehe Aug 27 '22 at 10:40
2

After examining libgen.h, I'm pretty sure I have a warning-free and error-free solution:

/* my C program */
#define _GNU_SOURCE     /*  for GNU version of basename(3) */
#include <libgen.h>     /*  for dirname(3) */
#undef basename         /*  (snide comment about libgen.h removed) */
#include <string.h>     /*  for basename(3) (GNU version) and strcmp(3) */

/* rest of C program... */

With the #undef line, now my program includes dirname(3) from libgen.h and the GNU version of basename(3) from string.h.

No compiler warnings/errors from either gcc (version 4.5.2) or clang (version 3.3).

pr1268
  • 1,176
  • 3
  • 9
  • 16
  • 1
    This is the correct answer, but it should come with a word of warning: there exists no GNU-specific version of dirname, so calling dirname and GNU basename on a path such as "/home/" (with a trailing slash) will give you a dirname of "/" and an empty string for the basename, completely dropping the "home" component of the path. For this reason, I think the best advice would be to avoid the GNU version of basename altogether. – Adrian Lopez Aug 25 '22 at 21:49
0

It's crazy basename and dirname have two versions.

We worked at a big project, it looks like these two apis already caused potentially bugs. So we marked "basename" "dirname" as deprecated for warning if someone use it:

#ifdef basename
__attribute__ ((deprecated))
char *__xpg_basename(char *path);
#else
__attribute__ ((deprecated))
char *basename(const char *path);
#endif

 __attribute__ ((deprecated))
char *dirname(char *path);

We also try to introduce a base c foundation library such as glib or libcork, but it looks like too heavy. So we write a tiny library for this purpose, it implementation like this:

#include <libgen.h>       // for dirname
#include <linux/limits.h> // for PATH_MAX
#include <stdio.h>        // for snprintf
#include <string.h>       // for basename
#include <stdbool.h>      // for bool

bool get_basename(const char *path, char *name, size_t name_size) {
  char path_copy[PATH_MAX] = {'\0'};
  strncpy(path_copy, path, sizeof(path_copy) - 1);
  return snprintf(name, name_size, "%s", basename(path_copy)) < name_size;
}

bool get_dirname(const char *path, char *name, size_t name_size) {
  char path_copy[PATH_MAX] = {'\0'};
  strncpy(path_copy, path, sizeof(path_copy) - 1);
  return snprintf(name, name_size, "%s", dirname(path_copy)) < name_size;
}

Then we replace all basename dirname call with get_basename get_dirname.

tangxinfa
  • 1,410
  • 13
  • 12
0

Make sure you're building with the GNU C library, rather than your system's (presumed) POSIX-compatible default.

This is often set in the GCC spec file. Use the -v option to show the current settings:

$ gcc -v
Using built-in specs.
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu/Linaro 4.4.4-14ubuntu5' --with-bugurl=file:///usr/share/doc/gcc-4.4/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.4 --enable-shared --enable-multiarch --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.4 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-objc-gc --disable-werror --with-arch-32=i686 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.4.5 (Ubuntu/Linaro 4.4.4-14ubuntu5)
unwind
  • 391,730
  • 64
  • 469
  • 606