Как найти подстроку в тексте средствами 1С 8.3

· 9 мин

Содержание

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

Разберём оба пути плюс то, что сложнее: поиск по строке с учётом регистра и буквы «ё», поиск по маске через регулярные выражения и перебор всех вхождений подстроки сразу. Везде — короткий код, который можно скопировать и запустить. Платформа — 1С 8.3, интерфейс «Такси».

Сначала определитесь, где лежит текст: в памяти (переменная, реквизит открытого объекта) или в таблице базы. От этого зависит выбор средства.

Где и как нужно искать текст?

Функции встроенного языка: СтрНайти и СтрЗаменить

Основная функция — СтрНайти. Полная сигнатура:

СтрНайти(Строка, ПодстрокаПоиска, НаправлениеПоиска, НачальнаяПозиция, НомерВхождения)

Обязательны только первые два параметра. Остальные нужны, когда ищете с конца или не первое вхождение. Пример:

Текст = "Договор поставки № 125 от 10.03";
Поз = СтрНайти(Текст, "№");
Если Поз > 0 Тогда
    Сообщить("Символ № найден на позиции " + Поз); // Поз = 17
Иначе
    Сообщить("Не найдено");
КонецЕсли;

Позиция считается с единицы, а «не найдено» — это ноль. Поэтому условие всегда Поз > 0, а не Поз >= 0. Проверка >= 0 срабатывает всегда и ломает логику — одна из самых частых ошибок новичков.

Старая функция Найти(Строка, Подстрока) делает то же самое, но считается устаревшей. В новом коде пишите СтрНайти — у неё есть направление и начальная позиция, а Найти оставлена только для совместимости.

Рядом стоят ещё две функции, которые почти всегда идут в паре с поиском:

  • 🔎 СтрЗаменить(Строка, Что, НаЧто) — заменяет все вхождения подстроки сразу, не только первое.
  • 🔢 СтрЧислоВхождений(Строка, Подстрока) — сколько раз фрагмент встречается в тексте (платформа 8.3.6 и новее).
  • ✂️ СтрДлина(Строка) — длина, пригодится, чтобы сдвигать позицию при переборе.
Исходный = "ООО Ромашка; ООО Василёк; ООО Клевер";
Чистый = СтрЗаменить(Исходный, "ООО ", "");
Сообщить(Чистый);                              // Ромашка; Василёк; Клевер
Сообщить(СтрЧислоВхождений(Исходный, "ООО"));  // 3

Сравним три метода по назначению:

ФункцияЧто делаетЧто возвращает
СтрНайтиищет позицию вхожденияномер позиции или 0
СтрЗаменитьменяет все вхожденияновую строку
СтрЧислоВхожденийсчитает вхождениячисло (может быть 0)

Если нужно только проверить, начинается или заканчивается строка на фрагмент, не вычисляйте позицию вручную — есть СтрНачинаетсяС и СтрЗаканчиваетсяНа. Они короче и читаются понятнее.

Регистр букв и «е» против «ё» при поиске

СтрНайти чувствительна к регистру: «Договор» и «договор» для неё — разные строки. Чтобы искать без учёта регистра, приведите обе строки к одному виду функцией ВРег (верхний регистр) или НРег (нижний):

Текст   = "Поступление товаров и услуг";
Фрагмент = "ТОВАР";
Поз = СтрНайти(ВРег(Текст), ВРег(Фрагмент)); // Поз = 13, регистр не мешает

Если задача не «где», а «равны ли строки без учёта регистра» — не городите ВРег с обеих сторон, используйте СтрСравнить. Она сравнивает без учёта регистра и возвращает 0 при совпадении:

Если СтрСравнить("ИВАНОВ", "иванов") = 0 Тогда
    // строки считаются одинаковыми
КонецЕсли;

Отдельная засада — буквы «е» и «ё». В коде встроенного языка это разные символы: СтрНайти("ёлка", "елка") вернёт 0. Если данные приходят от разных пользователей, где-то с «ё», где-то без, замените одну на другую перед сравнением:

Нормализованный = СтрЗаменить(НРег(Текст), "ё", "е");

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

Поиск подстроки в запросе через оператор ПОДОБНО

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

  • % — любое количество любых символов (в том числе ноль);
  • _ — ровно один любой символ.

Найти всю номенклатуру, в наименовании которой есть «болт»:

ВЫБРАТЬ
    Номенклатура.Ссылка,
    Номенклатура.Наименование
ИЗ
    Справочник.Номенклатура КАК Номенклатура
ГДЕ
    Номенклатура.Наименование ПОДОБНО "%болт%"

Шаблон лучше передавать параметром, а не вклеивать в текст запроса. Тогда пользователь вводит фрагмент в поле, а код собирает маску:

Запрос = Новый Запрос;
Запрос.Текст =
    "ВЫБРАТЬ Ссылка, Наименование
    |ИЗ Справочник.Номенклатура
    |ГДЕ Наименование ПОДОБНО &Маска";
Запрос.УстановитьПараметр("Маска", "%" + СтрокаПоиска + "%");
Результат = Запрос.Выполнить().Выгрузить();

Два подводных камня в запросах:

  1. 🧱 Поле неограниченной длины (например, «Комментарий») перед ПОДОБНО нужно обрезать до строки фиксированной длины: ВЫРАЗИТЬ(Комментарий КАК СТРОКА(500)). Иначе платформа выдаст ошибку, что по полю неограниченной длины условие не строится.
  2. 🔣 Если сам искомый текст содержит % или _, их надо экранировать, иначе они сработают как служебные. Задаётся символ экранирования: ПОДОБНО &Маска СПЕЦСИМВОЛ "\" — и в маске перед процентом ставится обратный слэш.

В языке запросов нет функции, которая вернёт номер позиции, как СтрНайти в коде. Для отбора по части строки есть именно ПОДОБНО. А чтобы вырезать кусок строки по известным координатам, пригодится ПОДСТРОКА(Строка, НачальнаяПозиция, Длина).

Поиск по маске: регулярные выражения

Когда искать надо не конкретный текст, а образец — телефон, ИНН, дату, артикул по формату — обычного СтрНайти мало. Здесь выручают регулярные выражения.

В платформе 8.3.23 и новее есть встроенный объект РегулярноеВыражение. Его плюс в том, что он не зависит от операционной системы и работает в том числе на сервере под Linux. Вы создаёте объект с шаблоном, затем проверяете строку на совпадение или получаете список найденных фрагментов. Точный состав методов и свойств смотрите в синтакс-помощнике конфигуратора по слову «регулярное» — там же готовые примеры.

Для платформ старше 8.3.23 или когда код точно исполняется на Windows, применяют COM-объект VBScript.RegExp. Он стабилен и хорошо документирован. Вытащим из текста все номера телефонов формата +7XXXXXXXXXX:

РегВыр = Новый COMОбъект("VBScript.RegExp");
РегВыр.Pattern = "\+7\d{10}"; // плюс, семёрка и десять цифр
РегВыр.Global = Истина;        // искать все совпадения, а не только первое
РегВыр.IgnoreCase = Истина;

Совпадения = РегВыр.Execute(ИсходныйТекст);
Для Каждого Совпадение Из Совпадения Цикл
    Сообщить("Телефон: " + Совпадение.Value);
КонецЦикла;

COM-объект VBScript.RegExp работает только на Windows (на клиенте или на Windows-сервере). На сервере под Linux он недоступен — там используйте встроенный объект РегулярноеВыражение. У COM-варианта позиция совпадения (FirstIndex) считается с нуля, а не с единицы, — это легко упустить.

Коротко про сам синтаксис шаблонов, он общий для обоих вариантов:

  • \d — цифра, \d{10} — ровно десять цифр подряд;
  • \w — буква, цифра или подчёркивание;
  • . — любой символ, .* — любой фрагмент;
  • ^ и $ — начало и конец строки;
  • [А-Яа-яЁё] — любая русская буква.

