Сортировка файлов с помощью Node.js

Сортировка файлов с помощью 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. Надеюсь, данная статья была полезна для Вас.

Анатолий Куликов

Анатолий Куликов

Автор блога, веб-разработчик
  • at sign
  • vk logo
Комментариев нет

Добавить комментарий

Для отправки комментария вам необходимо авторизоваться.