Нам часто требуется автоматизировать процесс создания резервных копий или архивов таким образом, чтобы они циклически перезаписывались, сохраняя определенное количество последних версий. Например, мы хотим иметь N архивов (с номерами от 1 до N), которые будут сменяться по дням, где номер архива определяется по текущей дате. Это позволяет избежать бесконечного накопления файлов и упрощает управление бэкапами.
Основная идея заключается в использовании математической операции деления по модулю. Если мы возьмем количество дней, прошедших с некоторой фиксированной даты (например, 1 января 2000 года), и разделим это число на желаемое количество цикличных архивов N, то остаток от деления даст нам номер архива (скорректированный для диапазона 1...N).
Задача формулируется следующим образом: как в среде командной строки Windows (CMD) реализовать алгоритм, который выдает номер архива от 1 до N, используя остаток от деления номера дня (считая от 1 января 2000 года или любой другой базовой даты) на N.
Для начала, давайте рассмотрим, как эта логика может быть реализована в среде, где работа с датами и математическими операциями более развита, например, в 1С. Это поможет нам понять основной алгоритм, который затем мы будем адаптировать для CMD.
В 1С, формула для получения цикличного номера архива будет выглядеть следующим образом:
N = 5; // Желаемое количество цикличных архивов
R = ((НачалоДня(ТекущаяДата()) - '20010101') / 3600 / 24) % N + 1;
Сообщить(R);
Разберем эту формулу по шагам:
НачалоДня(ТекущаяДата()): Мы получаем текущую дату без времени, что важно для точного подсчета дней.'20010101': Это базовая дата, от которой мы начинаем отсчет дней. Вы можете выбрать любую удобную дату, главное, чтобы она была константой для всех расчетов.(НачалоДня(ТекущаяДата()) - '20010101'): Эта операция в 1С возвращает разницу между двумя датами в секундах./ 3600 / 24: Мы делим разницу в секундах сначала на количество секунд в часе (3600), а затем на количество часов в сутках (24). Таким образом, мы получаем общее количество полных дней, прошедших с базовой даты до текущего дня.% N: Это оператор деления по модулю (остаток от деления). Он возвращает остаток от деления количества дней на N. Если N равно 5, остаток будет в диапазоне от 0 до 4.+ 1: Мы добавляем единицу к результату, чтобы номер архива был в диапазоне от 1 до N (в нашем примере от 1 до 5), а не от 0 до N-1.Именно эту логику нам нужно воспроизвести или сымитировать в командной строке Windows.
Чистый CMD обладает ограниченными возможностями для работы с датами и выполнения сложных математических операций. Однако, мы можем использовать встроенные в Windows языки сценариев, такие как VBScript, для выполнения вычислений и получения результата, который затем будет использован в BAT-файле.
Шаг 1: Создаем VBScript файл
Создайте файл с именем get_archive_number.vbs (например, в папке C:\Scripts\) и поместите в него следующий код. Этот скрипт вычислит количество дней с заданной начальной даты, применит операцию деления по модулю и выведет полученный номер архива.
Set objDate1 = CreateObject("WbemScripting.SWbemDateTime")
objDate1.SetVarDate #01/01/2000# ' Начальная дата, от которой ведется отсчет
Set objDate2 = CreateObject("WbemScripting.SWbemDateTime")
objDate2.SetVarDate Now ' Текущая дата
' Вычисляем разницу в днях
days_diff = DateDiff("d", objDate1.GetVarDate, objDate2.GetVarDate)
N = 5 ' Желаемое количество цикличных архивов (например, 5)
' Вычисляем номер архива (от 1 до N)
archive_number = (days_diff Mod N) + 1
WScript.Echo archive_number
Давайте подробнее рассмотрим ключевые элементы этого VBScript:
#01/01/2000#: Здесь мы задаем начальную дату, от которой будет вестись отсчет дней. Вы можете изменить ее на любую другую дату по вашему усмотрению.Now: Эта функция VBScript возвращает текущую системную дату и время.DateDiff("d", objDate1.GetVarDate, objDate2.GetVarDate): Это мощная функция VBScript, которая вычисляет разницу между двумя датами. Первый аргумент "d" указывает, что мы хотим получить разницу в днях. Таким образом, days_diff будет содержать точное количество дней между начальной и текущей датой.Mod N: Это оператор деления по модулю в VBScript. Он выполняет ту же функцию, что и оператор % в 1С, возвращая остаток от деления days_diff на N.WScript.Echo archive_number: Эта команда выводит вычисленное значение archive_number на стандартный вывод. Это позволит нам легко захватить его в BAT-файле.Шаг 2: Вызываем VBScript из BAT-файла
Создайте BAT-файл (например, create_archive.bat) в той же папке или в любом другом месте. Этот файл будет вызывать VBScript, захватывать его вывод и использовать полученный номер для формирования имени архива.
@echo off
REM Указываем путь к вашему VBS-скрипту
set "VBS_SCRIPT=C:\Scripts\get_archive_number.vbs"
REM Вызываем VBScript и захватываем его вывод в переменную ARCHIVE_NUM
for /f "usebackq" %%a in (`cscript //nologo "%VBS_SCRIPT%"`) do set "ARCHIVE_NUM=%%a"
echo Номер архива на сегодня: %ARCHIVE_NUM%
REM Теперь вы можете использовать %ARCHIVE_NUM% в команде создания архива
REM Например, для создания архива с именем ARCHIVE_1.rar, ARCHIVE_2.rar и т.д.
REM Предположим, у вас установлен WinRAR, и rar.exe находится в системном PATH.
set "ARCHIVE_PATH=C:\Archives"
set "SOURCE_PATH=C:\SourceData"
set "ARCHIVE_NAME=backup"
echo Создаем архив %ARCHIVE_PATH%\%ARCHIVE_NAME%_%ARCHIVE_NUM%.rar
rar a -ep1 "%ARCHIVE_PATH%\%ARCHIVE_NAME%_%ARCHIVE_NUM%.rar" "%SOURCE_PATH%"
echo.
echo Архив создан.
Рассмотрим команды BAT-файла:
@echo off: Отключает отображение выполняемых команд в консоли, делая вывод чище.set "VBS_SCRIPT=C:\Scripts\get_archive_number.vbs": Мы определяем переменную VBS_SCRIPT, которая содержит полный путь к созданному нами VBScript-файлу. Обязательно скорректируйте этот путь в соответствии с расположением вашего файла.for /f "usebackq" %%a in (`cscript //nologo "%VBS_SCRIPT%"`) do set "ARCHIVE_NUM=%%a": Это ключевая конструкция для взаимодействия с внешним скриптом.
cscript //nologo "%VBS_SCRIPT%": Запускает VBScript с помощью интерпретатора cscript (для консольного режима). Флаг //nologo отключает вывод информации о версии VBScript, оставляя только нужный нам результат.`...`) вокруг команды указывают for /f, что нужно выполнить эту команду и обработать ее вывод как входные данные."usebackq": Этот параметр позволяет использовать обратные кавычки для выполнения команды.%%a: В эту переменную будет помещена строка, выведенная VBScript (в нашем случае это число – номер архива).do set "ARCHIVE_NUM=%%a": Полученное значение из %%a присваивается переменной окружения ARCHIVE_NUM.%ARCHIVE_NUM% может быть использована в команде архивации для создания файла с соответствующим именем. В примере используется rar a -ep1 "..." "..." для создания RAR-архива. Убедитесь, что у вас установлен WinRAR и исполняемый файл rar.exe доступен через переменную среды PATH, либо укажите полный путь к нему.PowerShell является более современной и мощной оболочкой для автоматизации в Windows, доступной в большинстве современных систем. Он предлагает еще более удобные средства для работы с датами и математикой, а его использование аналогично VBScript.
Шаг 1: Создаем PowerShell скрипт
Создайте файл с именем get_archive_number.ps1 (например, в папке C:\Scripts\) и добавьте в него следующий код:
$startDate = [datetime]"2000-01-01" # Начальная дата
$currentDate = [datetime]::Now # Текущая дата
$timeSpan = $currentDate - $startDate
$daysDiff = $timeSpan.Days # Разница в днях
$N = 5 # Желаемое количество цикличных архивов
$archiveNumber = ($daysDiff % $N) + 1
Write-Host $archiveNumber
Давайте разберем этот PowerShell скрипт:
$startDate = [datetime]"2000-01-01" и $currentDate = [datetime]::Now: Мы создаем объекты даты и времени. PowerShell имеет мощные встроенные средства для работы с ними.$timeSpan = $currentDate - $startDate: Эта операция вычисляет разницу между двумя датами, результатом является объект типа TimeSpan, который содержит интервал времени.$daysDiff = $timeSpan.Days: Мы извлекаем количество дней из объекта TimeSpan. Это напрямую дает нам необходимое количество дней, прошедших с начальной даты.$daysDiff % $N: Это оператор деления по модулю в PowerShell, работающий аналогично VBScript и 1С.Write-Host $archiveNumber: Эта команда выводит вычисленный номер архива на стандартный вывод, который мы снова будем захватывать в BAT-файле.Шаг 2: Вызываем PowerShell скрипт из BAT-файла
В вашем BAT-файле (например, create_archive_ps.bat) вызовите PowerShell скрипт и захватите его вывод. Разместите этот BAT-файл в удобном для вас месте, например, там же, где и скрипт.
@echo off
REM Указываем путь к вашему PowerShell скрипту
set "PS_SCRIPT=C:\Scripts\get_archive_number.ps1"
REM Вызываем PowerShell скрипт и захватываем его вывод в переменную ARCHIVE_NUM
for /f "usebackq" %%a in (`powershell -ExecutionPolicy Bypass -File "%PS_SCRIPT%"`) do set "ARCHIVE_NUM=%%a"
echo Номер архива на сегодня: %ARCHIVE_NUM%
REM Далее используем %ARCHIVE_NUM% для создания архива
set "ARCHIVE_PATH=C:\Archives"
set "SOURCE_PATH=C:\SourceData"
set "ARCHIVE_NAME=backup"
echo Создаем архив %ARCHIVE_PATH%\%ARCHIVE_NAME%_%ARCHIVE_NUM%.rar
rar a -ep1 "%ARCHIVE_PATH%\%ARCHIVE_NAME%_%ARCHIVE_NUM%.rar" "%SOURCE_PATH%"
echo.
echo Архив создан.
Ключевые моменты при вызове PowerShell из BAT-файла:
powershell -ExecutionPolicy Bypass -File "%PS_SCRIPT%": Запускает PowerShell скрипт.
-ExecutionPolicy Bypass: Этот параметр временно обходит политику выполнения PowerShell для данного сеанса, позволяя запустить скрипт. Это может быть необходимо, если у вас строгие политики безопасности.-File "%PS_SCRIPT%": Указывает путь к файлу PowerShell скрипта, который нужно выполнить.ARCHIVE_NUM аналогичен использованию VBScript с помощью for /f и обратных кавычек.Если вам не требуется строгая привязка к "дням от 1 января 2000 года", а достаточно цикличной нумерации в пределах месяца (т.е. номер архива будет циклично меняться каждый месяц, используя только день месяца), то можно использовать чистый CMD. Это решение не совсем соответствует изначальной постановке задачи, но может быть полезно для более простых сценариев, где не нужна точная дата-зависимая цикличность на протяжении многих лет, а достаточно ежемесячной.
Рассмотрим пример, где номер архива определяется на основе текущего дня месяца:
@echo off
REM Извлечение текущего дня месяца
REM Формат команды 'date /t' может отличаться в зависимости от локали Windows.
REM Пример для формата 'ДД.ММ.ГГГГ' или 'Пт 20.10.2006'
set "current_day_raw="
for /f "tokens=1,2,3,4 delims=. " %%a in ('date /t') do (
REM Проверяем, если первый токен это день недели (например, "Пт"),
REM то день месяца будет во втором токене.
REM Иначе, если дата начинается сразу с дня (например, "20.10.2006"),
REM то день месяца будет в первом токене.
echo. | findstr /i /l "%%a" "Пн Вт Ср Чт Пт Сб Вс" >nul && (
set "current_day_raw=%%b"
) || (
set "current_day_raw=%%a"
)
)
REM Убираем ведущий ноль, если день месяца меньше 10 (например, 05 -> 5)
REM Это необходимо, так как set /A интерпретирует числа с ведущим нулем как восьмеричные.
set /A current_day = 100%current_day_raw% %% 100
set /A N=5
set /A ARCHIVE_NUM=(%current_day% %% N) + 1
echo Номер архива на основе дня месяца: %ARCHIVE_NUM%
REM Пример использования для создания архива:
set "ARCHIVE_PATH=C:\Archives"
set "SOURCE_PATH=C:\SourceData"
set "ARCHIVE_NAME=monthly_backup"
echo Создаем архив %ARCHIVE_PATH%\%ARCHIVE_NAME%_%ARCHIVE_NUM%.rar
rar a -ep1 "%ARCHIVE_PATH%\%ARCHIVE_NAME%_%ARCHIVE_NUM%.rar" "%SOURCE_PATH%"
echo.
echo Архив создан.
В этом примере мы выполняем следующие действия:
date /t для получения текущей даты. Обратите внимание, что формат вывода date /t сильно зависит от региональных настроек Windows (локали). Это может быть Пт 20.10.2006 или 20.10.2006 Пт, или даже 20/10/2006.echo. | findstr ... && (...) || (...): Эта конструкция позволяет нам гибко определить, какой токен из вывода date /t соответствует дню месяца. Мы проверяем, содержит ли первый токен день недели (например, "Пн", "Вт" и т.д.). Если да, то день месяца будет во втором токене; в противном случае – в первом. Это обеспечивает некоторую кросс-локальную совместимость.set /A current_day = 100%current_day_raw% %% 100: Это важный "хак" для CMD. Команда set /A по умолчанию интерпретирует числа, начинающиеся с нуля (например, "05"), как восьмеричные. Чтобы избежать ошибок, мы преобразуем "05" в "5". Мы делаем это, добавляя "100" к числу (получаем "10005"), а затем берем остаток от деления на 100 (получаем "5").set /A N=5: Определяем желаемое количество цикличных архивов.set /A ARCHIVE_NUM=(%current_day% %% N) + 1: Выполняем операцию деления по модулю, используя преобразованный день месяца. Результат также корректируем добавлением + 1.Важно помнить, что это решение будет давать цикличный номер архива в пределах месяца. Например, если N=5, то 1-го числа месяца будет архив №2, 2-го – №3, 3-го – №4, 4-го – №5, 5-го – №1, 6-го – №2 и так далее (поскольку (1%5)+1 = 2, (2%5)+1 = 3 и т.д., (5%5)+1 = 1). Это отличается от исходной задачи, где цикличность должна быть по дням с начала эпохи, охватывая многие годы.
Мы рассмотрели различные подходы к получению цикличного номера архива по дате в среде Windows. Хотя чистый CMD имеет ограничения для сложных операций с датами, использование вспомогательных скриптов на VBScript или PowerShell позволяет легко реализовать требуемую логику деления по модулю. Выбор конкретного метода зависит от ваших предпочтений и требований к совместимости с версиями Windows.
Для универсального и точного решения, соответствующего алгоритму (количество_дней_с_базовой_даты % N) + 1, рекомендуем использовать PowerShell или VBScript, которые могут быть бесшовно интегрированы в ваши BAT-файлы. Эти методы обеспечивают надежность и точность расчетов дат.
Использование только дня месяца в чистом CMD может быть простым решением для некоторых сценариев, но оно не полностью соответствует исходной задаче о подсчете дней от фиксированной даты и обеспечивает цикличность только в пределах одного месяца. Будьте внимательны к этому ограничению при выборе данного метода.