Как найти подстроку в 1С 8.3: СтрНайти, Сред, ПОДОБНО и регулярные выражения

· 11 мин

Содержание

Найти значение в строке 1С проще всего функцией СтрНайти(Строка, Подстрока): она вернёт номер символа, с которого начинается первое вхождение, или 0, если ничего не нашлось. Нумерация с единицы, регистр букв учитывается. Для платформ до 8.3.6 то же самое делает старая Найти(Строка, Подстрока).

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

Найти() и СтрНайти(): позиция подстроки

Обе функции отвечают на один вопрос: «где в строке начинается этот кусок текста». Разница в возможностях.

ФункцияС какой платформыЧто умеет
Найти(Строка, Подстрока)с 8.0, оставлена для совместимоститолько первое вхождение, только слева направо
СтрНайти(Строка, Подстрока, Направление, НачальнаяПозиция, НомерВхождения)с 8.3.6направление, старт с любой позиции, N-е вхождение

Простейшая проверка «содержит ли строка текст»:

Комментарий = "Оплата по счёту № 125 от 01.03, без НДС";

Если СтрНайти(Комментарий, "без НДС") > 0 Тогда
    СтавкаНДС = "Без НДС";
КонецЕсли;

Три вещи, на которых чаще всего спотыкаются:

  • 🔤 Регистр. СтрНайти("Без НДС", "без ндс") вернёт 0. Если регистр не важен, приведите обе строки к одному виду: СтрНайти(ВРег(Текст), ВРег(Искомое)).
  • 🔢 Позиция, а не булево. Результат — число. Пишите явное > 0, а не Если СтрНайти(...) Тогда: так условие читается однозначно.
  • ␣ Пробелы и невидимые символы. Текст из Excel или с сайта часто содержит неразрывный пробел Символы.НПП вместо обычного. На глаз строки одинаковые, а поиск даёт 0. Перед сравнением прогоните строку через СокрЛП() и замените Символы.НПП на пробел через СтрЗаменить().

Если нужно только количество вхождений, без позиций, есть СтрЧислоВхождений(Строка, Подстрока). А для начала и конца строки — СтрНачинаетсяС() и СтрЗаканчиваетсяНа() (тоже с 8.3.6). Они короче и понятнее, чем сравнивать позицию с единицей.

Поиск с конца строки и N-го вхождения

Отдельной функции «НайтиСКонца» в языке 1С нет. Поиск справа налево делает та же СтрНайти с параметром НаправлениеПоиска.СКонца. Позиция при этом всё равно отсчитывается слева, с первого символа.

Классический пример — достать имя файла из полного пути:

Путь = "C:\Обмен\Выгрузка\orders_2025.xml";

