Hacker Timesnew | past | comments | ask | show | jobs | submitlogin

I end up reposting this comment whenever rsync's slash treatment gets mentioned.

rsync has "weird" syntax for a reason. Unlike other unix-like commands, it treats trailing slashes as significant AND consistently. If a directory has a trailing slash, it means "contents" of the directory". No slash means "the directory itself". These are two different concepts, and a program that copies directories should take the difference into account. scp (and cp, for that matter) don't take this difference into account. That leads to gotchas with recursive (-r) copying. Most importantly, scp isn't idemopotent:

scp -r fromdir todir

If todir doesn't exist, scp will copy the contents of directory fromdir to a new directory named todir.

Execute the same command again (now that todir exists), and scp will copy fromdir to todir/fromdir .

On the other hand:

rsync -a fromdir/ todir

will always copy the contents of fromdir into a directory named todir (effectively, a directory rename operation), whether todir exists or not.

rsync -a fromdir todir

will always copy the directory fromdir into the directory todir, whether todir exists or not.

These rsync operations are idempotent, which it important because rsync is designed to incrementally re-sync directories. It is expected that it will commonly be run more than once, which is why it needed to address this IMHO fundamental bug/limitation in cp and scp.



I agree with you, but the cases when I want to copy the contents of a directory instead of the directory are extremely rare.

Because such cases are so rare, it does not make any difference for me if I have to add "/*" instead of the slightly shorter "/" (even when I also have to add "/.*" for hidden files/directories).

So I would like if rsync would have a command-line option to ignore trailing slashes, like other programs have (and which I always use with them).

As it is, rsync is the only program where I have to be extra cautious before executing a command-line, because the directory names are in most cases provided by auto-complete, and most shells terminate them with a trailing slash, which I have to be very careful to always delete, which slows me a lot.

An exception is when using zsh, which sometimes, but not always, deletes itself the trailing slash added by auto-complete (zsh might be confused by the aliases used by me, so it usually deletes the trailing slashes when they do not matter, but retains them when I would want them to be deleted).


> Because such cases are so rare, it does not make any difference for me if I have to add "/*" instead of the slightly shorter "/" (even when I also have to add "/.*" for hidden files/directories).

I think this is the wrong way to think of it. It's more like "rsync foo/ bar/" means "make these two directories the same" - it does change the last-modified timestamp on bar/ to match foo/. If you use "foo/*" it excludes the directory and last-modified becomes now.

For all the combinations this is what's in my head, and I find it for the most part very consistent and predictable:

* rsync -a foo bar/ -> put this file in this directory (a directory is a special type of file, so you end up with bar/foo/)

* rsync -a foo/ bar/ -> put the contents of foo/ into bar/ (but also we're syncing the directories, which is why bar/'s last-modified is changed)

* rsync -a foo bar -> put this file in here, whatever "bar" is (makes a file named bar, unless foo is a directory - you can't put a directory inside a non-directory file, so it makes directory bar and puts foo inside it)

* rsync -a foo/ bar -> This is the only one I have issues with because it doesn't particularly make sense so I never have reason to even try using it - put a directory into a file? But it's the same as "foo/ bar/" case.


> If you use "foo/*" it excludes the directory and last-modified becomes now.

True, but instead of adding "/" to the source you obtain the same effect by deleting the last component of the destination (which is normally a non-action, you just do not auto-complete far enough).

If you meant that you might want to have "foo" and "bar" directories with the same content and timestamps, but with different names, you can rename the target directory before and after the rsync.

This is negligible overhead, because in many decades of using computers for all kinds of applications, I have not encountered a single case when I would have needed to have 2 directories with identical content and even with identical directory timestamps, but with different directory names.

Making a backup directory in the same directory with the original directory would not be a good example, that would be just stupid. Any backup directory should be placed as far as possible from the original directory, when not on a different filesystem, in which case they can have the same name.

The way how rsync handles names without trailing slashes is completely right.

The way how rsync handles trailing slashes to encode alternative behaviors might have been OK if this feature would not have been in conflict with the auto-complete feature of the shells.

For all the rsync behaviors that can be obtained by adding some "/", there are alternatives almost as simple as that.

A slight simplification in the encoding of the seldom used alternative behaviors is paid by a large complication in the typing of the frequently used behaviors, where you must always insert a backspace + space sequence after each directory name, to delete the dangerous trailing slash.


> you can rename the target directory before and after the rsync.

The problem is that the ergonomics of renaming a directory on a remote machine is vastly inferior to doing so on a local machine. Locally, you can use mv. There is no "rmv".

Even worse, rsync is from a different generation of unix utilities than cp/mv or rcp/rsh or scp/ssh. It is more "Swiss army knife" style, able to stand on its own. It shouldn't need to depend on a mythical rmv command.

Finally, I very frequently use the "copy a directory to a new name" feature, both locally and remotely. Locally, I use it to make a quick backup of a directory for emergency recovery. Remotely, syncing a local log directory to a remote "log.machine-name" is pretty common I think. Without renaming, you would do "log -> remote:machine-name", but machine-name would have to exist on the remote machine, necessitating a separate "rmkdir" command.


Great explanation! I prefer rsync to cp, etc., because of this consistency, but I never appreciated the idempotency implications.


Once I found the rsync --relative (-R) command mode and its /./ meta syntax, I no longer appreciated this trailing slash feature as a good idea on its own.

When you are worrying about idempotent re-sync of trees, I think it is so much more important to think about where the roots of the synchronization should match up between local and remote, and to make that explicit in the calling convention.

   rsync -Ra /src-only/./{a,b,c}  remote:/remote-only/./
I also think you should be writing this idempotent call in a script to presumably be reused over time, and this helps document the goal.


I didn't know about this meta-syntax, thanks - it looks very useful.

I would have to think about how it fits in with the philosophy of the trailing slash sometime, but I don't want to hurt my head right now.


Isn't that problem also solved with the `dir/.` syntax? Adding the `.` signifies content versus the directly itself

I think it's `cp` that uses that syntax


I knew most of this but it was a great refresher and I learned a few more details! Thanks!


I think that's a failure on SCP side. It should have never allowed scp -r dir target_dir with non-existent target_dir in the first place.


I get what you are saying: cp/scp should have consistent behavior when the second arg is a directory. The "problem" operation you describe is analogous to copying plain files, though (copying contents to a new name).

I don't think having to create a target directory first is obvious, although neither is the fact that bare "cp" doesn't copy directories.

Finally, for remote copying it would be a nightmare for scp to require the destination directory to exist. To copy a directory tree to a remote server with a new name you'd need to either ssh a remote mkdir command, or add a flag to scp to specify the new name.




Consider applying for YC's Summer 2026 batch! Applications are open till May 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: