Скопируйте файлы, добавив путь в имя файла

Я хочу скопировать mp3 файлы из каталога на флешку. Они хранятся в структуре каталогов, такой как: Artist/Album/Track.mp3 . Так как mp3-плеер моей машины отстой, я хочу, чтобы все мои mp3-файлы были в корневом каталоге флеш-накопителя с именем файла в формате Artist-Album-Track.mp3 . Как я могу скопировать файлы из каталога на флешку, добавив путь в имя файла?

Это может сделать простой цикл for с расширениями параметров для извлечения различных элементов:

 for file in */*/*.mp3; do artist=${file%%/*} rest=${file#*/} album=${rest%%/*} track=${rest#*/*} cp -- "$file" /flash/drive/"${artist}-${album}-${track}" done 

Первое расширение удаляет все, начиная с конца файла и заканчивая первой косой чертой – генерируя художника.

Второе расширение лишает ведущих персонажей до и через первую косую черту – убирая художника с пути.

Третье расширение похоже на первое, удаляя имя файла из оставшегося пути, оставляя альбом.

Четвертое расширение похоже на второе, удаляя альбом, оставляя только имя файла.

Затем мы собираем все это обратно вместе с тире и копируем его в нужный путь / flash / drive.

Используя find и bash скопируйте файлы из $mp3dir в $destdir :

 mp3dir="$HOME/my_music" destdir="/mnt/my_mp3_player" find "$mp3dir" -type f -name '*.mp3' -exec bash -c ' mp3dir=$1; destdir=$2; shift 2 for pathname do dest=${pathname#$mp3dir/} dest="$destdir/${dest//\//-}" if [ -f "$dest" ]; then printf "%s exist, skipping %s\n" "$dest" "$pathname" >&2 else cp "$pathname" "$dest" fi done' bash "$mp3dir" "$destdir" {} + к mp3dir="$HOME/my_music" destdir="/mnt/my_mp3_player" find "$mp3dir" -type f -name '*.mp3' -exec bash -c ' mp3dir=$1; destdir=$2; shift 2 for pathname do dest=${pathname#$mp3dir/} dest="$destdir/${dest//\//-}" if [ -f "$dest" ]; then printf "%s exist, skipping %s\n" "$dest" "$pathname" >&2 else cp "$pathname" "$dest" fi done' bash "$mp3dir" "$destdir" {} + 

Это ищет любой обычный файл в $mp3dir или ниже, чьи имена заканчиваются на .mp3 . Для пакетов из них выполняется короткий сценарий. Пара параметров к сценарию – $mp3dir и $destdir а остальные – пути файлов MP3.

Сценарий выбирает два имени каталога из первых двух аргументов командной строки, а затем перебирает остальные аргументы, создавая путь назначения как $dest для каждого файла MP3. Это делается с помощью подстановки параметров bash которая заменяет все слэши в имени пути на дефисы после удаления начального бита $mp3dir пути.

Если $dest уже существует, печатается сообщение об этом, в противном случае файл копируется.

Связанные с:

  • Понимание опции -exec `find`

Я считаю плохой практикой манипулировать несколькими строками с помощью расширений параметров оболочки и назначения переменных.

 root_dir="~/songs" for fn in */*/*.mp3 do cp -v "$fn" "${root_dir}/${fn////-}" # replace all slashes by dashes done 

Выход:

 ~/songs/Artist-Album-Track.mp3 

Фон

Мне было любопытно, смогу ли я найти решение, которое достигает двух целей:

  • легче читать
  • легче отлаживать

Основываясь на замечательном ответе @ kusalananda на другие вопросы и ответы U & L под названием: Понимая опцию -exec find , я думаю, что я придумал что-то, что решит вашу проблему.

Для начала вот моя примерная структура каталогов, которая имитирует вашу.

 $ mkdir -p Artist{1..5}/Album{1..5} $ touch Artist{1..5}/Album{1..5}/Track{1..5}.mp3 

Это приводит к следующему:

 $ find Artist* -type f | head Artist1/Album1/Track1.mp3 Artist1/Album1/Track2.mp3 Artist1/Album1/Track3.mp3 Artist1/Album1/Track4.mp3 Artist1/Album1/Track5.mp3 Artist1/Album2/Track1.mp3 Artist1/Album2/Track2.mp3 Artist1/Album2/Track3.mp3 Artist1/Album2/Track4.mp3 Artist1/Album2/Track5.mp3 

Теперь скопируйте ваши mp3 файлы в ./targetDir :

 $ find . -type f -name '*.mp3' -exec sh -c \ 'cp {} targetDir/$(echo "{}" | sed "s#\./##g;s#/#-#g")' \; 

Что приводит к этому:

 $ ls targetDir/ | head Artist1-Album1-Track1.mp3 Artist1-Album1-Track2.mp3 Artist1-Album1-Track3.mp3 Artist1-Album1-Track4.mp3 Artist1-Album1-Track5.mp3 Artist1-Album2-Track1.mp3 Artist1-Album2-Track2.mp3 Artist1-Album2-Track3.mp3 Artist1-Album2-Track4.mp3 Artist1-Album2-Track5.mp3 

Мне нравится такой подход, потому что я могу сначала обернуть команду cp ... в echo чтобы я мог проверить вывод перед выполнением работы:

 $ find . -type f -name '*.mp3' -exec sh -c \ 'echo "cp {} targetDir/$(echo "{}" | sed "s#\./##g;s#/#-#g")"' \; cp ./Artist1/Album1/Track1.mp3 targetDir/Artist1-Album1-Track1.mp3 cp ./Artist1/Album1/Track2.mp3 targetDir/Artist1-Album1-Track2.mp3 cp ./Artist1/Album1/Track3.mp3 targetDir/Artist1-Album1-Track3.mp3 cp ./Artist1/Album1/Track4.mp3 targetDir/Artist1-Album1-Track4.mp3 cp ./Artist1/Album1/Track5.mp3 targetDir/Artist1-Album1-Track5.mp3 cp ./Artist1/Album2/Track1.mp3 targetDir/Artist1-Album2-Track1.mp3 cp ./Artist1/Album2/Track2.mp3 targetDir/Artist1-Album2-Track2.mp3 cp ./Artist1/Album2/Track3.mp3 targetDir/Artist1-Album2-Track3.mp3 cp ./Artist1/Album2/Track4.mp3 targetDir/Artist1-Album2-Track4.mp3 ... 

Как это устроено

Это решение принимает выходные данные find и затем запускает следующую команду оболочки:

 cp {} targetDir/$(echo "{}" | sed "s#\./##g;s#/#-#g")' 

Это будет cp файл из Artist../Album../Track.. в targetDir/.. и переформулировать имя так, чтобы оно targetDir/.. тире ( - ) targetDir/.. где есть косая черта ( / ).

ПРИМЕЧАНИЕ: я добавил 2 операции в sed . Первый удаляет любой префикс ./ который может существовать, если вы используете find . ... find . ... вместо того, чтобы find Artist* ..

Альтернативы?

Я думаю, что делать это с помощью find и cp все еще не идеальное решение. Я также поддерживаю каталоги файлов MP3 и думаю, что делать что-то подобное с rsync и с помощью списков, которые вы предоставляете, rsync может быть более полезной реализацией, более долгосрочной, поскольку вы можете использовать ее для обновления, а не переписывать каждый раз при запуске ,

 $ man rsync ... --files-from=FILE read list of source-file names from FILE 

Просто мысль.