Foreach-object

Создание системы файлового архива с использованием циклов ForEach

Давайте сломать шаги вниз. Вы используете Get-ChildItem, чтобы получить все файлы в папке «Документы». Переменная среды переменные $ env: USERPROFILE запускает скрипт, используя текущий профиль. Эта переменная более переносима, чем жестко закодированный путь. Результаты этого поиска присваиваются переменной $ MyDocs . Затем мы создаем наш цикл ForEach, заставляя его проходить через каждый $ Doc в $ MyDocs .

Внутри цикла мы проверяем, является ли свойство LastAccessTime каждого файла старше 30 дней. Мы получаем это с помощью командлета Get-Date и с помощью функции AddDays с отрицательным тридцать. Если это так, мы добавляем файл в массив $ myOldDocs . После того, как сортировка файлов завершена, мы берем наш законченный массив и создаем zip-файл. Этот процесс немного сложнее, так как включает в себя немного .NET. Не беспокойтесь, если вы не совсем поняли — вы можете украсть код из этого справочного документа TechNet .

Чтобы разобраться в том, что здесь происходит: мы переместим все наши старые файлы в новый каталог с именем на сегодняшнюю дату старше 30 дней. Как только эта папка будет создана, мы должны создать ZIP-архив с тем же именем. Мы проверим, чтобы убедиться, что архив успешно завершен, и файл .ZIP есть, а затем удалим новую папку. Установите это как запланированное задание для запуска один раз в месяц. Вы сэкономите немного места и сохраните папку «Документы» в чистоте.

PowerShell Do Until Loop

Do Until is pretty much the same as Do While in PowerShell. The only difference is the condition where they run on.

  • Do While keeps running as long as the condition is true. When the condition is false it will stop.
  • Do Until keeps running as long as the condition is false. When the condition becomes true it will stop.
Do {
    Write-Host "Computer offline"
    Start-Sleep 5
}
Until (Test-Connection -ComputerName 'lab01-srv' -Quiet -Count 1)

Write-Host "Computer online"

When to use While or Until really depends on what you want to do. Is basis you can say that when the exit condition must be true, then use Until. If the exit condition is negative (false) then you should use While.

Stopping a Do While/Until loop

In PowerShell, we can use Break to stop a loop early. It’s best to limit the use of breaks in your loops because they make your code harder to read. If you have structured your code well, then you really don’t need it.

If you need to use a break, then one thing can be useful, you can add a GoTo label behind it. This way you can jump to another loop function.

$i=0;
Do {
    Write-Host "Computer offline"
    Start-Sleep 1
    $i++;

    if ($i -eq 2) {
        Break :TestInternetConnection
    }
}
Until (Test-Connection -ComputerName 'test' -Quiet -Count 1)

Write-Host "Computer online"

:TestInternetConnection Do{
    Write-Host "Checking internet connection"
}
Until (Test-Connection -ComputerName '8.8.8.8' -Quiet -Count 1)

Write-host "Connected"

Using Continue in a PowerShell Loop

Another useful method that you can use inside a do-while/until loop in PowerShell is Continue. With Continue you can stop the script inside the Do body and continue to the next iteration of the loop.

This can be useful if you have a large script inside a Do block and want to skip to the next item when a certain condition is met.

$servers = @('srv-lab01','srv-lab02','srv-lab03')

Foreach ($server in $servers) {
    
    if ((Test-Connection -ComputerName $server -Quiet -Count 1) -eq $false) {
        Write-warning "server $server offline"
        Continue
    }

    # Do stuff when server is online
}

PowerShell Loop Examples

Below you will find a couple of loop examples that may help to create your own loops.

PowerShell Infinite Loop

You can create an infinite loop in PowerShell with the While loop function. While will keep running as long as the condition is true. So to create an infinite loop we can simply do the following:

While ($true) {
    # Do stuff forever
    Write-Host 'looping'

    # Use Break to exit the loop
    If ($condition -eq $true) {
        Break
    }
}

To exit the infinite loop you can use either Ctrl + C or based on a condition with Break.

Powershell Loop Through Files

To loop through files we are going to use the foreach-object function. Files are objects that we can get with the get-childitem cmdlet, so we can pipe the foreach-object behind it. Another reason to use ForEach-Object instead of foreach is that we don’t know upfront how many files we are going to process.

Foreach will load all the data in the memory, which we don’t want with an unknown data set.

$path = "C:\temp"
$csvItems = 0

Get-ChildItem $path | ForEach-Object {
    $_.Name;

    If ($_.Extension -eq '.csv') {
        $csvItems++;
    }
}

Write-host "Total CSV Files are $csvItems";

If you want to include the subfolder as well you can use the -recurse option. Use -filter to get only the .csv files for example.

Get-ChildItem $path -recurse -filter *.csv

Loop through a text file

In PowerShell, you can loop through a text file line for line. This can be really useful if you want to automatically read out log files for example.

The example below works fine for small files, but the problem is that Get-Content will read the entire file into the memory.

$lineNr = 0

foreach($line in Get-Content c:\temp\import-log.txt) {
    if($line -match 'warning'){
        # Work here
        Write-warning "Warning found on line $lineNr :"
        Write-warning $line
    }

    $lineNr++;
}

To read and process larger files with PowerShell you can also use the .Net file reader:

foreach($line in ::ReadLines("c:\temp\import-log.txt"))
{
       $line
}

Типы массивов

По умолчанию массив в PowerShell создается как тип . Благодаря этому он может содержать любые типы объектов и значений. Это возможно, поскольку все наследуется из типа .

Строго типизированные массивы

Массив любого типа можно создать с помощью сходного синтаксиса. Если вы создаете строго типизированный массив, он может содержать только значения или объекты заданного типа.

ArrayList

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

Как правило, если нам нужен массив, поддерживающий более быструю работу, мы в первую очередь вспоминаем о . Он выступает в качестве массива объектов везде, где это необходимо, и при этом поддерживает быстрое добавление элементов.

Мы создаем и добавляем в него элементы следующим образом.

Для получения этого типа вызывается .NET. В этом случае для его создания используется конструктор по умолчанию. Затем вызывается метод , чтобы добавить в него элемент.

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

Если единственными данными в массиве являются строки, ознакомьтесь также с использованием StringBuilder. Это практически то же самое, но некоторые методы предназначены только для работы со строками. специально предназначен для повышения производительности.

Обычно пользователи переходят на с массивов. Однако это наследие тех времен, когда в C# еще не была предусмотрена универсальная поддержка. От отказываются в пользу универсального

Универсальный список

Универсальный тип — это особый тип в C#, который определяет обобщенный класс, при этом пользователь указывает типы данных, которые используются в процессе создания. Поэтому если требуется список чисел или строк, необходимо определить, что требуется список типов или .

Список для строк создается следующим образом.

Так же создается список чисел.

Можно привести существующий массив к списку следующим образом, не создавая сначала объект:

Синтаксис можно сократить с помощью оператора в PowerShell 5 и более поздних версиях. Оператор должен быть первой строкой сценария. Объявляя пространство имен, PowerShell позволяет исключить из него типы данных при ссылке на них.

Это позволяет сделать гораздо более удобным.

Вам доступен аналогичный метод . В отличие от ArrayList метод не возвращает значение, поэтому для него не нужно выполнять .

Кроме того, мы по-прежнему можем получить доступ к таким элементам, как другие массивы.

List

Можно получить список любого типа, но, если вам неизвестен тип объектов, в качестве контейнера для них можно использовать .

Remove()

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

При работе с типами значений из списка удаляется первый из типов. Его можно вызвать снова, чтобы еще раз удалить это значение. Если используются ссылочные типы, необходимо предоставить объект, который требуется удалить.

Метод Remove возвращает , если удалось найти и удалить элемент из коллекции.

Можно использовать также множество других коллекций, однако эти оптимальны в качестве замены массивам.
Если вам интересно узнать больше о других вариантах, взгляните на Gist, собранный Марком Краусом.

Доступ к элементам массива и их использование

Чтение массива

Вы можете ссылаться на массив, используя его имя переменной. Чтобы отобразить все элементы в массиве, введите имя массива. Например, предположим , что это массив, содержащий целые числа 0, 1, 2, до 9; ввод:

Вы можете ссылаться на элементы массива с помощью индекса, начиная с позиции 0. Заключите номер индекса в квадратные скобки. Например, чтобы отобразить первый элемент в массиве , введите:

Чтобы отобразить третий элемент в массиве , введите:

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

Отрицательное число чисел от конца массива. Например, «-1» относится к последнему элементу массива. Чтобы отобразить последние три элемента массива, в порядке возрастания индекса введите:

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

Однако будьте осторожны при использовании этой нотации. Нотация циклов от конечной границы до начала массива.

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

Оператор plus () можно использовать для объединения диапазонов со списком элементов в массиве. Например, чтобы отобразить элементы на позициях индекса 0, 2 и 4–6, введите:

Кроме того, для перечисления нескольких диапазонов и отдельных элементов можно использовать оператор plus. Например, чтобы вывести список элементов от 0 до двух, четырех до шести и элемента в восьмом позициальном типе:

Итерации по элементам массива

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

Цикл выполняет итерации по массиву и возвращает каждое значение в массиве до достижения конца массива.

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

Цикл можно использовать для отображения элементов в массиве до тех пор, пока определенное условие больше не будет верным. Например, чтобы отобразить элементы в массиве , если индекс массива меньше 4, введите:

Параметры

-ArgumentList

Задает массив аргументов для вызова метода. Дополнительные сведения о поведении ArgumentList см. в .

Этот параметр впервые появился в Windows PowerShell 3.0.

Type: Object
Aliases: Args
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False

-Begin

Указывает блок скрипта, который выполняется до того, как этот командлет обработает все входные объекты. Этот блок скрипта выполняется только один раз для всего конвейера. Дополнительные сведения о блоке см. в .

Type: ScriptBlock
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False

-Confirm

Запрос подтверждения перед выполнением командлета.

Type: SwitchParameter
Aliases: cf
Position: Named
Default value: False
Accept pipeline input: False
Accept wildcard characters: False

-End

Задает блок скрипта, который выполняется после обработки всех входных объектов этим командлетом. Этот блок скрипта выполняется только один раз для всего конвейера. Дополнительные сведения о блоке см. в .

Type: ScriptBlock
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False

-InputObject

Задает входные объекты. запускает блок скрипта или инструкцию операции для каждого входного объекта. Введите переменную, которая содержит объекты, или команду или выражение, которое возвращает объекты.

При использовании параметра InputObject с вместо отправки результатов команды в значение InputObject обрабатывается как один объект. Это верно, даже если значение является коллекцией, которая является результатом выполнения команды, например .
Так как InputObject не может возвращать отдельные свойства из массива или коллекции объектов, рекомендуется, чтобы при использовании для выполнения операций с коллекцией объектов для этих объектов, имеющих определенные значения в определенных свойствах, использовать в конвейере, как показано в примерах в этом разделе.

Type: PSObject
Position: Named
Default value: None
Accept pipeline input: True
Accept wildcard characters: False

-MemberName

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

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

Этот параметр впервые появился в Windows PowerShell 3.0.

Type: String
Position:
Default value: None
Accept pipeline input: False
Accept wildcard characters: True

-Process

Указывает операцию, которая выполняется с каждым входным объектом. Этот блок скрипта выполняется для каждого объекта в конвейере. Дополнительные сведения о блоке см. в .

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

Type: ScriptBlock
Position:
Default value: None
Accept pipeline input: False
Accept wildcard characters: False

-RemainingScripts

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

Этот параметр впервые появился в Windows PowerShell 3.0.

Type: ScriptBlock
Position: Named
Default value: None
Accept pipeline input: False
Accept wildcard characters: False

-WhatIf

Показывает, что произойдет при запуске командлета. Командлет не выполняется.

Type: SwitchParameter
Aliases: wi
Position: Named
Default value: False
Accept pipeline input: False
Accept wildcard characters: False

Description

The cmdlet performs an operation on each item in a collection of input objects. The
input objects can be piped to the cmdlet or specified using the InputObject parameter.

