Selecting multiple patterns in a string using powershell

Grep(1) — linux manual page

Пример 4

C:\PS>function search-help
{
    $pshelp = "$pshome\es\about_*.txt", "$pshome\en-US\*dll-help.xml"
    select-string -path $pshelp -pattern $args
}

Описание
-----------
Эта простая функция использует командлет Select-String для поиска заданной строки в файлах справки Windows PowerShell. В данном примере функция осуществляет поиск по каталогу en-US, в котором содержатся файлы справки на английском (США) языке.

Чтобы найти с помощью этой функции строку, например "psdrive", введите "search-help psdrive".

Чтобы использовать эту функцию в произвольной консоли Windows PowerShell, замените путь на расположение файлов справки Windows PowerShell в конкретной системе, а затем скопируйте функцию в профиль Windows PowerShell.






Pattern, path, and alias

Select-String has two main parameters: pattern and path. The pattern is what you want to search for, and the path is where you want to search.

Note: The command name is not case-sensitive. You can use ‘select-string’ instead of ‘Select-String’. Personally I prefer lowercase commands and parameters.

The following is an example of using the named -path and -pattern parameters with select-string:

You can exclude the parameter names as long as you specify the pattern first, then the path. For example:

Select-String’s command alias is sls. Usually when you’re typing commands on the command line, you’ll want to use the shortest possible syntax. The following is an example of using the command alias:

If you open the PowerShell terminal from the desired directory, you can exclude the path, like this:

All four of the commands above are functionally the same. Throughout this article, I’ll use the full select-string command name and named parameter flags for clarity.

Filter by file extension