ПозицияСлэша = СтрНайти(Путь, "\", НаправлениеПоиска.СКонца);
ИмяФайла = Сред(Путь, ПозицияСлэша + 1);   // "orders_2025.xml"

Если слэша нет, ПозицияСлэша будет 0, а Сред(Путь, 1) вернёт всю строку. Для имени файла это как раз правильное поведение.

Для путей к файлам есть объект Файл: Новый Файл(Путь).Имя даёт имя с расширением, .ИмяБезРасширения и .Расширение — части. Ручной разбор нужен, когда в строке не путь, а, например, Склад/Секция/Ячейка.

Второе, третье и последнее вхождение

Пятый параметр СтрНайти — номер вхождения. Найти второй символ ; в строке:

Строка = "Иванов;Петров;Сидоров;Козлов";
Позиция = СтрНайти(Строка, ";", НаправлениеПоиска.СНачала, 1, 2);   // 14

С направлением СКонца номер вхождения считается справа: СтрНайти(Строка, ";", НаправлениеПоиска.СКонца, , 2) найдёт предпоследнюю точку с запятой.

Все вхождения подряд

Когда нужны все позиции, двигайте начальную позицию за найденный фрагмент:

Текст = "ТОВ-001, ТОВ-002, услуга, ТОВ-017";
Искомое = "ТОВ-";
Позиции = Новый Массив;

Старт = 1;
Пока Старт <= СтрДлина(Текст) Цикл
    Позиция = СтрНайти(Текст, Искомое, НаправлениеПоиска.СНачала, Старт);
    Если Позиция = 0 Тогда
        Прервать;
    КонецЕсли;
    Позиции.Добавить(Позиция);
    Старт = Позиция + СтрДлина(Искомое);
КонецЦикла;
// Позиции: 1, 10, 27

Проверка Старт <= СтрДлина(Текст) нужна: если передать начальную позицию за пределами строки, платформа выдаст ошибку о недопустимом значении параметра, а не вернёт ноль.

Если платформа старше 8.3.6

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

Вырезаем найденное: Лев(), Прав(), Сред()

Позиция сама по себе редко нужна. Обычно из строки надо достать кусок: номер, дату, код. Для этого три функции.

ФункцияЧто возвращаетПример для "Счет № 125 от 01.03"
Лев(Строка, N)первые N символовЛев(Стр, 4) → "Счет"
Прав(Строка, N)последние N символовПрав(Стр, 5) → "01.03"
Сред(Строка, Начало, Длина)N символов с позиции; без длины — до концаСред(Стр, 8, 3) → "125"

Жёсткие позиции, как в таблице, годятся только для строк фиксированного формата. В жизни номер бывает и 5, и 12500. Надёжнее искать границы и считать длину от них.

Универсальная функция «текст между двумя маркерами»:

Функция ЗначениеМежду(Текст, Начало, Конец)

    Поз1 = СтрНайти(Текст, Начало);
    Если Поз1 = 0 Тогда
        Возврат "";
    КонецЕсли;

    Старт = Поз1 + СтрДлина(Начало);
    Если Старт > СтрДлина(Текст) Тогда
        Возврат "";
    КонецЕсли;

    Поз2 = СтрНайти(Текст, Конец, НаправлениеПоиска.СНачала, Старт);
    Если Поз2 = 0 Тогда
        Возврат СокрЛП(Сред(Текст, Старт));
    КонецЕсли;

    Возврат СокрЛП(Сред(Текст, Старт, Поз2 - Старт));

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

// ЗначениеМежду("Счет № 125 от 01.03", "№", "от") → "125"

Если конечный маркер не найден, функция возвращает всё до конца строки. Нужно иначе — верните пустую строку, но решите это заранее, а не после жалобы пользователя.

Если длину для Сред() считаете как разность позиций, проверьте, что Поз2 > Старт. Иначе вместо значения получите пустую строку и не сразу поймёте почему.

Поиск по маске: ПОДОБНО в запросе

В самом встроенном языке оператора «похоже на шаблон» нет. Маски живут в языке запросов: оператор ПОДОБНО проверяет поле таблицы по шаблону. Так ищут товар по куску наименования, контрагента по части ИНН, документы по префиксу номера.

Спецсимволы шаблона:

СимволЗначениеПримерСовпадёт с
%любое число любых символов, в том числе ноль"%кабель%"«Кабель ВВГ», «Медный кабель»
_ровно один любой символ"ТОВ-__1"«ТОВ-001», «ТОВ-AB1»
[...]один символ из перечня или диапазона"[0-9]%"всё, что начинается с цифры
[^...]один символ не из перечня"[^0-9]%"всё, что начинается не с цифры

Запрос «наименование содержит текст из поля поиска»:

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|    Номенклатура.Ссылка,
|    Номенклатура.Наименование
|ИЗ
|    Справочник.Номенклатура КАК Номенклатура
|ГДЕ
|    Номенклатура.Наименование ПОДОБНО &Шаблон СПЕЦСИМВОЛ ""~""";

Запрос.УстановитьПараметр("Шаблон", "%" + ЭкранироватьДляПодобно(ТекстПоиска) + "%");
Выборка = Запрос.Выполнить().Выбрать();

Экранирование пользовательского ввода

Это тот приём, который пропускают чаще всего. Пользователь вводит «100%» или «тип_А», и символы % и _ внезапно становятся частью шаблона. Поиск по «тип_А» найдёт и «типБА». Защита — СПЕЦСИМВОЛ и функция, которая ставит его перед служебными символами:

Функция ЭкранироватьДляПодобно(Знач Текст)

    Текст = СтрЗаменить(Текст, "~", "~~");   // сначала сам спецсимвол
    Текст = СтрЗаменить(Текст, "%", "~%");
    Текст = СтрЗаменить(Текст, "_", "~_");
    Текст = СтрЗаменить(Текст, "[", "~[");
    Текст = СтрЗаменить(Текст, "]", "~]");
    Текст = СтрЗаменить(Текст, "^", "~^");
    Возврат Текст;

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

Порядок замен важен: если заменить ~ последним, вы удвоите уже поставленные экранирующие символы.

В типовых конфигурациях на БСП то же делает готовая функция ОбщегоНазначения.СформироватьСтрокуДляПоискаВЗапросе(). Если она есть в вашей конфигурации, берите её, а не свою.

Учёт регистра в ПОДОБНО определяется СУБД и её настройками сортировки, поэтому одинаковый запрос в файловой базе и на сервере может вести себя по-разному. Проверьте на своей базе, прежде чем обещать пользователям «поиск без учёта регистра».

Регулярные выражения в 1С

Регулярки нужны, когда шаблон сложнее «начинается с» и «содержит»: найти все ИНН в тексте письма, проверить формат e-mail, вытащить суммы с копейками. Есть два пути.

Встроенные функции платформы 8.3.23 и новее

Начиная с версии 8.3.23 в языке есть набор функций для регулярных выражений:

  • ✅ СтрСоответствуетШаблону(Строка, Шаблон) — проверка, что строка подходит под выражение, возвращает Истина или Ложь;
  • 🔎 СтрНайтиПоРегулярномуВыражению() — первое совпадение;
  • 📚 СтрНайтиВсеПоРегулярномуВыражению() — все совпадения;
  • ✏️ СтрЗаменитьПоРегулярномуВыражению() — замена по шаблону;
  • ✂️ СтрРазделитьПоРегулярномуВыражению() — разбиение строки.

Проверка ИНН на формат (10 цифр у организации, 12 у ИП и физлица):

Если Не СтрСоответствуетШаблону(ИНН, "^(\d{10}|\d{12})$") Тогда
    ОбщегоНазначения.СообщитьПользователю("ИНН должен содержать 10 или 12 цифр");
КонецЕсли;

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

COM-объект VBScript.RegExp на Windows

На старых платформах и в базах с режимом совместимости регулярки берут у Windows:

Текст = "Поставщик ИНН 7701234567, покупатель ИНН 500100732259";

RegExp = Новый COMОбъект("VBScript.RegExp");
RegExp.Pattern = "\b\d{10}(\d{2})?\b";
RegExp.Global = Истина;      // искать все совпадения, а не первое
RegExp.IgnoreCase = Истина;

Совпадения = RegExp.Execute(Текст);
Для Индекс = 0 По Совпадения.Count - 1 Цикл
    Совпадение = Совпадения.Item(Индекс);
    // FirstIndex считается с нуля, в 1С позиции с единицы
    Сообщить(СтрШаблон("Позиция %1: %2", Совпадение.FirstIndex + 1, Совпадение.Value));
КонецЦикла;

Ограничения этого способа:

  • 🪟 Работает только там, где выполняется код и есть Windows. На сервере 1С под Linux и в веб-клиенте объекта нет.
  • 📉 Microsoft объявила VBScript устаревшим компонентом, в будущих версиях Windows его могут убрать из установки по умолчанию. Для нового кода на свежей платформе берите встроенные функции.
  • 🐢 Создание COM-объекта не бесплатное. Создайте его один раз до цикла, а не на каждой строке.

Для простой проверки формата без COM есть ещё старый приём через XDTO: тип строки с фасетом pattern и проверка значения на соответствие типу. Он громоздкий, сейчас его имеет смысл держать только в уже существующем коде.

Поиск в больших текстах без тормозов

На строке в сто символов любой способ работает мгновенно. Проблемы начинаются на выгрузках в мегабайты, логах и тексте из файлов. Что реально ускоряет:

  1. Двигайте позицию, а не режьте строку. Код вида Текст = Сред(Текст, Позиция + 1) в цикле каждый раз копирует остаток строки. На 10 МБ и тысяче совпадений это заметные секунды. СтрНайти с параметром НачальнаяПозиция ничего не копирует.
  2. Не перебирайте строки через СтрПолучитьСтроку(). Функция при каждом вызове ищет N-ю строку от начала текста, и цикл по всем строкам становится квадратичным. Разбейте текст один раз: Строки = СтрРазделить(Текст, Символы.ПС), и идите по массиву.
  3. Читайте файл построчно. Для больших файлов ЧтениеТекста и ПрочитатьСтроку() в цикле не держат весь текст в памяти.
  4. Собирайте результат в массив. Склейка Итог = Итог + Фрагмент в цикле создаёт новую строку на каждом шаге. Добавляйте фрагменты в Массив и в конце вызывайте СтрСоединить(Массив, Разделитель).
  5. Простое — простыми средствами. Проверку «содержит ли» делайте через СтрНайти, регулярку оставьте для настоящих шаблонов.
  6. Много искомых значений — через Соответствие. Если нужно проверить, входит ли код из каждой строки в список из 5 000 кодов, положите список в Соответствие и проверяйте Список[Код] <> Неопределено, вместо 5 000 вызовов СтрНайти.

Для данных в базе действуют свои правила. Шаблон "%текст%" в ПОДОБНО не использует индекс, и СУБД просматривает всю таблицу. Шаблон с фиксированным началом "текст%" обычно работает быстрее. Если пользователям нужен поиск по словам во многих объектах, правильный инструмент — полнотекстовый поиск платформы, а не ПОДОБНО по десятку таблиц.

Готовые решения типовых задач

Разобрать строку «ключ=значение»

Такие строки приходят из обменов, настроек, комментариев:

Строка = "ИНН=7701234567; КПП=770101001; Банк=Сбербанк";
Реквизиты = Новый Соответствие;

Для Каждого Пара Из СтрРазделить(Строка, ";", Ложь) Цикл
    Поз = СтрНайти(Пара, "=");
    Если Поз = 0 Тогда
        Продолжить;
    КонецЕсли;
    Ключ = СокрЛП(Лев(Пара, Поз - 1));
    Значение = СокрЛП(Сред(Пара, Поз + 1));
    Реквизиты.Вставить(Ключ, Значение);
КонецЦикла;

// Реквизиты["КПП"] → "770101001"

Третий параметр Ложь в СтрРазделить выбрасывает пустые элементы, если строка закончилась на ;.

Оставить в строке только цифры

Номер телефона, ИНН из «грязного» поля, сумма с пробелами:

Функция ТолькоЦифры(Строка)
    Цифры = Новый Массив;
    Для Номер = 1 По СтрДлина(Строка) Цикл
        Символ = Сред(Строка, Номер, 1);
        Если СтрНайти("0123456789", Символ) > 0 Тогда
            Цифры.Добавить(Символ);
        КонецЕсли;
    КонецЦикла;
    Возврат СтрСоединить(Цифры);
КонецФункции

// ТолькоЦифры("+7 (912) 345-67-89") → "79123456789"

Найти строку в таблице значений по части текста

ТаблицаЗначений.НайтиСтроки() ищет только по точному совпадению. Для поиска по фрагменту остаётся перебор:

Найденные = Новый Массив;
Искомое = ВРег(ТекстПоиска);
Для Каждого СтрокаТЗ Из Таблица Цикл
    Если СтрНайти(ВРег(СтрокаТЗ.Наименование), Искомое) > 0 Тогда
        Найденные.Добавить(СтрокаТЗ);
    КонецЕсли;
КонецЦикла;

ВРег(ТекстПоиска) вынесен за цикл: незачем пересчитывать его на каждой строке.

Перед тем как отдать код поиска в рабочую базу
Выполнено: 0 / 6

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

Чем СтрНайти отличается от Найти?
Найти ищет только первое вхождение слева направо. СтрНайти появилась в 8.3.6 и умеет искать с конца, с заданной позиции и N-е вхождение. Для нового кода используйте СтрНайти.
Как проверить, что строка содержит хотя бы одно слово из списка?
Положите слова в массив и пройдите циклом с СтрНайти(ВРег(Текст), ВРег(Слово)) > 0, выходя по первому совпадению. Для сотен слов удобнее одно регулярное выражение вида слово1|слово2|слово3.
Почему поиск не находит текст, который явно есть в строке?
Чаще всего мешает регистр, неразрывный пробел или буква из другой раскладки: латинская «С» вместо кириллической выглядит так же. Сравните коды символов через КодСимвола(Сред(Строка, N, 1)).
Можно ли искать в строке без программирования?
Да. В списках и табличных частях 1С работает поиск Ctrl+F по колонкам, в текстовых и табличных документах — поиск по тексту через то же сочетание клавиш. Код нужен, когда поиск идёт внутри обработки или обмена.
Как найти символ перевода строки?
Ищите Символы.ПС. В текстах из Windows строки часто разделены парой Символы.ВК + Символы.ПС, поэтому после разбиения по ПС уберите лишний ВК через СокрЛП().