Как найти подстроку в тексте средствами 1С 8.3
Содержание
Подстроку в тексте находит функция СтрНайти: она возвращает номер позиции, с которой начинается вхождение, или 0, если фрагмент не найден. Это рабочий инструмент для кода на встроенном языке. Если же искать нужно не в переменной, а в поле базы — по части наименования, по куску комментария — отбор строит оператор запроса ПОДОБНО.
Разберём оба пути плюс то, что сложнее: поиск по строке с учётом регистра и буквы «ё», поиск по маске через регулярные выражения и перебор всех вхождений подстроки сразу. Везде — короткий код, который можно скопировать и запустить. Платформа — 1С 8.3, интерфейс «Такси».
Сначала определитесь, где лежит текст: в памяти (переменная, реквизит открытого объекта) или в таблице базы. От этого зависит выбор средства.
СтрНайти для позиции, СтрЗаменить для замены, СтрЧислоВхождений для подсчёта.ПОДОБНО с шаблоном %текст%. Без обращения к базе через запрос перебирать весь справочник циклом нельзя.РегулярноеВыражение (8.3.23+) или COM-объект VBScript.RegExp на Windows.Администрирование.Функции встроенного языка: СтрНайти и СтрЗаменить
Основная функция — СтрНайти. Полная сигнатура:
СтрНайти(Строка, ПодстрокаПоиска, НаправлениеПоиска, НачальнаяПозиция, НомерВхождения)
Обязательны только первые два параметра. Остальные нужны, когда ищете с конца или не первое вхождение. Пример:
Текст = "Договор поставки № 125 от 10.03";
Поз = СтрНайти(Текст, "№");
Если Поз > 0 Тогда
Сообщить("Символ № найден на позиции " + Поз); // Поз = 17
Иначе
Сообщить("Не найдено");
КонецЕсли;
Позиция считается с единицы, а «не найдено» — это ноль. Поэтому условие всегда Поз > 0, а не Поз >= 0. Проверка >= 0 срабатывает всегда и ломает логику — одна из самых частых ошибок новичков.
Старая функция Найти(Строка, Подстрока) делает то же самое, но считается устаревшей. В новом коде пишите СтрНайти — у неё есть направление и начальная позиция, а Найти оставлена только для совместимости.
Рядом стоят ещё две функции, которые почти всегда идут в паре с поиском:
- 🔎
СтрЗаменить(Строка, Что, НаЧто)— заменяет все вхождения подстроки сразу, не только первое. - 🔢
СтрЧислоВхождений(Строка, Подстрока)— сколько раз фрагмент встречается в тексте (платформа 8.3.6 и новее). - ✂️
СтрДлина(Строка)— длина, пригодится, чтобы сдвигать позицию при переборе.
Исходный = "ООО Ромашка; ООО Василёк; ООО Клевер";
Чистый = СтрЗаменить(Исходный, "ООО ", "");
Сообщить(Чистый); // Ромашка; Василёк; Клевер
Сообщить(СтрЧислоВхождений(Исходный, "ООО")); // 3
Сравним три метода по назначению:
| Функция | Что делает | Что возвращает |
|---|---|---|
СтрНайти | ищет позицию вхождения | номер позиции или 0 |
СтрЗаменить | меняет все вхождения | новую строку |
СтрЧислоВхождений | считает вхождения | число (может быть 0) |
Если нужно только проверить, начинается или заканчивается строка на фрагмент, не вычисляйте позицию вручную — есть СтрНачинаетсяС и СтрЗаканчиваетсяНа. Они короче и читаются понятнее.
Регистр букв и «е» против «ё» при поиске
СтрНайти чувствительна к регистру: «Договор» и «договор» для неё — разные строки. Чтобы искать без учёта регистра, приведите обе строки к одному виду функцией ВРег (верхний регистр) или НРег (нижний):
Текст = "Поступление товаров и услуг";
Фрагмент = "ТОВАР";
Поз = СтрНайти(ВРег(Текст), ВРег(Фрагмент)); // Поз = 13, регистр не мешает
Если задача не «где», а «равны ли строки без учёта регистра» — не городите ВРег с обеих сторон, используйте СтрСравнить. Она сравнивает без учёта регистра и возвращает 0 при совпадении:
Если СтрСравнить("ИВАНОВ", "иванов") = 0 Тогда
// строки считаются одинаковыми
КонецЕсли;
Отдельная засада — буквы «е» и «ё». В коде встроенного языка это разные символы: СтрНайти("ёлка", "елка") вернёт 0. Если данные приходят от разных пользователей, где-то с «ё», где-то без, замените одну на другую перед сравнением:
Нормализованный = СтрЗаменить(НРег(Текст), "ё", "е");
В запросах ситуация другая: там сравнение строк зависит от настроек базы (collation), и по умолчанию оператор ПОДОБНО регистр, как правило, не различает. На это не стоит полагаться вслепую — поведение задаётся при создании базы, поэтому в коде встроенного языка регистр приводите явно.
Поиск подстроки в запросе через оператор ПОДОБНО
Когда нужно отобрать элементы справочника или строки документа по части текста, перебирать всю таблицу циклом нельзя — это долго и неправильно. Работает оператор запроса ПОДОБНО с шаблоном. Два служебных символа:
%— любое количество любых символов (в том числе ноль);_— ровно один любой символ.
Найти всю номенклатуру, в наименовании которой есть «болт»:
ВЫБРАТЬ
Номенклатура.Ссылка,
Номенклатура.Наименование
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
Номенклатура.Наименование ПОДОБНО "%болт%"
Шаблон лучше передавать параметром, а не вклеивать в текст запроса. Тогда пользователь вводит фрагмент в поле, а код собирает маску:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ Ссылка, Наименование
|ИЗ Справочник.Номенклатура
|ГДЕ Наименование ПОДОБНО &Маска";
Запрос.УстановитьПараметр("Маска", "%" + СтрокаПоиска + "%");
Результат = Запрос.Выполнить().Выгрузить();
Два подводных камня в запросах:
- 🧱 Поле неограниченной длины (например, «Комментарий») перед
ПОДОБНОнужно обрезать до строки фиксированной длины:ВЫРАЗИТЬ(Комментарий КАК СТРОКА(500)). Иначе платформа выдаст ошибку, что по полю неограниченной длины условие не строится. - 🔣 Если сам искомый текст содержит
%или_, их надо экранировать, иначе они сработают как служебные. Задаётся символ экранирования:ПОДОБНО &Маска СПЕЦСИМВОЛ "\"— и в маске перед процентом ставится обратный слэш.
В языке запросов нет функции, которая вернёт номер позиции, как СтрНайти в коде. Для отбора по части строки есть именно ПОДОБНО. А чтобы вырезать кусок строки по известным координатам, пригодится ПОДСТРОКА(Строка, НачальнаяПозиция, Длина).
Поиск по маске: регулярные выражения
Когда искать надо не конкретный текст, а образец — телефон, ИНН, дату, артикул по формату — обычного СтрНайти мало. Здесь выручают регулярные выражения.
В платформе 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. Но Найти устарела, а у СтрНайти есть направление поиска и начальная позиция. В новом коде используйте только СтрНайти.Как искать с конца строки?
СтрНайти четвёртым аргументом НаправлениеПоиска.СКонца. Функция вернёт позицию последнего вхождения, а не первого.Можно ли одним вызовом найти любое из нескольких слов?
СтрНайти, либо используйте регулярное выражение с перечислением через |, например ошибка|сбой|отказ.Почему `ПОДОБНО` ничего не находит, хотя текст в наименовании есть?
ВЫРАЗИТЬ, либо в шаблоне забыты символы % по краям. Проверьте, что маска имеет вид "%текст%", а не просто "текст".Работают ли регулярные выражения на сервере под Linux?
VBScript.RegExp — нет, он только для Windows. Встроенный объект РегулярноеВыражение (платформа 8.3.23 и новее) работает независимо от операционной системы, в том числе на Linux.