sh recursive copy (cp -r) - How to exclude subfolder
I don't know why you think that rsync would be slow. The speed of a copy is mostly determined by the speed of the disk. Rsync has many options to specify what you want included and excluded, so it gives you much better control than shell globbing.
As the bash manual states, the !(patter)
is only recognized in bash if extglob
is set. In your example you didn't set extglob
. Further, a bash
started as sh
is still bash
, but will disable some extensions for compatibility.
The SSH server will start the user's login shell, as specified in /etc/passwd
. You can either change the shell, or use that shell to start another shell that fits your needs better.
SSH runs your login shell on the remote system, whatever that is. But !(foo)
requires shopt -s extglob
, which you might not have set on the remote.
Try this to see if SSH runs Bash on the remote side:
ssh me@somehost 'echo "$BASH_VERSION"'
If that prints anything, but your startup scripts don't set extglob
, you can do it by hand on the command passed to ssh
:
ssh me@somehost 'shopt -s extglob
echo srcdir/!(subdir)'
# or
ssh me@somehost $'shopt -s extglob\n echo srcdir/!(subdir)'
extglob
affects the parsing of the command line, and only takes effect after a newline, so we have to put a literal newline there, a semicolon isn't enough.
ssh me@somehost 'shopt -s extglob; echo srcdir/!(subdir)'
Also not that if you escape the parenthesis with backslashes, they lose their special properties, like any other glob characters. This is not what you want to do in this case.
$ touch foo bar; shopt -s extglob; set +o histexpand
$ echo *
bar foo
$ echo !(foo)
bar
$ echo \*
*
$ echo !\(foo\)
!(foo)
A few notes first:
- the ssh server doesn't start
sh
to interpret the command line sent by the client, it runs the login shell of the user on the remote host, asthat-shell -c <the-string-provided-by-the-client>
. The login shell of the remote user could be anything. Bear in mind that some shells liketcsh
,fish
orrc
have very different syntax from that ofsh
. - it is really a command line, or more exactly a string (that can contain newline characters, so several lines). Even if you do
ssh host cmd arg1 'arg 2'
wherecmd
,arg1
andarg 2
are three arguments passed tossh
,ssh
concatenates those arguments with spaces and actually sends thecmd arg1 arg 2
string tosshd
, and the remote shell would split that intocmd
,arg1
,arg
and2
. !(subdir)
is a glob operator (aksh
glob operator also supported byzsh -o kshglob
andbash -O extglob
). Like all globs, it excludes hidden files, so beware there may be other files that it excludes.
Here, to avoid the problem with finding out the right syntax for the remote shell, you can actually tell that other shell to start the shell you want and feed it the code via stdin (one of the options listed at How to execute an arbitrary simple command over ssh without knowing the login shell of the remote user?)
ssh host 'bash -O extglob -O dotglob' << 'EOF'
cp -r srcdir/!(subdir) dstdir/
EOF
bash -O extglob -O dotglob
is a command line that is understood the same by all major shells, including Bourne-like ones, csh, rc, fish... The above would work as long as bash
is installed and is in the user's $PATH
(default $PATH
, possibly modified by the user's login shell like with ~/.zshenv
for zsh
, ~/.cshrc
for csh
, ~/.bashrc
for bash
).
POSIXly (though in practice, you may find that more systems have a bash
command than a pax
command), you could do:
ssh host sh << 'EOF'
cd srcdir && pax -rw -'s|^\.//\./subdir\(/.*\)\{0,1\}$||' .//. /path/to/destdir/
EOF
-s
applies substitutions to the paths being transferred. When that substitution expands to nothing, the file is excluded. The problem is that substitutions also apply to target of symlinks. That's why we use .//.
above to make it less likely that a symlink be affected.