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