Если искомый фрагмент сам содержит служебные символы — точку, плюс, скобки, вопросительный знак, — их надо экранировать обратным слэшем. Точка . в шаблоне означает «любой символ», и чтобы найти именно точку в номере версии, пишите \.: шаблон \d+\.\d+\.\d+ поймает строку вида 8.3.23, а не любые три группы цифр через произвольные разделители.

Как найти все вхождения подстроки сразу

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

Текст    = "код 100, код 205, код 301";
Фрагмент = "код";
Позиции  = Новый Массив;

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

Сообщить("Найдено вхождений: " + Позиции.Количество()); // 3

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

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

Части = СтрРазделить("иванов@mail.ru;петров@mail.ru", ";", Ложь);
// Части[0] = "иванов@mail.ru", Части[1] = "петров@mail.ru"

А если нужно только количество вхождений без самих позиций — не пишите цикл вообще, вызовите СтрЧислоВхождений.

Проверьте себя
Что вернёт `СтрНайти("отчёт", "ОТЧЁТ")` без приведения регистра?
Чем заменить `Найти` в новом коде?
Какой символ в шаблоне `ПОДОБНО` означает «любое количество любых символов»?

Скорость поиска и типичные ошибки

Код работает — теперь про то, где он незаметно тормозит и ломается.

  • 🐢 ПОДОБНО "%текст%" не использует индекс. Ведущий % заставляет базу просматривать всю таблицу. На справочнике в несколько сотен строк это незаметно, на сотнях тысяч — заметно. Если % стоит только в конце ("болт%"), индекс по полю ещё помогает.
  • 📚 Большие объёмы текста — через полнотекстовый поиск. Перебирать СтрНайти тысячи длинных описаний в цикле медленно. Для таких задач в платформе есть механизм полнотекстового поиска: его индекс включается в разделе Администрирование и ищет по словам, а не по подстроке.
  • 🧮 Проверка >= 0 вместо > 0. СтрНайти возвращает 0 при неудаче, поэтому «найдено» — это строго > 0. С >= 0 код считает найденным всё подряд.
  • 🔠 Забыли про регистр. В коде СтрНайти различает большие и маленькие буквы. Приводите обе строки через ВРег либо сравнивайте через СтрСравнить.
  • 🪄 Служебные % и _ в тексте поиска. Если пользователь ищет «скидка 50%», процент сработает как маска. Экранируйте через СПЕЦСИМВОЛ.
  • 🧩 Поле неограниченной длины в ПОДОБНО. Без ВЫРАЗИТЬ(... КАК СТРОКА(N)) запрос упадёт с ошибкой.
Проверка перед запуском поиска
Выполнено: 0 / 6

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

Чем `Найти` отличается от `СтрНайти`?
Результат одинаковый — позиция вхождения или 0. Но Найти устарела, а у СтрНайти есть направление поиска и начальная позиция. В новом коде используйте только СтрНайти.
Как искать с конца строки?
Передайте в СтрНайти четвёртым аргументом НаправлениеПоиска.СКонца. Функция вернёт позицию последнего вхождения, а не первого.
Можно ли одним вызовом найти любое из нескольких слов?
Встроенной функции «найти одно из» нет. Либо проверяйте слова по очереди через СтрНайти, либо используйте регулярное выражение с перечислением через |, например ошибка|сбой|отказ.
Почему `ПОДОБНО` ничего не находит, хотя текст в наименовании есть?
Частая причина — поле неограниченной длины без ВЫРАЗИТЬ, либо в шаблоне забыты символы % по краям. Проверьте, что маска имеет вид "%текст%", а не просто "текст".
Работают ли регулярные выражения на сервере под Linux?
COM-объект VBScript.RegExp — нет, он только для Windows. Встроенный объект РегулярноеВыражение (платформа 8.3.23 и новее) работает независимо от операционной системы, в том числе на Linux.