8

While putting together a script, I came across this command:

f=${file##*/}

I am curious to know, what does ## in this line mean?

Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
user1407199
  • 163
  • 1
  • 1
  • 9

1 Answers1

23

In bash, it removes a prefix pattern. Here, it's basically giving you everything after the last path separator /, by greedily removing the prefix */, any number of characters followed by /):

pax> fspec=/path/to/some/file.txt ; echo ${fspec##*/}
file.txt

Greedy in this context means matches as much as possible. There's also a non-greedy variant (matches the smallest possible sequence), and equivalents for suffixes:

pax> echo ${fspec#*/}    # non-greedy prefix removal
path/to/some/file.txt
pax> echo ${fspec%%/*}   # greedy suffix removal (no output)
pax> echo ${fspec%/*}    # non-greedy suffix removal
/path/to/some

The ##*/ and %/* are roughly equivalent to what you get from basename and dirname respectively, but within bash so you don't have to invoke an external program:

pax> basename ${fspec} ; dirname ${fspec}
file.txt
/path/to/some

For what it's worth, the way I remember the different effects of ##, %%, #, and %, is as follows. They are "removers" of various types.

Because # often comes before a number (as in #1), it removes stuff at the start. Similarly, % often comes after a number (50%) so it removes stuff at the end.

Then the only distinction is the greedy/non-greedy aspect. Having more of the character (## or %%) obviously means you're greedy, otherwise you'd share them :-)

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • 1
    And I believe the Korn shell had this notation before Bash did; it is also available in the Korn shell, at any rate. – Jonathan Leffler Apr 16 '13 at 06:57
  • Just be aware that `%%/*` isn't an exact replacement for dirname. If you feed dirname a bare file name it returns '.', while `%%/*` returns the name unchanged. (In a script that could lead to things like creating a directory with the file name instead of recognizing the current directory already exists.) – William Apr 16 '13 at 14:08
  • Is there a way to be greedy to a certain extent? For example let's say I have the following: `fspec=/path/to/some/file.txt ` and I want the following output: `some/file.txt` – A Merii Jul 04 '21 at 11:42
  • 1
    @AMerii: off the top of my head, I would say no with those operators. But I'm sure that, if you asked a question, it would get a few decent answers on other approaches. For example: `echo '/path/to/some/file.txt' | perl -pne 's?.*/([^/]*/[^/]*$)?\1?'` :-) – paxdiablo Jul 04 '21 at 12:23
  • Thanks for the suggestion, I will be creating a new question then :D – A Merii Jul 06 '21 at 08:07
  • This answer is bash-specific (fair, since the question is tagged that way), but note that these parameter expansion features are much more widely available (e.g. with `sh`), as they are part of the POSIX standard: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html – Patrick Sanan Nov 15 '21 at 09:51