Как заменить папку, имя которой является датой, т.е. ГГГГММДД, с иерархией папок года, месяца, даты?

8

У меня есть список папок, в которых есть даты для имен. Даты в формате ГГГГММДД (например, 20150129). В этих папках находятся текстовые документы, связанные с этой конкретной датой.

Я хотел бы реструктурировать их в иерархии папок, переходя из года в месяц, и переместить текстовые документы в соответствующую папку «дата», расположенную ниже в иерархии.

Другими словами, я хотел бы, чтобы корневая папка была названа в честь года, подобного 2015, и затем создала подпапки, названные месяцами, например, 01, а затем создала дополнительные подпапки, названные датами, например, 29, которые содержат соответствующие текстовые документы. ,

Так что путь будет выглядеть 2015/01/29/file.txtили 2015>01>29>file.txt.

Я взглянул на Automator, и кажется, что нечто подобное невозможно, хотя я могу ошибаться, поэтому я хотел бы знать ...

  1. Есть ли какое-то простое решение этой проблемы, которое может понять любой неспециалист, например, рабочий процесс Automator, или это требует некоторого понимания команд терминала и регулярных выражений?

  2. Как можно решить эту проблему, если есть решение?

davidjnatarajan
источник
Кто бы ни проголосовал за то, чтобы закрыть этот вопрос как «слишком широкий», почему? Мне любопытно, что является "слишком широким" в этом вопросе?
user3439894
Все эти папки ГГГГММДД находятся непосредственно в одной главной папке или они распределены по более широкой иерархии?
nohillside
@patrix В моем случае все они находятся в одной директории или главной папке
davidjnatarajan

Ответы:

8

Предполагая, что все эти папки ГГГГММДД являются частью одного и того же родительского каталога, который вы можете запустить

cd PARENT_DIRECTORY
for d in */; do
    [[ $d =~ [0-9]{8}/ ]] || continue
    mkdir -p -- "${d:0:4}/${d:4:2}"
    mv -- "$d" "${d:0:4}/${d:4:2}/${d:6:2}"
done
  • for d in */; doЦикл считывает все записи каталога, то замыкающие /гарантируют , что только имена каталогов фактически совпадают
  • [[ $d =~ [0-9]{8}/ ]] проверяет, состоит ли текущая запись из 8 цифр, и продолжает, если нет, следующую запись
  • ${d:0:4}/${d:4:2}/${d:6:2}использует расширение параметра внутри bashдля создания строки, содержащей новый путь
  • И --в mkdirи mvпредотвращает проблему в случае, если каталог или имя файла начинается с -. Это не может произойти здесь, но это, вероятно, хорошая практика в любом случае.

Спасибо @terdon и @ user3439894 за идеи о том, как улучшить оригинальный скрипт.

Nohillside
источник
Спасибо за ответ, это работает отлично! Я чувствую, что это решение лучше, чем предоставленное @grgarside, потому что оно намного быстрее, особенно когда имеешь дело с массивным корпусом, включающим тысячи текстовых документов.
Давиджнатараян
8

Вы можете использовать следующее в Терминале. cdв содержащую папку, затем выполните следующее:

find . -type f -exec bash -c \
  'F=$(sed -E "s#^\./([0-9]{4})([0-9]{2})([0-9]{2})#\1/\2/\3#" <<< $1);\
  mkdir -p -- $(dirname "$F");\
  mv -- "$1" "$F"' - {} \;

find . -type fполучает каждый файл в текущем каталоге рекурсивно.
-exec bash -cоткрывает оболочку для запуска следующих команд.
F=$(…)открывает подоболочку и использует sed в пути к файлу для управления путем в папки.
^\./([0-9]{4})([0-9]{2})([0-9]{2})является регулярным выражением с тремя группами захвата, а именно: является заменой, где каждая группа захвата ( и т. д.) разделена . создает каталоги для перемещения файлов в. перемещает каждый файл в соответствующую папку.
\1/\2/\3\1/
mkdir -p -- $(dirname "$F")
mv -- "$1" "$F"

Это берет иерархию слева и преобразует ее в иерархию справа:

├── 20170201               └── 2017
   └── abcdefghij             ├── 02
└── 20170302                      └── 01
    └── abcdefghij 2                  └── abcdefghij
                               └── 03
                                   └── 02
                                       └── abcdefghij 2

Если в содержащей папке есть другие файлы с датой в качестве имени, они будут перемещены, как если бы они были папкой. Чтобы предотвратить это, замените вторую строку на:

  'F=$(sed -E "s#^\./([0-9]{4})([0-9]{2})([0-9]{2})(?:/.+)#\1/\2/\3#" <<< $1);\

В (?:/.+)гарантирует , что путь имеет последующий компонент, поэтому игнорирование что - либо без ребенка в родительском каталоге , которые являются файлами.

Grg
источник
@klanomath regex101.com
grg
@grgarside Thanx
klanomath