Как программно скопировать файл или папку на встроенном языке 1С

· 9 мин

Содержание

Чтобы скопировать файл в 1С программно, хватает одной строки: КопироватьФайл("C:\Обмен\in.xml", "D:\Архив\in.xml"). Метод глобального контекста, работает на клиенте и на сервере, файл-приёмник с тем же именем перезаписывается. Для больших файлов, передачи между клиентом и сервером и копирования целых папок нужны другие методы: двоичные данные, файловые потоки, COM-объект Scripting.FileSystemObject или системные утилиты.

В клиентском коде новых конфигураций вместо синхронного метода используют КопироватьФайлАсинх (платформа 8.3.18 и выше). Любой вызов оборачивайте в Попытка: занятый файл или отсутствие прав прерывают операцию исключением.

Где выполняется код и что проверить заранее

Главная ловушка при копировании файлов в 1С — не синтаксис, а место выполнения кода. Путь C:\Обмен в серверной процедуре указывает на диск сервера 1С, а не на компьютер пользователя.

🖥️ Файловая база, тонкий клиент. Клиентский и серверный код выполняются на одном компьютере. Пути одинаковые, права — права пользователя Windows.

🏢 Клиент-серверная база . Код с директивой &НаСервере выполняется рабочим процессом кластера. Он работает под учётной записью службы сервера 1С (по умолчанию USR1CV8). Именно этой учётной записи нужны права на чтение источника и запись в каталог-приёмник, включая сетевые папки.

🌐 Веб-клиент. Прямого доступа к файловой системе нет. Нужно расширение работы с файлами и только асинхронные методы.

Перед запуском проверьте ещё три вещи:

  1. Безопасный режим. Внешние обработки и расширения в безопасном режиме не могут работать с файлами. Код выдаст исключение о запрете операции.
  2. Профили безопасности кластера. Если администратор их включил, доступ к каталогам разрешается явно.
  3. Сетевые пути. Указывайте UNC-путь \\Сервер\Обмен\, а не подключённый диск Z:\. У службы сервера 1С подключённых дисков пользователя нет.

Быстрая проверка места выполнения: вызовите в процедуре КаталогВременныхФайлов() и выведите результат через Сообщить. Путь сразу покажет, на чьём компьютере работает код.

КопироватьФайл: базовый способ копирования файла в 1С

Синтаксис: КопироватьФайл(ИмяФайлаИсточника, ИмяФайлаПриемника). Оба параметра — полные пути с именем файла. Указать вторым параметром только каталог нельзя: имя приёмника нужно передать целиком.

&НаСервере
Процедура СкопироватьВыгрузку()

    Источник = "\\FS01\Обмен\выгрузка.xml";
    КаталогАрхива = "\\FS01\Архив\" + Формат(ТекущаяДатаСеанса(), "ДФ=yyyyMMdd") + "\";

    ФайлИсточника = Новый Файл(Источник);
    Если Не ФайлИсточника.Существует() Тогда
        ВызватьИсключение "Файл не найден: " + Источник;
    КонецЕсли;

    // Каталог-приёмник КопироватьФайл сам не создаёт
    СоздатьКаталог(КаталогАрхива);

    КопироватьФайл(Источник, КаталогАрхива + ФайлИсточника.Имя);

КонецПроцедуры

Что важно знать про метод:

  • 📁 Каталог-приёмник должен существовать. СоздатьКаталог создаёт и вложенные папки, а на существующем каталоге ошибку не выдаёт.
  • ♻️ Перезапись без вопросов. Файл с тем же именем заменяется. Если старую версию нужно сохранить, проверьте Новый Файл(Приемник).Существует() и добавьте к имени суффикс.
  • 🔒 Только чтение. Если у файла-приёмника стоит атрибут «только чтение», копирование упадёт с исключением. Снимите атрибут: ФайлПриемника.УстановитьТолькоЧтение(Ложь).
  • 🔁 Перемещение. Для переноса без копии есть ПереместитьФайл(Источник, Приемник), для удаления — УдалитьФайлы(Путь).

Как скопировать папку целиком

Отдельного метода копирования каталога во встроенном языке нет. Обходим дерево через НайтиФайлы с флагом рекурсии и воссоздаём структуру:

&НаСервере
Процедура СкопироватьКаталог(Знач Источник, Знач Приемник)

    Разделитель = ПолучитьРазделительПути();
    Если Не СтрЗаканчиваетсяНа(Источник, Разделитель) Тогда
        Источник = Источник + Разделитель;
    КонецЕсли;
    Если Не СтрЗаканчиваетсяНа(Приемник, Разделитель) Тогда
        Приемник = Приемник + Разделитель;
    КонецЕсли;

    СоздатьКаталог(Приемник);
    ДлинаИсточника = СтрДлина(Источник);

    Для Каждого Элемент Из НайтиФайлы(Источник, "*", Истина) Цикл
        ОтносительныйПуть = Сред(Элемент.ПолноеИмя, ДлинаИсточника + 1);
        Если Элемент.ЭтоКаталог() Тогда
            СоздатьКаталог(Приемник + ОтносительныйПуть);
        Иначе
            СоздатьКаталог(Приемник + Сред(Элемент.Путь, ДлинаИсточника + 1));
            КопироватьФайл(Элемент.ПолноеИмя, Приемник + ОтносительныйПуть);
        КонецЕсли;
    КонецЦикла;

КонецПроцедуры

ПолучитьРазделительПути() делает код переносимым: на сервере под Linux разделитель — /. Свойство Путь у объекта Файл возвращает каталог файла с завершающим разделителем, поэтому повторный вызов СоздатьКаталог для уже созданной папки безопасен.

Копирование через двоичные данные и буфер в памяти

Объект ДвоичныеДанные читает файл в память целиком. Это удобно, когда файл нужно не просто скопировать, а передать: с сервера на клиент, в реквизит типа ХранилищеЗначения, во вложение письма или в HTTP-запрос.

Данные = Новый ДвоичныеДанные("C:\Шаблоны\договор.docx");
Данные.Записать("C:\Документы\договор_125.docx");

Две строки дают ту же копию, что и КопироватьФайл, но с возможностью вмешаться между чтением и записью: посчитать хеш, проверить размер Данные.Размер(), сохранить в базу.

Передача файла между сервером и клиентом

Классический сценарий: на сервере сформировали файл, пользователю нужно сохранить его у себя. Прямого «копирования с сервера на клиент» нет, работает связка с временным хранилищем:

&НаСервере
Функция АдресФайлаОтчета()
    Данные = Новый ДвоичныеДанные("\\FS01\Отчеты\сверка.xlsx");
    Возврат ПоместитьВоВременноеХранилище(Данные, УникальныйИдентификатор);
КонецФункции

&НаКлиенте
Процедура СохранитьОтчет(Команда)
    Адрес = АдресФайлаОтчета();
    Данные = ПолучитьИзВременногоХранилища(Адрес);
    Данные.Записать("C:\Отчеты\сверка.xlsx");
КонецПроцедуры

Второй параметр ПоместитьВоВременноеХранилище — идентификатор формы. Данные живут, пока открыта форма, и не копятся в памяти сервера.

Буфер двоичных данных

БуферДвоичныхДанных — область памяти фиксированного размера, которую можно читать и менять побайтно. Сам по себе для копирования файла он не нужен, зато незаменим в связке с потоками: через него файл переносится порциями, а не целиком.

ДвоичныеДанные держат весь файл в оперативной памяти. Копия файла на 2–3 ГБ таким способом на сервере может съесть память рабочего процесса и замедлить работу всех пользователей. Для крупных файлов берите КопироватьФайл или потоки.

Потоковое копирование больших файлов

Файловые потоки появились в платформе 8.3.9. Они читают и пишут файл частями, поэтому расход памяти не зависит от размера файла. Самый короткий вариант — метод КопироватьВ:

&НаСервере
Процедура СкопироватьПотоком(Источник, Приемник)

    ПотокЧтения = ФайловыеПотоки.ОткрытьДляЧтения(Источник);
    ПотокЗаписи = ФайловыеПотоки.Создать(Приемник);
    Попытка
        ПотокЧтения.КопироватьВ(ПотокЗаписи, 1048576); // буфер 1 МБ
    Исключение
        ПотокЧтения.Закрыть();
        ПотокЗаписи.Закрыть();
        ВызватьИсключение;
    КонецПопытки;
    ПотокЧтения.Закрыть();
    ПотокЗаписи.Закрыть();

КонецПроцедуры

Если нужен контроль над каждой порцией — показать прогресс, остановиться по флагу, посчитать контрольную сумму на лету, — читайте вручную через буфер:

