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,453
maihabunash
  • 7,131

3 Answers3

18

Just quote your path

basename  "/isf/GCM/VPfig/Aas/AR/ClCo el Doma Republic/VMN CRTro.txt"
rob
  • 375
  • 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
  • 71,831
  • 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
  • 58,310
  • 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. 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 '14 at 14:51