You can filter by file extension. For example, to search for files with the .cs extension (C# source files), use *.cs in the path name like this:

This outputs the following:

You can also filter by multiple file extensions by using the -include flag, like this:

This outputs the following:

How do you use -include and -exclude at the same time?

Let’s say you want to exclude appsettings.Development.json from the above example. In other words, you’re including JSON files, but excluding this specific file. Select-String -include and -exclude don’t work together (Note: I’m not sure if this is a bug, or if it’s by design).

Instead, use Get-ChildItem (alias ls) to filter the files and pipe them to Select-String, like this:

This outputs the following results:

Notice that it excluded appsettings.Development.json.

Что такое Select-String и его параметры

Представьте, что вы пишете фрагменты кода в PowerShell и потеряли из виду определенные строки и текст в этом файле PowerShell. Вам нужно найти это во многих тысячах строк кода в тысячах строк и слов. Появляется команда Select-String, которая позволяет вам искать строки и текст в этих входных файлах PowerShell. Он похож на grep в Linux.

Select-String — это командлет, который используется для поиска текста и шаблонов во входных строках и файлах. Он похож на grep в Linux и FINDSTR в Windows. При использовании Select-String для поиска некоторого текста он находит первое совпадение в каждой строке и отображает имя файла, номер строки и всю строку, в которой произошло совпадение. Его можно использовать для поиска нескольких совпадений в строке или для отображения текста до или после совпадения или для получения результатов в логических выражениях, таких как True или False. Вы также можете использовать его для отображения всего текста, кроме совпадения с выражением, которое вы используете в команде. Подстановочные знаки, которые вы используете в FINDSTR, также можно использовать в Select-String. Кроме того, Select-String работает с различными кодировками файлов, такими как ASCII, Unicode и т. Д. Он использует Byte-Order-Mark (BOM) для определения кодировки файла. Если спецификация отсутствует в файле, Select-String примет файл как UTF8.

Параметры Select-String

Microsoft предусмотрел и разработал следующие параметры, которые будут использоваться в синтаксисе.

-Все совпадения

Он используется для поиска всех совпадений в строке, в отличие от первого совпадения в строке, которое обычно выполняет Select-Sting.

-Деликатный случай

Это означает, что совпадение чувствительно к регистру. По умолчанию Select-String не чувствителен к регистру.

-Контекст

Он используется для захвата указанного количества строк, которые вы вводите до и после строки соответствия. Если вы введете 1, будет зафиксирована одна строка до и после матча.

-Культура

В кодировании существуют определенные культуры, такие как порядковый, инвариантный и т. Д. Этот параметр используется для указания культуры в синтаксисе.

-Кодирование

Он используется для указания формата кодировки текста в таких файлах, как ASCII, UTF8, UTF7, Unicode и т. Д.

-Исключать

Этот параметр используется для исключения определенного текста из файла.

-Включают

Этот параметр используется для включения определенного текста в файл.

-InputObject

Он используется для указания текста для поиска.

-Список

Он используется для получения списка файлов, соответствующих тексту.

-LiteralPath

Он используется для указания пути поиска.

-Нет

Как правило, Select-String выделяет совпадение в файле. Этот параметр используется, чтобы избежать выделения.

-Не соответствует

Он используется для поиска текста, не соответствующего указанному шаблону.

-Дорожка

Он используется для указания пути для поиска вместе с использованием подстановочных знаков.

-Шаблон

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

-Тихий

Этот параметр используется для получения вывода в виде логических значений, таких как True или False.

-Сырой

Он используется, чтобы видеть только совпадающие объекты, а не информацию о совпадении.

-SimpleMatch

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

Группы, захваты и подстановки

Конструкции группирования разделяют входную строку на подстроки, которые можно использовать в этом же регулярном выражении или игнорировать. Сгруппированные подстроки называются подвыражениями. По умолчанию подвыражения записываются в пронумерованные группы, хотя вы также можете присвоить им имена.

Группирующая конструкция — это регулярное выражение, заключённое в круглые скобки. Любой текст, совпадающий с заключённым в него регулярным выражением, захватывается. В следующем примере вводимый текст разбивается на две группы захвата.

'The last logged on user was CONTOSO\jsmith' -match '(.+was )(.+)'
True

Используйте автоматическую переменную $Matches для получения захваченного текста. Текст, представляющий всё совпадение, сохраняется под ключом 0.

$Matches.0
The last logged on user was CONTOSO\jsmith

Захваты сохраняются в числовых ключах INTEGER, которые увеличиваются слева направо. Захват 1 содержит весь текст до имени пользователя, захват 2 содержит только имя пользователя.

$Matches
    Name                           Value
    ----                           -----
    2                              CONTOSO\jsmith
    1                              The last logged on user was
    0                              The last logged on user was CONTOSO\jsmith

Ключ 0 — это ЦЕЛОЕ число. Вы можете использовать любой метод HASHTABLE для доступа к сохранённому значению.

'Good Dog' -match 'Dog'
True

$Matches
Dog

$Matches.Item(0)
Dog

$Matches.0
Dog

Именованные захваты

По умолчанию захваты хранятся в возрастающем числовом порядке слева направо. Вы также можете назначить ИМЯ группе захвата. Это ИМЯ становится ключом в автоматической переменной $Matches.

Внутри группы захвата используйте ?<keyname> для хранения захваченных данных под именованным ключом.

$string = 'The last logged on user was CONTOSO\jsmith'
$string -match 'was (?<domain>.+)\\(?<user>.+)'
True

$Matches

    Name                           Value
    ----                           -----
    domain                         CONTOSO
    user                           jsmith
    0                              was CONTOSO\jsmith

$Matches.domain
CONTOSO

$Matches.user
jsmith

В следующем примере сохраняется последняя запись журнала в журнале безопасности Windows. Предоставленное регулярное выражение извлекает имя пользователя и домен из сообщения и сохраняет их под ключами: N для имени и D для домена.

$log = (Get-WinEvent -LogName Security -MaxEvents 1).message
$r = '(?s).*Account Name:\s*(?<N>.*).*Account Domain:\s*(?<D>*)'
$log -match $r
True

$Matches

    Name                           Value
    ----                           -----
    D                              CONTOSO
    N                              jsmith
    0                              A process has exited....

Подстановки в регулярных выражениях

Использование регулярных выражений с оператором -replace позволяет динамически заменять текст с помощью захваченного текста.

ВВОД -replace ОРИГИНАЛ, ПОДСТАНОВКА

Здесь:

  • ВВОД: строка по которой выполняется поиск
  • ОРИГИНАЛ: регулярное выражение, используемое для поиска входной строки.
  • ПОДСТАНОВКА: выражение подстановки для замены совпадения, найденные во входной строке.

Операнды ОРИГИНАЛ и ПОДСТАНОВКА подчиняются правилам обработчика регулярных выражений, таким как экранирование символов.

На группы захвата можно ссылаться в строке ПОДСТАНОВКА. Замена выполняется с помощью символа $ перед идентификатором группы.

Два способа ссылаться на группы захвата – по НОМЕРУ и по ИМЕНИ.

По НОМЕРУ — Группы захвата нумеруются слева направо.

'John D. Smith' -replace '(\w+) (\w+)\. (\w+)', '$1.$2.$3@contoso.com'
John.D.Smith@contoso.com

По ИМЕНИ — На группы захвата также можно ссылаться по имени.

'CONTOSO\Administrator' -replace '\w+\\(?<user>\w+)', 'FABRIKAM\${user}'
FABRIKAM\Administrator

Выражение $& представляет весь совпавший текст:

'Gobble' -replace 'Gobble', '$& $&'
Gobble Gobble

Поскольку символ $ имеет специальное значение, вам нужно использовать строки с одинарными кавычками, либо экранировать символ $ при использовании двойных кавычек.

'Hello World' -replace '(\w+) \w+', '$1 Universe'
Hello Universe

"Hello World" -replace "(\w+) \w+", "`$1 Universe"
Hello Universe

Кроме того, если вы хотите использовать символ $ в качестве буквального символа, используйте $$ вместо обычных escape-символов. При использовании двойных кавычек всё равно экранируйте все экземпляры $, чтобы избежать неправильной трактовки.

'5.72' -replace '(.+)', '$$$1'
$5.72

"5.72" -replace "(.+)", "`$`$`$1"
$5.72

ОПЦИИ

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

GIST | По умолчанию команда «grep» чувствительна к регистру. Следующий пример показывает как можно искать и при этом не учитывать регистр, например «Adams» и «adams» одно и то же:

--ignore-case 'adams'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
John Adams, 1797-1801

GIST | Поиск наоборот (иногда говорят инвертный поиск), то есть будут выведены все строки, кроме имеющих вхождение указанного шаблона:

--invert-match 'Adams'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
George Washington, 1789-1797
Thomas Jefferson, 1801-1809

GIST | Опции конечно же можно и нужно комбинировать друг с другом. Например поиск наоборот с выводом порядковых номеров строк с вхождениями:

--line-number --invert-match 'Adams'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
1:George Washington, 1789-1797
3:Thomas Jefferson, 1801-1809

GIST | Раскраска. Иногда удобно, когда искомое нами слово подсвечивается цветом. Все это уже есть в «grep», остается только включить:

--line-number --color=always 'Adams'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
2:John Adams, 1797-1801

GIST | Мы хотим выбрать все ошибки из лог файла, но знаем что в следующей сточке после ошибки может содержаться полезная информация, тогда удобно вывести несколько строк из контекста. По умолчанию «grep» выводит лишь строку, в которой было найдено совпадение, но есть несколько опций, позволяющих заставить «grep» выводить больше. Для вывода нескольких строк (в нашем случае двух) после вхождения:

--color=always -A2 'Adams'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
James Madison, 1809-1817
James Monroe, 1817-1825
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
James Madison, 1809-1817

GIST | Аналогично для дополнительного вывода нескольких строк перед вхождением:

--color=always -B2 'James'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
James Madison, 1809-1817
James Monroe, 1817-1825
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
James Madison, 1809-1817
James Monroe, 1817-1825

GIST | Однако чаще всего требуется выводить симметричный контекст, для этого есть ещё более сокращённая запись. Выведем по две строки как сверху так и снизу от вхождения:

--color=always -C2 'James'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
James Madison, 1809-1817
James Monroe, 1817-1825
John Quincy Adams, 1825-1829
Andrew Jackson, 1829-1837
Martin Van Buren, 1837-1841
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
James Madison, 1809-1817
James Monroe, 1817-1825
John Quincy Adams, 1825-1829
Andrew Jackson, 1829-1837

GIST | Когда Вы ищете , то по умолчанию «grep» будет выводить также, , и тому подобные комбинации. Найдём только те строки, которые выключают именно всё слово целиком:

--word-regexp --color=always 'John'
John Fitzgerald Kennedy, 1961-1963
Lyndon Baines Johnson, 1963-1969
John Fitzgerald Kennedy, 1961-1963

GIST | Ну и напоследок если Вы просто хотите знать количество строк с совпадениями одним единственным числом, но при этом не выводить больше ничего:

--count --color=always 'John'
John Fitzgerald Kennedy, 1961-1963
Lyndon Baines Johnson, 1963-1969
Richard Milhous Nixon, 1969-1974
2

Стоит отметить, что у большинства опций есть двойник, например можно привести к более короткому виду и т.д.

Поиск совпадений одного из нескольких символов

Когда мы не уверены в конкретном символе — мы можем указать несколько использовав квадратные скобки [] . Значения, которые будут помещены в эти скобки будут соответствовать одному значению:

В примере выше ищутся совпадения либо по букве R или P. Можно указывать диапазон значений. Например такое написание говорит, что мы ищем цифры от 1 до 9. Если написать у нас будут искаться числа с 5 до 9, затем с 6 по 9 и т.д.

Ниже мы ищем значения, где есть буквы от ‘с’ до ‘я’, после которых есть буква ‘е’. Во втором примере мы ищем элементы массива, которые начинаются с букв ‘д’ до ‘с’:

Можно использовать любые метасимволы. В примере ниже у нас идет поиск числа или буквы, затем пробела и опять число-буквенное значение:

Вы часто можете увидеть написание похоже на следующее, что обозначает любую букву а A до z, от 0 до 9:

grep примеры использования

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

Найдет файлы с упоминанием меня любимого, если таковые есть. Точнее не файлы, а строки с упоминанием указанного слова, т.е в данном случае sonikelf. Здесь стоит упомянуть, что строкой grep считает все символы, находящиеся между двумя символами новой строки.

grep sonikelf file.txt поиск sonikelf в файле file.txt, с выводом полностью совпавшей строкой
grep -o sonikelf file.txt поиск sonikelf в файле file.txt и вывод только совпавшего куска строки
grep -i sonikelf file.txt игнорирование регистра при поиске
grep -bn sonikelf file.txt показать строку (-n) и столбец (-b), где был найден sonikelf
grep -v sonikelf file.txt инверсия поиска (найдет все строки, которые не совпадают с шаблоном sonikelf)
grep -A 3 sonikelf file.txt вывод дополнительных трех строк, после совпавшей
grep -B 3 sonikelf file.txt вывод дополнительных трех строк, перед совпавшей
grep -C 3 sonikelf file.txt вывод три дополнительные строки перед и после совпавшей
grep -r sonikelf $HOME рекурсивный поиск по директории $HOME и всем вложенным
grep -c sonikelf file.txt подсчет совпадений
grep -L sonikelf *.txt вывести список txt-файлов, которые не содержат sonikelf
grep -l sonikelf *.txt вывести список txt-файлов, которые содержат sonikelf
grep -w sonikelf file.txt совпадение только с полным словом sonikelf
grep -f sonikelfs.txt file.txt поиск по нескольким sonikelf из файла sonikelfs.txt, шаблоны разделяются новой строкой
grep -I sonikelf file.txt игнорирование бинарных файлов
grep -v -f file2 file1 > file3 вывод строк, которые есть в file1 и нет в file2
grep -in -e ‘python’ `find -type f` рекурсивный поиск файлов, содержащих слово python с выводом номера строки и совпадений
grep -inc -e ‘test’ `find -type f` | grep -v :0 рекурсивный поиск файлов, содержащих слово python с выводом количества совпадений
grep . *.py вывод содержимого всех py-файлов, предваряя каждую строку именем файла
grep «Http404» apps/**/*.py рекурсивный поиск упоминаний Http404 в директории apps в py-файлах

Escape regex

regex is a complex language with common symbols and a shorthand syntax. There are times where you may want to match a literal value instead of a pattern. The will escape out all the regex syntax for you.

Take this string for example . It contains regex syntax that may not be obvious to you.

You may think this is matching a specific phone number but the thing it would match is . My point is that when you use a literal string where a regex is expected, that you will get unexpected results. This is where the solves that issue.

I don’t want to talk on this too much because this is an anti-pattern. If you are needing to regex escape your entire pattern before you match it, then you should use the method instead.

The only time you should be escaping a regex is if you are placing that value inside a more complex regex. Even that is solved with a more complex regex pattern.

If you are using this in your code. Rethink why you need it because odds are, you are using the wrong operator or method.

Multiple matches per line

The operator will only match once per line so the variable only contains that first match. There are times where I want to grab every occurace of a pattern even if there are multiples per line. I have 2 ways that I approach this scenario.

Синтаксис grep и find

Начнём с оператора find. Синтаксис файловой поисковой команды выглядит так:

Некоторые употребительные параметры:

  • -depth : поиск в текущей папке и подкаталогах;
  • -version : вывести версию команды;
  • -print : показывать полные имена файлов (в Linux они могут быть сколь угодно большими);
  • -type f : поиск исключительно файлов;
  • -type d – поиск только директорий (папок).

Перечень доступных опций (указываются через дефис):

  • name : файловый поиск по имени;
  • user : поиск по имени владельца файла;
  • perm : по атрибуту «режим доступа»;
  • mtime : по времени последнего изменения (редактирования) файла;
  • group : по группе;
  • atime : по дате последнего открытия файла;
  • newer : поиск файла с датой, более новой, чем заданная в шаблоне директивы;
  • size : по размеру файла в байтах;
  • nouser : поиск файлов, не имеющих введённого атрибута «владелец».

Синтаксис grep:

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

В шаблоне указывается, что нужно искать, используя непосредственно заданную строку или регулярное выражение.

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

Рассмотрим наиболее употребительные параметры grep:

  • -b : выводить номер блока перед выдачей результирующей строки;
  • -c : необходимо подсчитать число вхождений искомого фрагмента;
  • -i : поиск без учёта регистра;
  • -n : выдавать на стандартное устройство вывода номер строки, в которой найден искомый фрагмент или шаблон;
  • – l : в результате выдачи должны присутствовать только имена файлов с найденным поисковым фрагментом;
  • -s : игнорировать вывод ошибок;
  • -w : поиск фрагмента, опоясанного с двух сторон пробелами;
  • -v : инвертированный поиск, то есть отображение всех строк, не содержащих заданный фрагмент;
  • -e : параметр указывает, что далее следует регулярное выражение, имеющее собственный синтаксис;
  • -An : вывод искомого фрагмента и предыдущих n строк;
  • -Bn : то же, но со строками, идущими после шаблона.

Теперь имеет смысл перейти от теоретической части к практической.3

БАЗОВЫЕ РЕГУЛЯРНЫЕ ВЫРАЖЕНИЯ

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

Знак каретки и доллара имеют в регулярном выражении особый смысл. Их называют «якорями» (anchor). Якоря – это специальные символы, которые указывают местонахождение в строке необходимого совпадения. Когда поиск доходит до якоря, он проверяет, есть ли соответствие, и если есть – продолжает идти по шаблону, не прибавляя ничего к результату.

GIST | Якорь каретка используют чтобы указать, что регулярное выражение необходимо проверить именно с начала строки:

--color=always '^J'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
John Adams, 1797-1801

GIST | Аналогично якорь доллар стоит использовать в конце шаблона, чтобы указать, что совпадение действительно только если искомая строка символов находится в конце текстовой строки и никак иначе:

--color=always '9$'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
Thomas Jefferson, 1801-1809

GIST | Любой символ. Символ точка используется в регулярных выражениях для того, чтобы обозначить, что в указанном месте может находиться абсолютно любой символ:

--color=always '0.$'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
John Adams, 1797-1801
Thomas Jefferson, 1801-1809

GIST | Экранирование. Если нужно найти именно символ точка, тогда экранирование в помощь. Знак экранирования (как правило это обратный слеш), предшествующий символу вроде точки, превращает метасимвол в литерал:

--color=always '\.'
George Washington. 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
George Washington. 1789-1797

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

--color=always '0'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
John Adams, 1797-1801
Thomas Jefferson, 1801-1809

GIST | Диапазон. Это два символа, разделенные дефисом, например, 0-9 (десятичные цифры) или 0-9a-fA-F (шестнадцатеричные цифры):

--color=always ''
George Washington, ???
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
John Adams, 1797-1801
Thomas Jefferson, 1801-1809

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

--color=always '$'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
John Adams, 1797-1801
Thomas Jefferson, 1801-1809

GIST | Классы символов POSIX. Существует некий набор уже заранее заготовленных классов символов, которые Вы можете использовать в регулярных выражениях. Их там с десяток, достаточно быстро просмотреть мануал чтобы понять назначение каждого. Например отфильтруем только шестнадцатеричные цифры:

--color=always '^]*$'
4.2
42
42abc
42
42abc

GIST | Повторение (0 или больше раз). Одним из наиболее часто используемых метасимволов является символ звёздочка, что означает «повторить предыдущий символ или выражение ноль или больше раз»:

--color=always '^*$'
George Washington, ???
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
George Washington, ???

Различают базовые регулярные выражения BRE (basic regular expressions) и расширенные ERE (extended regular expressions). В BRE распознаются следующие метасимволы и все другие символы расцениваются как литералы. В ERE добавлены ещё такие метасимволы и связанные с ними функции. Ну а чтобы всех окончательно запутать в «grep» придумали такую штуку – символы в BRE обрабатываются как метасимволы, если они экранированы обратным слешем, в то время как в ERE постановка перед любыми метасимволами обратного слеша приводит к тому, что они трактуются как литералы.

Понравилась статья? Поделиться с друзьями:
Быть в курсе нового
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: