Как найти подстроку в 1С 8.3: СтрНайти, Сред, ПОДОБНО и регулярные выражения
Содержание
Найти значение в строке 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)в цикле каждый раз копирует остаток строки. На 10 МБ и тысяче совпадений это заметные секунды.СтрНайтис параметромНачальнаяПозицияничего не копирует. - Не перебирайте строки через
СтрПолучитьСтроку(). Функция при каждом вызове ищет N-ю строку от начала текста, и цикл по всем строкам становится квадратичным. Разбейте текст один раз:Строки = СтрРазделить(Текст, Символы.ПС), и идите по массиву. - Читайте файл построчно. Для больших файлов
ЧтениеТекстаиПрочитатьСтроку()в цикле не держат весь текст в памяти. - Собирайте результат в массив. Склейка
Итог = Итог + Фрагментв цикле создаёт новую строку на каждом шаге. Добавляйте фрагменты вМассиви в конце вызывайтеСтрСоединить(Массив, Разделитель). - Простое — простыми средствами. Проверку «содержит ли» делайте через
СтрНайти, регулярку оставьте для настоящих шаблонов. - Много искомых значений — через Соответствие. Если нужно проверить, входит ли код из каждой строки в список из 5 000 кодов, положите список в
Соответствиеи проверяйтеСписок[Код] <> Неопределено, вместо 5 000 вызововСтрНайти.
Для данных в базе действуют свои правила. Шаблон "%текст%" в ПОДОБНО не использует индекс, и СУБД просматривает всю таблицу. Шаблон с фиксированным началом "текст%" обычно работает быстрее. Если пользователям нужен поиск по словам во многих объектах, правильный инструмент — полнотекстовый поиск платформы, а не ПОДОБНО по десятку таблиц.
Готовые решения типовых задач
Разобрать строку «ключ=значение»
Такие строки приходят из обменов, настроек, комментариев:
Строка = "ИНН=7701234567; КПП=770101001; Банк=Сбербанк";
Реквизиты = Новый Соответствие;
Для Каждого Пара Из СтрРазделить(Строка, ";", Ложь) Цикл
Поз = СтрНайти(Пара, "=");
Если Поз = 0 Тогда
Продолжить;
КонецЕсли;
Ключ = СокрЛП(Лев(Пара, Поз - 1));
Значение = СокрЛП(Сред(Пара, Поз + 1));
Реквизиты.Вставить(Ключ, Значение);
КонецЦикла;
// Реквизиты["КПП"] → "770101001"
Третий параметр Ложь в СтрРазделить выбрасывает пустые элементы, если строка закончилась на ;.
Оставить в строке только цифры
Номер телефона, ИНН из «грязного» поля, сумма с пробелами:
Функция ТолькоЦифры(Строка)
Цифры = Новый Массив;
Для Номер = 1 По СтрДлина(Строка) Цикл
Символ = Сред(Строка, Номер, 1);
Если СтрНайти("0123456789", Символ) > 0 Тогда
Цифры.Добавить(Символ);
КонецЕсли;
КонецЦикла;
Возврат СтрСоединить(Цифры);
КонецФункции
// ТолькоЦифры("+7 (912) 345-67-89") → "79123456789"
Найти строку в таблице значений по части текста
ТаблицаЗначений.НайтиСтроки() ищет только по точному совпадению. Для поиска по фрагменту остаётся перебор:
Найденные = Новый Массив;
Искомое = ВРег(ТекстПоиска);
Для Каждого СтрокаТЗ Из Таблица Цикл
Если СтрНайти(ВРег(СтрокаТЗ.Наименование), Искомое) > 0 Тогда
Найденные.Добавить(СтрокаТЗ);
КонецЕсли;
КонецЦикла;
ВРег(ТекстПоиска) вынесен за цикл: незачем пересчитывать его на каждой строке.
Частые вопросы
Чем СтрНайти отличается от Найти?
Найти ищет только первое вхождение слева направо. СтрНайти появилась в 8.3.6 и умеет искать с конца, с заданной позиции и N-е вхождение. Для нового кода используйте СтрНайти.Как проверить, что строка содержит хотя бы одно слово из списка?
СтрНайти(ВРег(Текст), ВРег(Слово)) > 0, выходя по первому совпадению. Для сотен слов удобнее одно регулярное выражение вида слово1|слово2|слово3.Почему поиск не находит текст, который явно есть в строке?
КодСимвола(Сред(Строка, N, 1)).Можно ли искать в строке без программирования?
Ctrl+F по колонкам, в текстовых и табличных документах — поиск по тексту через то же сочетание клавиш. Код нужен, когда поиск идёт внутри обработки или обмена.Как найти символ перевода строки?
Символы.ПС. В текстах из Windows строки часто разделены парой Символы.ВК + Символы.ПС, поэтому после разбиения по ПС уберите лишний ВК через СокрЛП().