Сортировка файлов с помощью Node.js
СтатьиС моего последнего поста прошло уже несколько месяцев, что-то давно ничего не писал — много работы и интересных проектов. Но сегодня решил, что режим тишины нужно нарушить и, в конце концов, поделится некоторыми наработками, так сказать.
Не так давно появилась очень интересная задача, которую нужно было решить. Про неё и хочу рассказать в данном посте.
Задача
Не так давно появилась следующая задача. Дано следующее: есть папка с 1500+ файлов разных расширений и размеров, названные рандомно (от просто названий типа «документ» и родного «asdfgh», до хэшеподобных названий), и таблица, в которой указан сам файл и его название, которое должно быть. И кроме того, там же указано в каком каталоге он должен находится (в зависимости от названия организации и прочего).
Вероятно, очень путано объяснил, приведу пример. Например, есть файл вида «последняя_версия-12.docx», который на самом деле называется «Инструкция по использованию Google», которая должна находится в директории «Менеджеру», которая является дочерней для папки «LLC Horns and Hooves».
В каждой папке может быть по нескольку файлов — от 3 до 8 (в зависимости от того, есть ли они или нет). И у родительской папки может быть n дочерних директорий.
В общем, в итоге нужно рассортировать больше 1000 файлов, раскладывая их по нужным папкам и переименовывая. Да, вы правильно отметили, что я сначала написал больше 1500, а теперь уже говорю о чуть больше 1000 файлов — некоторые лишние и ненужные, вообще не упомянутые в таблице.
Эксперимент показал, что в среднем на 1 файл нужно потратить минуты 2: найти название в таблице -> найти его в исходной папке -> скопировать в нужную директорию (если нет, то создать) -> переименовать. Т.е. по прикидкам получилось около 2000 минут или 33 часа с небольшим непрерывной работы. Фактически, 4 рабочих дня только и делать, что работать с этими файлами.
Естественно, это работу необходимо было автоматизировать. И на помощь позвали Node.js
Решение
На самом деле было большой удачей то, что была таблица, в которой сам файл с расширением, его правильное имя, а также список директорий (родительская + дочерняя при наличии) уже были указаны. Фактически, уже часть работы сделана, нам осталось только написать немножко кода.
Первое, что нужно сделать — превратить таблицу в более удобный формат данных, с которым уже можно работать. Самое простое — это превратить её в json, благо даже онлайн-сервисов очень много. Мне больше всего понравился вот этот (домен на http).
На выходе мы получаем массив объектов, где каждый имеет одинаковые по названию ключи, содержащие в себе все необходимые нам данные.
Есть есть массив, значит мы его можем перебрать. И вот здесь наступает время Node и javascript. Итак, начинаем кодить.
Для начала нам нужно подключить модуль для работы с файловой системой и собственно, сам json-файл (полученный json вынес в отдельный файл, чтобы не мешался).
Весь код будет хранится в файле index.js.
// Подключаем модуль
const fs = require('fs');
// Читаем файл с входными данными
const inputFile = 'data.json';
const data = JSON.parse(fs.readFileSync(inputFile, 'utf8'));
Таким образом мы прочитали файл и в виде массива объектов присвоили его переменной data. Теперь нам необходима функция, которая будет обрабатывать массив. Поскольку в одном объекте у меня могло быть до 8 файлов, то я приведу код в сокращении, поскольку там все одно и то же, только ключи меняются.
Мы люди простые, поэтому саму функцию по перебору назовем handlerData и скормим ей нашу переменную.
// Функция-обработчик массива
function handlerData(data) {
for(let i = 0; i < data.length; i++) {
let numstep = i + 1;
console.group('Обрабатываем строчку #' + numstep);
/* ==== Работаем с папками ==== */
// Определяем и создаем родительскую директорию, в которой будет записан новый файл
let outputFolder = 'output/' + data[i].folder;
try {
const stats = fs.statSync(outputFolder);
}
catch(err) {
console.error('Директории ' + data[i].folder + ' нет, создаем её');
fs.mkdirSync(outputFolder);
}
/* ==== Работаем с файлом ==== */
if(data[i].f1_file) {
let inputFile = data[i].f1_file;
let inputFilePath = 'input/' + inputFile;
let inputFileXtn = getExtension(inputFile);
// Название файла после переименования
let outputFile = outputFolder + '/' + data[i].f1_name + inputFileXtn;
// Читаем и записываем файл
let fileContent = fs.readFileSync(inputFilePath);
fs.writeFileSync(outputFile, fileContent);
// Пишем лог
console.log('Файл ' + inputFile + ' записан в директорию "' + outputFolder + '" под именем "' + data[i].f1_name + inputFileXtn + '"');
}
}
}
Теперь разберем, что происходит в данной функции:
- Мы передаем на обработку переменную data (массив объектов) и без лишних прелюдий запускаем переборку массива.
- Начинаем с создания папки — по ключу data[i].folder смотрим, как должна называться директория. Если её нет — создаём и передаем все данные в переменную outputFolder.
- Далее переходим к файлу — проверяем, есть ли в данном объекте такой ключ (файл), и если да, то начинаем работать с ним — записываем название исходного файла data[i].f1_file, определяем выходной путь outputFile, который состоит из комбинации пути до файла, нового или правильного имени файла (data[i].f1_name) и расширения файла (об этом ниже).
- Далее мы берем и читаем этот файл (fs.readFileSync) и перезаписываем (fs.writeFileSync).
Таким образом мы организовываем чтение файлов из папки input с дальнейшем переименованием и копированием в папку output в нужные директории.
Теперь про расширение файла. Поскольку у нас файлы переименовываются, а расширения разные, то если мы этот момент упустим, то создадим кучу файлов вообще без расширения. Поэтому для этой задачи нужно дописать ещё одну небольшую функцию, которая возвращает расширение файла — getExtension.
// Функция возврата расширения файла
function getExtension(filename) {
let r = filename.lastIndexOf('.');
return (r < 0) ? '' : filename.substr(r);
}
В самом начале я говорил также о том, что у папки могут быть дочерние директории — с ними поступаем примерно так же, как и с файлами: проверяем ключ на существование, если он есть, то дописываем в outputFolder её название, проверяем и т.д.
It’s work!
Теперь финал — пишем в консоли node index. И остается только наблюдать, как скрипт стремительно перебирает весь наш массив.
В моём случае весь процесс занял всего 72 секунды. Это примерно в 1600 раз быстрее, чем это сделал бы человек. Про вероятные опечатки или просто «случайно взял не тот файл» говорить не стоит.
Часто в интернете, в чатах язык javascript часто ругают. Но тем не менее, при правильном использовании он поможет сохранить Вам очень много времени. А время = деньги 😉
Что можно улучшить?
Поскольку данный вариант делался не для красоты, а побыстрее и для себя, то здесь есть несколько вещей, которые в принципе можно улучшить, если вдруг подобная задача возникнет у вас и это нужно будет делать регулярно.
Длина названия файла. В нескольких случаях файлы назывались так длинно, что node не мог сохранить такой файл, поскольку файловая система такую длину не поддерживает. Поэтому можно сделать функцию, которая следила за этим и при необходимости обрубала часть названия. И объединить её с той, что возвращает расширение. Опционально — убирать лишние пробелы.
Проблемные символы. Вернее, один проблемный символ — «/». Он считался за часть пути. Это нужно иметь ввиду. Этот вопрос можно решить также в функции, что описана абзацем выше.
Работа с файлами — отдельная функция. Честно, в своём рабочем варианте я немного наг****дил и просто строки с обработкой файлов скопировал/вставил несколько раз, меняя только префикс у ключа. Это в общем хоть как-то пойдет, если файлов в объекте немного (как у меня), но если их будет гораздо больше — от обязательно в отдельную функцию.
Логирование работы. Оно в общем уже реализовано, его можно только доработать — в примере вся процедура работы пишется в консоли, а можно сделать создание файла, в котором будет всё подробно расписан, какой файл куда и под каким именем сохранён.
Вот такой кейс получился по работе с Node.js. Надеюсь, данная статья была полезна для Вас.