Whenever I copied a remote file/directory over SSH with rsync, for example
rsync -Ptr 'remote:downloads/文件夹/' .
It always failed with
rsync: [sender] change_dir "/home/[me]/downloads/[random encoding here]" failed: No such file or directory (2) rsync error: some files/attrs were not transferred (see previous errors) (code 23) at main.c(1861) [Receiver=3.2.7] rsync: [Receiver] write error: Broken pipe (32)
This has troubled me for such a long time but I used to just ignore it: I moved the file or directory into a directory that has no non-ASCII character in its name and just copied that directory instead. This works well until I decided to move more and more of my command line daily activities into Emacs. Dired itself allows you to copy remote files over tramp, but it is extremely slow on files larger than a few KBs, and even worse it is blocking, so you cannot do anything when it is copying.
So I found a package called dired-rsync, which use the beloved rsync and is asynchronous when copying. However, it simply fails to copy anything contains non-ASCII character in its path. Now I seriously began to figure out what's going on and more importantly how should I fix it.
Wrong guess?
So the first thing I think of is locale. Why do I think that? For example:
ssh remote echo '$LANG'
This will print quite differently from you might expect. The reason is that, when run a command over SSH in this way, the command is executed in a non-login shell. A non-login shell means that /etc/profile
is not loaded, which means /etc/profile.d/locale.sh
is not loaded, which means LANG
is very likely to be left unset, if your environment is set-up the same way as I do.
To test this, compare the following 2 line's result:
ssh ezpro 'bash -c "echo \"\$LANG\""'
ssh ezpro 'bash -c -l "echo \"\$LANG\""'
That means locale is very likely to be not set correctly in the environment rsync's part over SSH is running, which might lead to its broken handling of non-ASCII path. Makes sense? Well, maybe. It turns out that the solution does not need to handle locale in some way, so forget about this part.
Simple Fix
The solution is ironically simple: just use -s
flag. From its man page:
–secluded-args, -s This option sends all filenames and most options to the remote rsync via the protocol (not the remote shell command line) which avoids letting the remote shell modify them. Wildcards are expanded on the remote host by rsync instead of a shell. […]
Adding this flag simply JUST WORKS. No other changes to the invocation is needed. I found this solution from an AskUbuntu answer.