3

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

Or

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?

drs
  • 5,363
  • 9
  • 40
  • 69
maihabunash
  • 6,973
  • 19
  • 64
  • 80

3 Answers3

18

Just quote your path

basename  "/isf/GCM/VPfig/Aas/AR/ClCo el Doma Republic/VMN CRTro.txt"
rob
  • 352
  • 3
  • 12
  • 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. –  Sep 18 '17 at 10:58
11

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.

phemmer
  • 70,657
  • 19
  • 188
  • 223
  • 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 `-`). – Stéphane Chazelas Jul 29 '14 at 12:51
1

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:-/}"
done
echo now shift out...
shift $(($#-1))
printf 'arg#1:\t%s\n' "$1"

OUTPUT

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.

mikeserv
  • 57,448
  • 9
  • 113
  • 229
  • Note that the above doesn't work in `bash` (except in `sh` emulation) and you get an odd behaviour with `mksh` – Stéphane Chazelas Jul 29 '14 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 '14 at 14:06
  • It only worked because `set` is a _special_ builtin. For `mksh`, see http://thread.gmane.org/gmane.os.miros.mksh/424 – Stéphane Chazelas Jul 29 '14 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 '14 at 14:20
  • @StéphaneChazelas - about the [`bash` thing](http://austingroupbugs.net/view.php?id=352). Also, while I've got your ear, in a manner of speaking, any idea what happens after [interpretation approved?](http://austingroupbugs.net/view.php?id=654) - it's added to the next version maybe? – mikeserv Jul 29 '14 at 14:51