0

A shell variable holds a path. How does one get its filename portion?

In bash(1), I experimented and found that I can do it with ."${i/*\///}" where i is the name of the environment variable. Such a method is not only ugly but also erroneous in the case where the path does not contain any / character.

I'll demonstrate a practical case where such a function is needed. Let's say we want to make a symbolic link for every PDF files in a source directory to the current directory.

$ for i in /source/path/*.pdf; do\
  ln -s "$i" ."${i/*\///}"; \
  done
1
  • 2
    For the example, if you didn't need the dot, just ln -s "$i" automatically uses the basename of the target as the link name. Also a nit: that's a shell variable (or shell parameter) but not an environment variable because you didn't export it. Within the shell they both work, but if you want to pass information to another program this difference matters. Sep 24, 2018 at 5:05

2 Answers 2

5

${i##*/}

This works in Posix shells, including bash, dash, ksh, zsh, etc. From the standard POSIX Parameter Expansion section of the Posix Shell & Utilities specification:

${parameter##[word]}
Remove Largest Prefix Pattern. The word shall be expanded to produce a pattern. The parameter expansion shall then result in parameter, with the largest portion of the prefix matched by the pattern deleted.

Alternatively, and traditionally, the basename command has been used for this purpose. Caution: Performance can become a significant issue because basename is implemented as an external command (for example, /usr/bin/basename). Because you're performing this inside a loop, you will call an external command for each file. On a list of 1000 files, this may be the difference between 0.05 seconds (parameter expansion) and 2.0 seconds (basename command). But for a list of 10,000 files, it could be the difference between 0.5 seconds (expansion) vs 20 seconds (basename). The difference in performance becomes more extreme as the number of files increases.

For readability as well as performance, you can implement your own basename function, for example:

mybasename() { echo "${1##*/}"; }

(The selection of a better name for the function, and/or the implementation of the full basename command line interface, are left as an exercise for the reader. :)

2
0

You can put the path in a variable and use its length-count to get only the desired substring from the full path name, like this:

$ pth="/source/path/";
$ for i in $path*.pdf; do \
  ln -s "$i" ./"${i:${#pth}}"; \
  done

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .