Here is an example of file path:

/isf/GCM/VPfig/Aas/AR/ClCo el Doma Republic/VMN CRTro.txt

What I want to get is the file basename:

VMN CRTro.txt

So I try the following:

echo /isf/GCM/VPfig/Aas/AR/ClCo el Doma Republic/VMN CRTro.txt | sed s'/\// /g' | awk '{print $NF}'
CRTro.txt     <-- not as expected


basename  /isf/GCM/VPfig/Aas/AR/ClCo el Doma Republic/VMN CRTro.txt
basename: extra operand `Doma'
Try `basename --help' for more information.     <-- basename cant handle spaces 

What the best way to get the basename of a file with spaces in it?

  • 2
    what about basename "/isf/GCM/VPfig/Aas/AR/ClCo el Doma Republic/VMN CRTro.txt"
    – rob
    Jul 29, 2014 at 12:30
  • 2
    yes its so simple , I am really stupid!!!!!!!!!!!!!!! Jul 29, 2014 at 12:32

3 Answers 3


Just quote your path

basename  "/isf/GCM/VPfig/Aas/AR/ClCo el Doma Republic/VMN CRTro.txt"
  • 2
    Surely single quotes would be safer here than double quotes. Otherwise, if the filename includes anything that the shell can expand, the shell will perform that expansion, which will not be the desired result.
    – user6860
    Sep 18, 2017 at 10:58

The core of the issue is quoting. Without quotes, it's treating the file name as multiple arguments to basename.
If the path is hard set, and not a variable, then rob's answer is good. But if this is part of a script, where a variable is being used, you have 2 good solutions:

$ filepath="/isf/GCM/VPfig/Aas/AR/ClCo el Doma Republic/VMN CRTro.txt"
$ basename "$filepath"
VMN CRTro.txt

However basename is an external utility, and not part of bash. There is an alternative solution built into bash:

$ filepath="/isf/GCM/VPfig/Aas/AR/ClCo el Doma Republic/VMN CRTro.txt"
$ echo "${filepath##*/}"
VMN CRTro.txt

The ${filepath##*/} tells bash to perform the glob */, which matches as many characters as possible followed by a /, and then strip it out.

  • 6
    The usual warning about basename vs ${var##*/}: it doesn't work properly for path/to/dir/ or /. You'd want basename -- "$filepath" (for paths that start with -). Jul 29, 2014 at 12:51

Both the answers here are more than sufficient - though I would personally do as Patrick suggests and use ${var##*/}. Still, just for fun:

IFS=/ ; set -f
set -- ${0+/isf/GCM/VPfig/Aas/AR/ClCo el Doma Republic/VMN CRTro.txt}
for p do i=$((i+1))
    printf "arg#$i:\t%s\n" "${p:-/}"
echo now shift out...
shift $(($#-1))
printf 'arg#1:\t%s\n' "$1"


arg#1:  /
arg#2:  isf
arg#3:  GCM
arg#4:  VPfig
arg#5:  Aas
arg#6:  AR
arg#7:  ClCo el Doma Republic
arg#8:  VMN CRTro.txt
now shift out...
arg#1:  VMN CRTro.txt

You don't have to worry about $IFS eating up your variable evaluations if you set it properly. You can even use it to your advantage.

  • Note that the above doesn't work in bash (except in sh emulation) and you get an odd behaviour with mksh Jul 29, 2014 at 13:53
  • @StéphaneChazelas - I fixed it for bash - I guess it doesn't like variable declarations against builtins. Or maybe it doesn't persist them. Anyway, just added a semicolon. I don't have mksh though - I should probably install it.
    – mikeserv
    Jul 29, 2014 at 14:06
  • It only worked because set is a special builtin. For mksh, see thread.gmane.org/gmane.os.miros.mksh/424 Jul 29, 2014 at 14:17
  • @StephaneChezales - I just installed it and I see what you mean - you get 7 arguments because the lone / is completely evaluated away. So $1 becomes isf - weird. Anyway, it does work, but it bothers me. 16 minutes ago huh? As always, thank you.
    – mikeserv
    Jul 29, 2014 at 14:20
  • @StéphaneChazelas - about the bash thing. Also, while I've got your ear, in a manner of speaking, any idea what happens after interpretation approved? - it's added to the next version maybe?
    – mikeserv
    Jul 29, 2014 at 14:51

Not the answer you're looking for? Browse other questions tagged or ask your own question.