rsync, scp and trailing slashes

2020-07-05

In the OpenSSH 8.0 Release Notes, the developers recommended against the use of scp in favour of "more modern protocols". During my search for an alternative, I learned that rsync, for some reason, handles a trailing slash on the source directory differently than both scp and cp, which a lot of reddit users found annoying.

Since this is supposedly different on BSD and Linux, I tested how all three commands behave:

#!/bin/sh
cp -r source cp
cp -r source/ cp-slash

scp -r source scp
scp -r source/ scp-slash

rsync -r source rsync
rsync -r source/ rsync-slash

The source directory contains three files "a", "b" and "c", the target directories all exist and are empty.

Results

BSD

Tree View
    $ tree
    .
    ├── source
    │   ├── a
    │   ├── b
    │   └── c
    │
    ├── cp
    │   └── source
    │       ├── a
    │       ├── b
    │       └── c
    ├── cp-slash
    │   ├── a
    │   ├── b
    │   └── c
    ├── scp
    │   └── source
    │       ├── a
    │       ├── b
    │       └── c
    ├── scp-slash
    │   ├── a
    │   ├── b
    │   └── c
    ├── rsync
    │   └── source
    │       ├── a
    │       ├── b
    │       └── c
    └── rsync-slash
        ├── a
        ├── b
        └── c

So far, all three commands behave exactly the same: If no trailing slash is used, a new subdirectory "target/source" will be created, otherwise the content of the source directory is dumped right into into the target.
In other words: If no trailing slash is used, the directory itself is copied, otherwise its content is copied.

Linux

Tree View
    $ tree
    .
    ├── source
    │   ├── a
    │   ├── b
    │   └── c
    │
    ├── cp
    │   └── source
    │       ├── a
    │       ├── b
    │       └── c
    ├── cp-slash
    │   └── source
    │       ├── a
    │       ├── b
    │       └── c
    ├── scp
    │   └── source
    │       ├── a
    │       ├── b
    │       └── c
    ├── scp-slash
    │   └── source
    │       ├── a
    │       ├── b
    │       └── c
    ├── rsync
    │   └── source
    │       ├── a
    │       ├── b
    │       └── c
    └── rsync-slash
        ├── a
        ├── b
        └── c

On Linux, cp and scp ignore the slash, they always create a new subdirectory. However, rsync behaves like it does on BSD. This is especially problematic if combined with shell autocompletion, which will usually add a trailing slash to directories. When this happens unexpectedly, it will definitely lead to a bunch of garbage being dumped into a folder sooner or later.

Exceptions

These patterns are nice and all, but as always, there are exceptions. At least those are the same on both BSD and Linux:

Trailing Slash on the target

Fortunately, a trailing slash on the target directory does not appear to make a difference, both if the target exists and if it doesn't.

Summary

rsync usually acts like cp and scp do on BSD systems, unless the target directory does not exist and no trailing slash was appended to the source path. It is more consistent on BSD than on Linux, but still doesn't fit in completely.