Starting in Windows PowerShell 3.0, there are two different ways to construct a
command.

  • Script block. You can use a script block to specify the operation. Within the script block,
    use the variable to represent the current object. The script block is the value of the
    Process parameter. The script block can contain any PowerShell script.

    For example, the following command gets the value of the ProcessName property of each process
    on the computer.

    supports the , , and blocks as described in
    .

    Note

    The script blocks run in the caller’s scope. Therefore the blocks have access to variables in
    that scope and can create new variables that persist in that scope after the cmdlet completes.

  • Operation statement. You can also write an operation statement, which is much more like
    natural language. You can use the operation statement to specify a property value or call a
    method. Operation statements were introduced in Windows PowerShell 3.0.

    For example, the following command also gets the value of the ProcessName property of each
    process on the computer.

  • Parallel running script block. Beginning with PowerShell 7.0, a third parameter set is
    available that runs each script block in parallel. The ThrottleLimit parameter limits the
    number of parallel scripts running at a time. As before, use the variable to represent the
    current input object in the script block. Use the keyword to pass variable references to
    the running script.

    In PowerShell 7, a new runspace is created for each loop iteration to ensure maximum isolation.
    This can be a large performance and resource hit if the work you are doing is small compared to
    creating new runspaces or if there are a lot of iterations performing significant work. As of
    PowerShell 7.1, runspaces from a runspace pool are reused by default. The runspace pool size is
    specified by the ThrottleLimit parameter. The default runspace pool size is 5. You can still
    create a new runspace for each iteration using the UseNewRunspace switch.

    By default, the parallel scriptblocks use the current working directory of the caller that started
    the parallel tasks.

    For more information, see the section of this article.

Синтаксис

Ниже показан синтаксис:

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

Примеры

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

В этом примере массив создается и инициализируется строковыми значениями , и . При первом запуске инструкции она задает переменную, равную первому элементу (). Затем он использует командлет для отображения буквы a. При следующем цикле задано значение и т. д. После отображения буквы d PowerShell завершает цикл.

операторы также можно использовать вместе с командлетами, возвращающими коллекцию элементов. В следующем примере инструкция Foreach выполняет шаги по списку элементов, возвращаемых командлетом .

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

В этом примере цикл использует свойство переменной для выполнения операции сравнения (). Переменная содержит все свойства объекта, возвращаемого командлетом. Таким образом, вы можете вернуть больше, чем просто имя файла.
В следующем примере PowerShell возвращает длину и время последнего доступа в списке инструкций:

В этом примере вы не ограничиваетесь выполнением одной команды в списке инструкций.

Можно также использовать переменную за пределами цикла и увеличить переменную внутри цикла. В следующем примере количество файлов превышает 100 КБ.

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

В предыдущем примере также показано, как отформатировать результаты длины файла:

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

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

Дополнительные сведения см «.

Операторы

Операторы в PowerShell также эффективны для массивов. Некоторые из них работают несколько иначе.

-join

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

В операторе мне особенно нравится то, что он обрабатывает единичные элементы.

Я использую его для ведения журнала и подробных сообщений.

-join $array

Вот еще один полезный прием, о котором мне рассказал Ли Дейли. Если нужно объединить все без использования разделителя, вместо этого:

Можно использовать с массивом в качестве параметра без префикса. Этот пример наглядно демонстрирует то, о чем я говорил.

-replace и -split

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

-in

Если одно значение, которое вам нужно проверить, совпадает с одним или несколькими значениями, можно использовать оператор . Значение должно быть в левой, а массив — в правой части оператора.

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

-eq и -ne

Равенство и массивы могут оказаться достаточно сложными. Если массив располагается в левой части, выполняется сравнение всех элементов. Вместо возврата возвращается совпадающий объект.

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

При использовании в операторе возвращается значение . Если значение не возвращается, то речь о значении . Оба этих оператора оцениваются как .

Я еще вернусь к этому вопросу позднее, когда мы будем говорить о тестировании .

-match

Оператор пытается сопоставить все элементы в коллекции.

При использовании с одним значением специальная переменная заполняется сведениями о соответствии. Этого не происходит, если массив обрабатывается таким образом.

Этот же подход можно использовать в случае с .

Я подробно расскажу о переменных , и в другой публикации под названием Множество способов использования регулярных выражений (Множество способов использования регулярных выражений).

$null или empty

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

На первый взгляд этот оператор выглядит вполне работоспособным.

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

Именно поэтому рекомендуется размещать в левой части оператора. Благодаря этому сценарий выполняется без проблем.

