296

I'm trying to write a post-commit hook for SVN, which is hosted on our development server. My goal is to try to automatically checkout a copy of the committed project to the directory where it is hosted on the server. However I need to be able to read only the last directory in the directory string passed to the script in order to checkout to the same sub-directory where our projects are hosted.

For example if I make an SVN commit to the project "example", my script gets "/usr/local/svn/repos/example" as its first argument. I need to get just "example" off the end of the string and then concat it with another string so I can checkout to "/server/root/example" and see the changes live immediately.

jww
  • 90,984
  • 81
  • 374
  • 818
TJ L
  • 23,034
  • 7
  • 58
  • 76

4 Answers4

431

basename does remove the directory prefix of a path:

$ basename /usr/local/svn/repos/example
example
$ echo "/server/root/$(basename /usr/local/svn/repos/example)"
/server/root/example
sth
  • 211,504
  • 50
  • 270
  • 362
  • 2
    basename is definitely what I'm looking for. How can get the basename of an argument stored into a variable though? E.g. `SUBDIR="/path/to/whatever/$(basename $1)"` – TJ L Jul 20 '10 at 20:38
  • 5
    @tj111: sounds like is no `$1`, or `$1` is empty – sth Jul 20 '10 at 20:59
  • 1
    unfortunately, if you wrap commands, basename is not a good idea. just something to keep in mind – dtc Jul 20 '16 at 20:36
128

The following approach can be used to get any path of a pathname:

some_path=a/b/c
echo $(basename $some_path)
echo $(basename $(dirname $some_path))
echo $(basename $(dirname $(dirname $some_path)))

Output:

c
b
a
Captain Man
  • 6,270
  • 4
  • 44
  • 68
Jingguo Yao
  • 6,428
  • 6
  • 44
  • 57
  • 3
    does not work with paths that have spaces... you can overcome that with quotes... which somehow works `echo "$(basename "$(dirname "$pathname")")"` – Ray Foss Aug 15 '19 at 14:52
83

Bash can get the last part of a path without having to call the external basename:

subdir="/path/to/whatever/${1##*/}"
Dennis Williamson
  • 324,833
  • 88
  • 366
  • 429
  • 3
    On my Mac, using substring notation is more than order of magnitude faster than dirname / basename for the case where you're doing something trivial to each of a few thousand files. – George Jun 26 '14 at 01:24
  • This didn't work at all when my "/path/to/whatever/" was a variable. – Buttle Butkus May 04 '15 at 21:28
  • @ButtleButkus: Show me what you did and what you expected as the result, because it will work. – Dennis Williamson May 05 '15 at 00:31
  • 1
    `d=/home/me/somefolder;subdir="/$d/${1##*/}"` I ended up with something like `//home/me/somefolder//` the $d actually comes from a loop `for d in $(find $SOMEFOLDER -maxdepth 1 -type d);` Using `subdir=$(basename $d)` works as expected. – Buttle Butkus May 05 '15 at 02:02
  • 1
    @ButtleButkus: You should use `while` instead of `for` to iterate over the output of `find` (`find -print0 | xargs -0` is better) or use globbing: `for d in $SOMEFOLDER/*/` (the final slash works like `-type d` - you can use `**` in Bash 4 for recursion if you `shopt -s globstar`, but an "Argument list too long" message is possible). Note that the `${1}` portion of the command represents the first argument of a script or function. You may need to use `${d##*/}` or some other variable or argument specification or make sure that an argument is being passed in `$1` – Dennis Williamson May 05 '15 at 02:55
  • Is there easy way to account for input path ending in `/`? – Piotr Dobrogost Oct 08 '15 at 09:04
  • @PiotrDobrogost: Strip off the final `/` first (this has no effect if there's no trailing `/`). It has to be done in two steps: `subdir="${1%/}"; subdir="/path/to/whatever/${1##*/}"` – Dennis Williamson Oct 08 '15 at 17:18
  • 6
    @DennisWilliamson **Thanks** a lot for **sharing**. _For any future readers who start to wonder **why it is not working** or I am the only stupid out here_ . Above answer assumes that `$1` contains `the path from which last component is to be taken out`. I missed that part. My use case: `target_path='/home/user/dir1/dir2/dir3/'; target_path="${target_path%/}"; last_component=${target_path##*/}; echo $last_component` - Works – Vinay Vissh Apr 13 '18 at 11:10
  • 2
    See here for an explanation of *how* and *why* `${1##*/}` works: https://unix.stackexchange.com/a/171786/15070 – Matt Apr 15 '18 at 10:50
  • The given answer seems a little misleading. Would be better to have a little more context. – Cognitiaclaeves Sep 10 '20 at 19:03
1

To print the file name without using external commands,

Run:

fileNameWithFullPath="${fileNameWithFullPath%/}";
echo "${fileNameWithFullPath##*/}" # print the file name

This command must run faster than basename and dirname.

Mostafa Wael
  • 1,098
  • 1
  • 7
  • 14