Я пытаюсь создать сценарий bash на основе входного файла (list.txt). Входной файл содержит список файлов с абсолютным путем. На выходе должен быть сценарий bash (move.sh), который перемещает файлы в другое место, сохраняет структуру папок, но немного раньше меняет имя целевой папки.
Пример файла Input list.txt выглядит так:
/In/Folder_1/SomeFoldername1/somefilename_x.mp3
/In/Folder_2/SomeFoldername2/somefilename_y.mp3
/In/Folder_3/SomeFoldername3/somefilename_z.mp3
Выходной файл (move.sh) после создания должен выглядеть так:
mv "/In/Folder_1/SomeFoldername1/somefilename_x.mp3" /gain/Folder_1/
mv "/In/Folder_2/SomeFoldername2/somefilename_y.mp3" /gain/Folder_2/
mv "/In/Folder_3/SomeFoldername3/somefilename_z.mp3" /gain/Folder_3/
Структура папок должна быть более или менее сохранена.
после выполнения созданного сценария bash (move.sh) результат должен выглядеть так:
/gain/Folder_1/somefilename_x.mp3
/gain/Folder_2/somefilename_y.mp3
/gain/Folder_3/somefilename_z.mp3
Что я сделал до сих пор.
find /In/ -iname "*.mp3" -type f > /home/maars/mp3/list.txt
cp -a /home/maars/mp3/list.txt /home/maars/mp3/move.sh
# read the list and split the absolute path into fields
while IFS= read -r line;do
fields=($(printf "%s" "$line"|cut -d'/' --output-delimiter=' ' -f1-))
done < /home/maars/mp3/move.sh
# add the target path based on variables at the end of the line
sed -i -E "s|\.mp3|\.mp3"\"" /gain/"${fields[1]}"/|g" /home/maars/mp3/move.sh
sed -i "s|/In/|mv "\""/In/|g" /home/maars/mp3/move.sh
Сценарий просто использует значение ${fields[1]}, то есть Folder_1, и помещает его во все строки в конце. Вместо Folder_2 и Folder_3.
Текущий результат выглядит как
mv "/In/Folder_1/SomeFoldername1/somefilename_x.mp3" /gain/Folder_1/
mv "/In/Folder_2/SomeFoldername2/somefilename_y.mp3" /gain/Folder_1/
mv "/In/Folder_3/SomeFoldername3/somefilename_z.mp3" /gain/Folder_1/
rsync не подходит, так как мне нужен полный контроль над перемещаемыми файлами.
Что я могу сделать лучше, чтобы решить эту проблему?
Обновлено: @Socowi мне очень помог, указав в правильном направлении. После того, как я глубоко погрузился в мир регулярных выражений, я смог решить свои проблемы. Большое тебе спасибо
done < /home/maars/mp3/move.sh - это просто копия done < /home/maars/mp3/list.txt, как определено в строке 1. Я сохраняю done < /home/maars/mp3/list.txt для других занятий.
Ах я вижу. Другой вопрос; вы хотите переместить только файлы mp3 или целые каталоги? Если вы хотите переместить только файлы mp3: существуют ли уже целевые каталоги или их нужно создать?
только mp3. Целевые каталоги /gain/Folder_x/ уже существуют.
Но каталога /gain/Folder_1/SomeFoldername1 не существует. Ok.
В моем сценарии этой папки в настоящее время не существует, это правильно. Но в моем случае это не совсем необходимо. Если он будет создан, это нормально, но не важно.
Поскольку вы хотите получить /gain/Folder_1/SomeFoldername1/somefilename_x.mp3 в качестве конечного результата, вам нужно сначала создать /gain/Folder_1/SomeFoldername1. mv не может создавать каталоги.
это ясно. для пояснения я скорректировал окончательный вид на: /gain/Folder_1/somefilename_x.mp3/gain/Folder_2/somefilename_y.mp3/gain/Folder_3//somefilename_z.mp3





Она не встроена в bash, но команда mmv хороша для такого типа mv, где вам нужно использовать подстановочные знаки в путях. Что-то вроде следующего должно работать:
mmv "in/*/*/*" "#1/#3"
Обратите внимание, что это не создаст каталоги для вас, но в вашем примере выше похоже, что они уже существуют?
The script just use the value of ${fields[1]}, which is Folder_1 and put this in all lines at the end. Instead of Folder_2 and Folder_3.
Вы перебираете все строки и обновляете fields для каждой строки. После завершения цикла fields сохраняет свое значение (из последней строки). Вам нужно будет переместить команды sed в свой цикл и убедиться, что только текущая строка заменена на sed. Однако есть способ получше - см. Ниже.
What could I do better
Есть много вещей, которые вы могли бы улучшить, например
fields с mapfile -d/ fields вместо printf + cut + ($()). Таким образом, у вас также не будет проблем с пробелами в путях.sed только один раз вместо создания массива fields и использования нескольких команд sed. Вы можете заменить шаг 2 этим небольшим скриптом:cp -a /home/maars/mp3/list.txt /home/maars/mp3/move.sh
sed -i -E 's|^/[^/]*/([^/]*).*$|mv "&" "/gain/\1"|' /home/maars/mp3/move.sh
Однако лучшей оптимизацией было бы отказаться от этого трехэтапного подхода и использовать только один сценарий для поиска и перемещения файлов:
find /In/ -iname "*.mp3" -type f -exec rename -n 's|^/.*?/(.*?)/.*/(.*)$|/gain/$1/$2|' {} +
Опция -n напечатает то, что будет переименовано, без фактического переименования чего-либо. Удалите -n, когда вы будете довольны результатом. Вот результат:
rename(/In/Folder_1/SomeFoldername1/somefilename_x.mp3, /gain/Folder_1/somefilename_x.mp3)
rename(/In/Folder_2/SomeFoldername2/somefilename_y.mp3, /gain/Folder_2/somefilename_y.mp3)
rename(/In/Folder_3/SomeFoldername3/somefilename_z.mp3, /gain/Folder_3/somefilename_z.mp3)
Я попытался применить sed -iE 's|^/[^/]*/([^/]*).*$|mv "&" "/gain/\1"|' /home/maars/mp3/move.sh, но получил сообщение об ошибке: sed: -e expression #1, char 40: invalid reference \1 on s 'command's RHS'
@zumbi Похоже, sed не терпит сокращенной формы -iE. Попробуйте еще раз с -i -E. См. Редактировать.
Он работал с -i -E. Мне нужно поиграть с find /In/ -iname "*.mp3" -type f -exec rename -n 's|^/.*?/(.*?)/.*/(.*)$|/gain/$1/$2|' {} +, чтобы понять это. потому что это еще не дает мне никаких результатов.
Возможно, у вас другой rename, чем я имел в виду. В Ubuntu rename - это символическая ссылка на /usr/bin/file-rename. Программа написана Ларри Уоллом (см. man rename | grep -A1 AUTHOR), а rename --version печатает /usr/bin/rename using File::Rename version 0.20. В других системах та же самая команда может иметь другое имя, например prename. Для получения дополнительной информации см. здесь и здесь.
Сначала мне нужно было установить rename.
Вы написали
done < /home/maars/mp3/move.sh. Вы не имели в видуdone < /home/maars/mp3/list.txt?