25

I have a script which requires an directory as one argument. I want to support the two form: one is like

a/b/c

(no slash at the end) and another is like

 a/b/c/

(has slash at the end).

My question: given either of the two form, how can I just keep the first form unchanged and strip the last slash of the second form to convert it to the first form.

Yulong Ao
  • 516
  • 3
    Not that it should be much of a problem - a double slash anywhere except at the start is equivalent to a single slash, so don't worry about joining them using /. – muru Apr 23 '15 at 01:40
  • @muru I did not know this before :). – Yulong Ao Apr 23 '15 at 01:46
  • 2
    @muru A slash at the end can make a difference, such as the difference between acting on a symlink and acting on the directory it points to, or the source argument of rsync. – Gilles 'SO- stop being evil' Apr 24 '15 at 04:55
  • @Gilles indeed, but as you can see, I'm talking of joining paths. – muru Apr 24 '15 at 04:56
  • You'll run into trouble parsing paths as strings if you're not careful - consider a/////b/c or a/../a/.././//a/b/c////. The best solution is usually cd - { cd -- "$1" && cd - || exit; } >/dev/null" which will handle all of that correctly, automatically print a properly formatted diag for error and put a full path in $OLDPWD. Use cd -P to automatically canonicalize symlinks in your argument to absolute paths. And strip $PWD from the head of $OLDPWD for relative paths like dir=${OLDPWD#"$PWD"/}; [ -n "${dir##/*}" ] || ! echo not relative\! – mikeserv Apr 24 '15 at 05:28

3 Answers3

39
dir=${1%/}

will take the script's first parameter and remove a trailing slash if there is one.

glenn jackman
  • 85,964
17

To remove a trailing slash if there is one, you can use the suffix removal parameter expansion construct present in all POSIX-style shells:

x=${x%/}

There are a few complications. This only removes a single slash, so if you started with a/b/c// then you'll still end up with a slash. Furthermore, if the original path was /, you need to keep the slash. Here's a more complex solution that takes care of these cases:

case $x in
  *[!/]*/) x=${x%"${x##*[!/]}"};;
  *[/]) x="/";;
esac

Alternatively, in ksh, or in bash after shopt -s extglob:

[[ x = *[!/] ]] || x=${x%%*(/)}

Note that in many cases, it doesn't matter that there is a trailing slash. It does matter if the argument is a symbolic link to a directory: with a trailing slash, the argument designates the directory, whereas with no trailing slash, the argument designates the symbolic link itself. It also matters with a few other programs, for example the source argument of rsync is treated differently depending on the presence of a trailing slash.

5

realpath resolves given path. Among other things it also removes trailing slashes. Use -s to prevent following simlinks

DIR=/tmp/a///
echo $(realpath -s $DIR)
# output: /tmp/a
czerny
  • 1,657