Copy only directories with .mp3 files with rsync
First, why --exclude=* --include=*
doesn't work: because the first matching rule applies. *
matches everything, so everything is excluded, and rsync doesn't even try the second rule for any file. See Rsync filter: copying one pattern only for a guide to rsync filters.
To include .mp3
files, you need
rsync -a -m --include='*.mp3' --include='*/' --exclude='*'
i.e. include MP3, include all directories (if a directory isn't included then none of the files inside it are included) and exclude everything else. The -m
option (a.k.a. --prune-empty-dirs
) makes rsync skip directories that don't contain any files to be copied. But that won't include other files in the same directory as the .mp3
file. For that, you need some help from the shell or other tools.
In zsh, you can match .mp3
files in subdirectories with **/*.mp3
, and then use a history modifier as a glob qualifier to transform the result into the list of directories containing .mp3
files.
rsync -a **/*.mp3(:h) /destination
If you have too many directories (or more precisely, if the combined length of their names is too large), this may break the command line limit. Removing duplicates might help:
typeset -aU directories
directories=(**/*.mp3(:h))
rsync -a $directories /destination
This doesn't eliminate the risk that the command is too long, it merely reduces it. To eliminate the risk, use zargs
to run rsync in batches.
autoload -U zargs
typeset -aU directories
directories=(**/*.mp3(:h))
do_rsync () { rsync -a $@ /destination; }
zargs --max-chars=$(($(get_conf ARG_MAX) - 500)) -- $directories -- do_rsync
You're trying to do too much with rsync. The first thing to do is to produce the list of all the MP3 files.
find Music -type f -iname '*.mp3'
Then convert the list of files into a list of the directories that contain the files.
sed 's:/[^/]*$::'
Eliminate any duplication of directories in the list.
sort -u
Now you have the list of directories you need. Feed them to rsync one at a time. Use --relative to keep the directory tree intact at the destination.
while IFS= read -r dir; do rsync --archive --relative "$dir" /mnt; done
Altogether as a single pipeline:
find Music -type f -iname '*.mp3' |
sed 's:/[^/]*$::' |
sort -u |
while IFS= read -r dir; do rsync --archive --relative "$dir" /mnt; done