РазмерБуфера = 1048576;
Буфер = Новый БуферДвоичныхДанных(РазмерБуфера);
Всего = ПотокЧтения.Размер();
Скопировано = 0;

Прочитано = ПотокЧтения.Прочитать(Буфер, 0, РазмерБуфера);
Пока Прочитано > 0 Цикл
    ПотокЗаписи.Записать(Буфер, 0, Прочитано);
    Скопировано = Скопировано + Прочитано;
    // здесь можно обновить прогресс: Скопировано / Всего * 100
    Прочитано = ПотокЧтения.Прочитать(Буфер, 0, РазмерБуфера);
КонецЦикла;

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

Буфер от 64 КБ до нескольких мегабайт — разумный диапазон. Меньше — много обращений к диску, больше — выигрыша по скорости обычно нет.

COM-объект и системные утилиты

На Windows можно поручить копирование самой операционной системе. Это полезно, когда нужно скопировать папку с сотнями файлов или продолжать с места обрыва по сети.

Scripting.FileSystemObject

FSO = Новый COMОбъект("Scripting.FileSystemObject");
FSO.CopyFile("C:\Обмен\*.xml", "D:\Архив\", Истина);
FSO.CopyFolder("C:\Обмен\Фото", "D:\Архив\Фото", Истина);

Третий параметр Истина разрешает перезапись. В отличие от КопироватьФайл, CopyFile принимает маску и каталог-приёмник с завершающим \.

robocopy через КомандаСистемы или ЗапуститьПриложение

Команда = "robocopy ""\\FS01\Обмен"" ""D:\Архив\Обмен"" /E /R:3 /W:5";
ЗапуститьПриложение(Команда, , Истина);

Ключи /E — с подкаталогами, включая пустые, /R:3 — три повтора при ошибке, /W:5 — пауза 5 секунд между повторами. Третий параметр ЗапуститьПриложение со значением Истина заставляет 1С дождаться окончания команды.

СпособПлюсыОграничения
Scripting.FileSystemObjectМаски, копирование папок одной строкойТолько Windows, нет в веб-клиенте
robocopyПовторы, докачка, журнал копированияТолько Windows, код возврата нужно разбирать отдельно
cp -r на Linux-сервереВстроен в системуПрава пользователя службы, другой синтаксис путей

COM и запуск внешних программ на сервере 1С выполняются от имени службы. В безопасном режиме и при включённых профилях безопасности они запрещены. Для типовых конфигураций на поддержке встроенные методы надёжнее.

Обработка ошибок при копировании

Копирование падает по понятным причинам: файла нет, нет прав, файл открыт другой программой, закончилось место. Без перехвата пользователь увидит системное окно и прерванную операцию. Оборачивайте вызов в Попытка и пишите подробности в журнал регистрации:

&НаСервере
Функция СкопироватьБезопасно(Источник, Приемник)

    Попытка
        КопироватьФайл(Источник, Приемник);
    Исключение
        Подробно = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке());
        ЗаписьЖурналаРегистрации("Копирование файлов",
            УровеньЖурналаРегистрации.Ошибка, , ,
            СтрШаблон("%1 -> %2
            |%3", Источник, Приемник, Подробно));
        Возврат Ложь;
    КонецПопытки;

    // Проверка результата: файл есть и размер совпадает
    ФайлИсточника = Новый Файл(Источник);
    ФайлПриемника = Новый Файл(Приемник);
    Возврат ФайлПриемника.Существует()
        И ФайлПриемника.Размер() = ФайлИсточника.Размер();

КонецФункции

Пользователю показывайте КраткоеПредставлениеОшибки, в журнал пишите подробное. Для критичных файлов — выгрузок в банк, резервных копий — сравнивайте не только размер, но и хеш:

Хеш = Новый ХешированиеДанных(ХешФункция.MD5);
Хеш.ДобавитьФайл(Приемник);
КонтрольнаяСумма = Хеш.ХешСумма;
Диагностика: выберите симптом

Асинхронное копирование и фоновые задания

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

КопироватьФайлАсинх (платформа 8.3.18 и выше)

&НаКлиенте
Асинх Процедура СкопироватьНаКлиенте(Команда)
    Попытка
        Ждать КопироватьФайлАсинх("C:\Обмен\in.xml", "C:\Архив\in.xml");
        ПоказатьПредупреждение(, "Файл скопирован");
    Исключение
        ПоказатьПредупреждение(, КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
    КонецПопытки;
КонецПроцедуры

Код читается как синхронный, но не блокирует интерфейс. Ошибку ловит обычная Попытка вокруг Ждать.

НачатьКопированиеФайла (для платформ до 8.3.18)

&НаКлиенте
Процедура СкопироватьСОповещением(Команда)
    Оповещение = Новый ОписаниеОповещения("ПослеКопирования", ЭтотОбъект);
    НачатьКопированиеФайла(Оповещение, "C:\Обмен\in.xml", "C:\Архив\in.xml");
КонецПроцедуры

&НаКлиенте
Процедура ПослеКопирования(СкопированныйФайл, ДополнительныеПараметры) Экспорт
    Сообщить("Готово: " + СкопированныйФайл);
КонецПроцедуры

Копирование в фоновом задании

Если нужно скопировать тысячи файлов или большой архив на сервере, вынесите работу в фоновое задание. Пользователь продолжит работать, а копирование пойдёт в отдельном сеансе.

Параметры = Новый Массив;
Параметры.Добавить("\\FS01\Обмен");
Параметры.Добавить("\\FS02\Архив\Обмен");
ФоновыеЗадания.Выполнить("РаботаСФайламиСервер.СкопироватьКаталог",
    Параметры, , "Копирование каталога обмена");

Процедура должна быть экспортной и лежать в общем серверном модуле. В конфигурациях на БСП для этого удобнее модуль ДлительныеОперации: он сам показывает пользователю ожидание и возвращает результат. Для регулярного копирования по расписанию заведите регламентное задание с той же процедурой.

Фоновое задание выполняется на сервере и от имени службы 1С. Все правила о путях и правах из первого раздела действуют и здесь.

Какой метод копирования выбрать

Точные цифры скорости зависят от диска, сети и антивируса, поэтому ориентируйтесь на характер задачи, а не на замеры с чужого сервера.

МетодКогда подходитПамятьСкоростьРиски
КопироватьФайлОдин файл, известные путиМинимумВысокая, копирует системаНет каталогов, нет прогресса
ДвоичныеДанныеНужно передать или сохранить файл в базеВесь файл в памятиВысокая на малых файлахНехватка памяти на файлах в гигабайты
ФайловыйПоток + КопироватьВБольшие файлы на сервереРазмер буфераСопоставима с КопироватьФайлНужно не забыть закрыть потоки
Поток с ручным буферомПрогресс, отмена, хеш на летуРазмер буфераЧуть ниже из-за циклаОшибка с длиной последней порции
FileSystemObject, robocopyПапки, маски, повторы по сетиВне 1СВысокаяТолько Windows, запрет в безопасном режиме
КопироватьФайлАсинхКлиентский код, веб-клиентМинимумКак у КопироватьФайлТребует платформу 8.3.18 и выше

Простое правило: один файл — КопироватьФайл или его асинхронный вариант, передача между клиентом и сервером — ДвоичныеДанные и временное хранилище, файлы больше сотни мегабайт — потоки, папки по расписанию — фоновое или регламентное задание.

Частые вопросы

Можно ли скопировать файл в 1С, если он открыт в Excel?
Зависит от того, как Excel открыл файл. Если программа держит исключительную блокировку, копирование завершится исключением. Закройте файл или повторите попытку позже в цикле с паузой.
Как скопировать файл с компьютера пользователя на сервер 1С?
Поместите файл во временное хранилище на клиенте асинхронным методом помещения файла, затем на сервере получите ДвоичныеДанные через ПолучитьИзВременногоХранилища и запишите методом Записать.
Работает ли КопироватьФайл на сервере под Linux?
Да, метод кроссплатформенный. Используйте пути в формате Linux и ПолучитьРазделительПути(). COM-объекты и robocopy там недоступны.
Как скопировать файл с новым именем, если такой уже есть?
Проверьте Новый Файл(Приемник).Существует() и добавьте к имени суффикс, например дату и время через Формат(ТекущаяДата(), “ДФ=yyyyMMdd_HHmmss”).
Сохраняет ли КопироватьФайл дату изменения файла?
Не полагайтесь на это. Если дата важна, запомните ее через ПолучитьВремяИзменения() у источника и установите копии методом УстановитьВремяИзменения() объекта Файл.