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.

Gilles 'SO- stop being evil'
  • 807,993
  • 194
  • 1,674
  • 2,175
Yulong Ao
  • 516
  • 2
  • 5
  • 12
  • 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
  • Here is a good Q/A on [Multiple Consecutive Path Separators](http://unix.stackexchange.com/q/1910/2343) – Peter.O Apr 23 '15 at 08:42
  • 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

38
dir=${1%/}

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

glenn jackman
  • 84,176
  • 15
  • 116
  • 168
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.

bergoid
  • 3
  • 2
Gilles 'SO- stop being evil'
  • 807,993
  • 194
  • 1,674
  • 2,175
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,577
  • 3
  • 15
  • 20