Массив не равен пустому массиву. Если вы уверены, что у вас есть массив, проверьте количество объектов в нем. Если это массив , число объектов равно .

Однако есть еще одна сложность, которую нужно учитывать в этом случае. Можно использовать даже при наличии одного объекта, если только этот объект не является . Эта ошибка исправлена в PowerShell 6.1.
Это хорошая новость, однако многие люди по-прежнему используют версию 5.1, так что им стоит иметь в виду указанную ошибку.

Если вы по-прежнему используете PowerShell 5.1, можно перенести объект в массив перед проверкой количества объектов, чтобы получить точное число.

Чтобы безопасно воспроизвести этот сценарий, проверьте , а затем проверьте число объектов.

Недавно кто-то спрашивал, как проверить, соответствует ли каждое значение в массиве заданному значению.
Пользователь Reddit /u/bis предложил это разумное решение, которое проверяет наличие некорректных значений, а затем инвертирует результат.

Петли Powershell ForEach: дверь в расширенную обработку данных

ForEach — это псевдоним ForEach-Object. (Псевдоним — это просто ярлык для команды в PowerShell.) Сейчас самое время поговорить о том, как PowerShell обрабатывает данные.

Как и большинство современных языков программирования, PowerShell является объектно-ориентированным. . Все в PowerShell — это объект, то есть даже переменные имеют расширенные свойства и функции Именно из-за этого свойства вы можете задать поиск по переменной и в итоге получить массив результатов.

В некоторых языках обработка этого массива будет многоэтапным процессом. Сначала получаем длину, а затем просчитываем каждый шаг.

В PowerShell вы проходите массив и выполняете действие для каждого из них, используя ForEach. Это сэкономит вам несколько строк кода, что полезно, если у вас есть более длинный скрипт. Например, ниже приведен небольшой скрипт, который будет использовать пару циклов Powershell ForEach. Он создает ZIP-архив всех ваших файлов, которые вы не открывали в течение 30 дней.

Описание

Командлет выполняет операцию с каждым элементом в коллекции входных объектов. Входные объекты можно передать в командлет или указать с помощью параметра InputObject .

Начиная с Windows PowerShell 3.0 существует два разных способа создания команды.

  • Блок скрипта. Можно использовать блок скрипта, чтобы указать операцию. В блоке скрипта используйте переменную для представления текущего объекта . Блок скрипта — это значение параметра Process. Блок скрипта может содержать любой скрипт PowerShell.

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

    поддерживает блоки , и , как описано в .

    Примечание

    Блоки скриптов выполняются в области вызывающей стороны. Поэтому блоки имеют доступ к переменным в этой области и могут создавать новые переменные, которые сохраняются в этой области после завершения командлета.

  • Оператор Operation. Можно также написать оператор операции, который больше похож на естественный язык. С помощью инструкции операции можно указать значение свойства или вызвать метод. Инструкции операций появились в Windows PowerShell 3.0.

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

  • Параллельно выполняющаяся блокировка скрипта. Начиная с PowerShell 7.0 доступен третий набор параметров, который запускает каждый блок скрипта параллельно. Параметр ThrottleLimit ограничивает количество параллельных скриптов, выполняемых одновременно. Как и ранее, используйте переменную для представления текущего входного объекта в блоке скрипта. Используйте ключевое слово для передачи ссылок на переменные в выполняющуюся скрипт.

    В PowerShell 7 для каждой итерации цикла создается новое пространство выполнения, чтобы обеспечить максимальную изоляцию.
    Это может быть большим снижением производительности и ресурсов, если работа, выполняемая вами, невелика по сравнению с созданием новых пространств выполнения или если имеется много итераций, выполняющих значительную работу. Начиная с PowerShell 7.1, пространства выполнения из пула runspace повторно используются по умолчанию. Размер пула пространства выполнения определяется параметром ThrottleLimit . Размер пула пространств выполнения по умолчанию — 5. Вы по-прежнему можете создать новое пространство выполнения для каждой итерации с помощью параметра UseNewRunspace .

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

    Дополнительные сведения см. в разделе этой статьи.

Рейтинг
( Пока оценок нет )
Editor
Editor/ автор статьи

Давно интересуюсь темой. Мне нравится писать о том, в чём разбираюсь.

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

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