Перемещение как именованные файлы в самоименованные каталоги

У меня есть несколько тысяч файлов в одном каталоге, как который я хотел бы сопоставить в каталогах так:

От этого:

└── Files
    ├── AAA.mkv
    ├── AAA.nfo
    ├── AAA-picture.jpg
    ├── BBB.mp4
    ├── BBB.srt
    ├── BBB-clip.mp4
    ├── CCC.avi
    ├── CCC.srt
    ├── CCC-clip.mov
    └── CCC.nfo

К этому:

└── Files
    ├── AAA
    │   ├── AAA.mkv
    │   ├── AAA.nfo
    │   └── AAA-picture.jpg
    ├── BBB
    │   ├── BBB.mp4
    │   ├── BBB.srt
    │   └── BBB-clip.mp4
    └── CCC
         ├── CCC.avi
         ├── CCC.srt
         ├── CCC-clip.mov
         └── CCC.nfo

Имена файлов варьируются по длине и количеству слов, иногда разделяемых пробелами и возможно некоторыми с дефисами (в дополнение к тем заканчивающимся '-short'. Они - прежде всего, видеофайлы со множеством форматов/контейнеров: mov/mpg/mkv/mp4/avi/ogg. Некоторые снабжены субтитрами. У некоторых есть файлы со связанными метаданными (.nfo или - клип)

Править: Основные файлы являются видео (это - то, где я хотел бы потянуть имя каталога). Связанные файлы представляют метаданные. Некоторые отличающиеся в именовании только расширением. Существует полдюжина других вариаций на основное имя файла как-clip.mp4-clip.mov или -picture.jpg, который я изобразил, было ли что-то предложено с теми немногих затем, я мог бы (надо надеяться), работать при выяснении остальных. Таким образом, AAA.mkv перемещается в каталог под названием AAA. Затем все файлы метаданных, которые начинаются с AAA, присоединяются к нему (т.е. в этом примере: AAA-picture.jpg и AAA.nfo). Таким образом, базовое имя является на самом деле подстрокой в случае файла AAA-picture.jpg. Я сказал бы, что, вероятно, относительно безопасно просто использовать дефис в качестве фактора разграничивания..., хотя '-клип' или '-изображение' в целом были бы более безопасными.

Как я могу сделать это, не заболев синдромом канала запястья? Я посмотрел на это, но это достаточно отличалось, что шипели мои слабые способности к сценариям.

Спасибо.

4
задан 13 April 2017 в 05:24

3 ответа

В то время как Ваш вопрос отмечен с bash, это было бы несколько неприятно (по моему скромному мнению) для использования bash для такой задачи. Я предложил бы использовать Python, потому что он имеет много хороших функций для сложных задач, и этот ответ предоставляет решение с помощью того языка.

По существу, что происходит, вот то, что мы используем regex для разделения имен файлов в нескольких разделителях, получаем только первую часть и используем уникальный набор тех первых частей как базовые имена для новых каталогов.

Мы затем пересекаем главный каталог снова и сортируем файлы в их соответствующих местах.

Сценарий не делает ничего захватывающего, и на самом деле в анализе алгоритма это не также успело бы из-за вложенного для циклов, но для "быстрого и грязного, все же осуществимого" решения это в порядке. Если Вам интересно, что делает каждая строка, существует много комментариев, добавленных для объяснения функциональности

Отметьте, демонстрация только показывает печать новых имен файлов для тестирования цели только. Не прокомментируйте os.rename() часть для фактического перемещения файла.

Демонстрация

bash-4.3$ # Same directory structure as in OP example
bash-4.3$ ls TESTDIR
bash-4.3$ # now run script
AAA  AAA.mkv  AAA.nfo  AAA-picture.jpg  BBB  BBB-clip.mp4  BBB.mp4  BBB.srt
bash-4.3$ ./collate_files.py ./TESTDIR
/home/xieerqi/TESTDIR/AAA/AAA-picture.jpg
/home/xieerqi/TESTDIR/AAA/AAA.mkv
/home/xieerqi/TESTDIR/AAA/AAA.nfo
/home/xieerqi/TESTDIR/BBB/BBB.srt
/home/xieerqi/TESTDIR/BBB/BBB.mp4
/home/xieerqi/TESTDIR/BBB/BBB-clip.mp4

Сам сценарий

#!/usr/bin/env python
import re,sys,os

top_dir = os.path.realpath(sys.argv[1])

# Create list of items in directory first
# splitting names at multiple separators
dir_list = [os.path.join(top_dir,re.split("[.-]",f)[0])
            for f in os.listdir(top_dir)
]
# Creating set ensures we will have unique
# directory namings
dir_set = set(dir_list)

# Make these directories first
for dir in dir_set:
    if not os.path.exists(dir):
        os.mkdir(dir)

# now get all files only, no directories
files_list = [f for f in os.listdir(top_dir)
              if os.path.isfile(os.path.join(top_dir,f))
]

# Traverse lists of directories and files,
# check if a filename starts with directory
# that we're testing now, and if it does - move
# the file to that directory
for dir in dir_set:
    id_string = os.path.basename(dir)
    for f in files_list:
        filename = os.path.basename(f)
        if filename.startswith(id_string):
           new_path = os.path.join(dir,filename)
           print(new_path)
           #os.rename(f,new_path)

Дополнительные примечания:

  • Сценарий может хорошо быть адаптирован для разделения файлов в других нескольких разделителях (в re.split() функция): добавьте внутренние квадратные скобки (значение "[.-]") добавьте любые символы, которые Вы хотите.
  • Подвижная часть выполняется с os.rename() функция. Кроме того, Вы могли import shutil и используйте shutil.move() функция. См. https://stackoverflow.com/a/8858026/3701431
5
ответ дан 23 November 2019 в 11:35

Я сделал маленький сценарий удара, чтобы сделать это, упрощенное и улучшенное благодаря комментариям от OP, @dannysauer, @Arronical и @Scott

#!/bin/bash
for file in *
  do mkdir -p "${file%%[.-]*}" 2>/dev/null
    if [[ -d "${file%%[.-]*}" ]]; then
       if [[ -f "$file" ]]; then
         echo mv -v -- "$file" "${file%%[.-]*}"
       fi
    fi
done

Выполненный с echo сначала и затем удалите echo на самом деле перемещать файлы. Скрипт должен быть запущен из каталога, где Вы хотите переместить файлы. Если Вы предпочитаете, здесь это как короткая команда:

for file in *; do mkdir -p "${file%%[.-]*}"; if [[ -d "${file%%[.-]*}" ]]; then if [[ -f "$file" ]]; then echo mv -v -- "$file" "${file%%[.-]*}"; fi ; fi ; done

(снова, удалить echo после тестирования)

Объяснение:

  • for file in *; do mkdir -p "${file%%[.-]*}" сделайте каталог с названием первой части названия каждого файла (пока первый дефис или точечный символ) -p флаг очень важен здесь - без него, сценарий переместит только первый файл соответствия (благодаря Arronical для указания на это -p остановится mkdir от попытки создать существующие каталоги и жалобу на это)
  • 2>/dev/null сценарий жалуется, что не может создать каталог с тем же именем как самим (но все еще работает), таким образом, мы выбрасываем ошибку - это не необходимо при выполнении как острота
  • if [[ -d "${file%%[.-]*}" ]]; then если существует каталог с тем именем (если mkdir было успешно), затем...
  • if [[ -f "$file" ]] если мы имеем дело с файлом (не каталог или что-то еще) затем...
  • mv -v -- "$file" "${file%%[.-]*}" переместите его в каталог соответствия.
9
ответ дан 23 November 2019 в 11:35

На маленьком сценарии Python:

#!/usr/bin/env python3
import shutil
import os
import sys

dr = sys.argv[1]

for f in os.listdir(dr):
    split = f.rfind("."); short = f.find("-")
    if split != -1:
        extension = f[split:]
        newname = f[:short] if short != -1 else f[:split]
        target = os.path.join(dr, newname)
        if not os.path.exists(target):
            os.mkdir(target)
        shutil.move(os.path.join(dr, f), os.path.join(target, f))

Использовать его:

  • скопируйте его в пустой файл
  • Сохраните его как move_into.py
  • Выполните его с каталогом как аргумент:

    python3 /path/to/move_into.py /path/to/directory
    

Сценарий предполагает, что все (соответствующие) файлы имеют расширения. Если файл не имеет никакого расширения, ничего не происходит с ним. Если это - проблема, упомяните, может быть изменен легко.

Объяснение

  • Сценарий ищет возможное расширение.
  • Если не существующий, сценарий оставляет файл (или dir) один.
  • Еще файл будет разделен "-", если есть первый раздел впоследствии используется для создания папок (при необходимости)
  • В противном случае базовое имя файла используется для именования папки.

Впоследствии, файл перемещен в соответствующую папку.

5
ответ дан 23 November 2019 в 11:35

Другие вопросы по тегам:

Похожие вопросы: