Конвейер — основная концепция, которую необходимо изучить, иначе вы сможете использовать лишь ничтожную часть функций PowerShell. Конвейерный принцип был реализован еще в оболочках операционной системы Unix, затем скопирован в Cmd.exe, а в PowerShell поднят на новый уровень. Прежде чем перейти к теме конвейера PowerShell, я считаю целесообразным напомнить общие сведения, касающиеся стандартного входа и выхода.

Стандартный вход и выход

В Cmd.exe конвейер тесно связан со стандартным входом, перенаправлением входа, стандартным выходом и его перенаправлением. По существу, стандартный вход — это ввод с клавиатуры, если только вы не предписываете оболочке считать входные данные откуда-либо еще (перенаправление входа). Стандартный выход — это обычный результат выполнения команды, отображаемый на экране, если только вы не предписываете оболочке сохранить выходные данные куда-либо еще (перенаправление выхода).

Чтобы увидеть работу стандартного входа, введем в окне PowerShell или Cmd.exe следующую команду:

sort.exe

После ввода этой команды курсор стоит на месте и ждет дальнейшего ввода, так как по умолчанию команда Sort.exe сортирует стандартный вход. Поскольку мы ничего не указали, команда ожидает ввода. Для отмены действия команды нажмите комбинацию клавиш Ctrl+C.

Теперь предположим, что существует файл с именем MyData.txt, данные в котором требуется сортировать. Вот как будет выглядеть на экране сортируемый выход файла (стандартный выход):

type MyData.txt | sort.exe

В этом примере команда Type выводит содержимое файла MyData.txt как стандартный выход, который поступает на конвейер (|) и используется в качестве входа для Sort.exe.

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

В большинстве командных оболочек (например, Cmd.exe) стандартный выход и стандартный вход представляют собой текст. Это делает решение многих задач, связанных с различными манипуляциями с данными, неудобным и громоздким. На приведенном экране показан пример «кульбитов», которые приходится сделать в Cmd.exe, чтобы всего лишь вывести список текстовых файлов, последний раз сохраненных в текущем году.

 

Сценарии Cmd.exe для вывода списка текстовых файлов, созданных в текущем году
Экран. Сценарии Cmd.exe для вывода списка текстовых файлов, созданных в текущем году

Сценарий Sample1.cmd выводит время последнего сохранения каждого файла, за которым следует символ жесткой табуляции, после чего выводится имя файла. Сценарий Sample2.cmd берет текущий год и выполняет Sample1.cmd, выводя лишь те файлы, у которых год последнего сохранения совпадает с текущим годом. Красная стрелка указывает на символ жесткой табуляции в обоих сценариях. На экране также показан выходной результат выполнения Sample2.cmd (File1.txt и File3.txt).

Отметим, что оба сценария предусматривают синтаксический анализ строк, зависящий от формата строки даты (%%~tF в Sample1.cmd и %DATE% в Sample2.cmd). В отличных от американо-англоязычных версиях Windows строки кода, где используется дата, придется корректировать, так как различные языковые стандарты используют разные форматы даты. Кроме того, из-за мудреного синтаксиса сценарии Cmd.exe малопонятны и неудобны в использовании (к примеру, что означает%DATE:~10,4%?).

Цель данного примера — продемонстрировать неуклюжее и громоздкое решение задачи, кажущейся простой (вывод списка файлов, созданных в текущем году). Здесь значительная часть проблемы обусловлена необходимостью выполнения синтаксического анализа строк для определения года. Кроме всего прочего, синтаксис года зависит от языкового стандарта, что может осложнить ситуацию для сред, в которых сценарии используются коллективно. Заметим также, что с появлением дополнительных требований (например, удалить файлы, последний раз сохраненные до наступления текущего года) сценарии будут становиться все более громоздкими и сложными для восприятия. Должен существовать более удобный путь. Давайте посмотрим, как подобные задачи решает PowerShell.

Конвейер PowerShell

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

Фильтрация с помощью Where-Object

Рассмотрим предыдущий пример: задача вывода списка файлов *.txt, в последний раз записанных в текущем году. В PowerShell это делается путем извлечения объектов файловой системы (Get-ChildItem) и выбора (Where-Object) только тех из них, у которых значением свойства LastWriteTime является текущий год:

Get-ChildItem *.txt | Where-Object {
  $_.LastWriteTime.Year -eq (Get-Date).Year
}

Эту команду можно записать в одну строку, но я разбил ее на несколько строк, чтобы облегчить восприятие. Код между фигурными скобками {} называется блоком сценария. В блоке сценария Where-Object переменная $_ означает «текущий объект с конвейера». Таким образом, данная команда предписывает «взять объекты файловой системы, относящиеся к типу *.txt (Get-ChildItem *.txt) и вывести из них только те, у которых (Where-Object) год последнего сохранения ($_.LastWriteTime.Year) равен (-eq) текущему году ((Get-Date).Year)».

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

Теперь предположим, что требуется удалить файлы, последнее сохранение которых выполнялось до начала текущего года. Чтобы решить эту задачу, достаточно немного изменить фильтр и подать результат по конвейеру на вход Remove-Item:

Get-ChildItem *.txt | Where-Object {
  $_.LastWriteTime.Year -lt (Get-Date).Year
} | Remove-Item

Все, что мы изменили, — использовали -lt (меньше) вместо -eq (равно), а затем после символа конвейера добавили команду Remove-Item.

В этих двух командах PowerShell вместо передачи текстовых строк между командами передаются объекты: файл — это объект; дата его последнего сохранения — тоже объект.

Выполнение действий с помощью ForEach-Object

Помимо фильтрации с помощью Where-Object, для каждого объекта, проходящего по конвейеру, можно выполнить определенное действие с помощью ForEach-Object. Подобно Where-Object, команда ForEach-Object использует блок сценария и переменную $_, представляющую текущий объект на конвейере.

Для примера предположим, что нам требуется вывести полный путь и имя каждого файла *.txt. Команда, позволяющая решить эту задачу, выглядит так:

Get-ChildItem *.txt | ForEach-Object {
  $_.FullName
}

Выход этой команды — полный путь и имя каждого файла *.txt. Конечно, внутри блока сценария можно выполнить много других действий. Например, записать имена, а затем удалить файлы *.log из каталога C:\Logs позволяет такая команда:

Get-ChildItem C:\Logs\*.log |
ForEach-Object {
  "Removing $($_.FullName)"
  Remove-Item $_
} | Out-File C:\Logs\Cleanup.txt -Append

Эта команда выводит текстовую строку «Удаление<полный путь и имя файла журнала>», а затем удаляет файл (Remove-Item). Все выведенные строки записываются в файл C:\Logs\Cleanup.txt.

Фильтрацию (Where-Object) можно комбинировать с действиями (ForEach-Object) для построения еще более гибких команд. Например, удалить файлы *.log старше шести месяцев и записывать имя каждого из них перед удалением позволяет следующая команда:

Get-ChildItem C:\Logs\*.log | Where-Object {
  $_.LastWriteTime -lt
  (Get-Date).AddMonths(-6)
} | ForEach-Object {
  "Removing $($_.FullName)"
  Remove-Item $_
} | Out-File C:\Logs\Cleanup.txt -Append

Даже если вы не являетесь экспертом по PowerShell, примерное понимание изложенных выше основ объектов и конвейера позволит вам разобраться, как работают эти команды PowerShell.

Мощь конвейера

Конвейер — это краеугольный камень, открывающий возможность реализации всего богатства функций PowerShell. Поэкспериментировав с описанными выше примерами, вы обнаружите, что PowerShell упрощает сложные задачи намного эффективнее, чем это возможно в Cmd.exe. Получить дополнительную информацию и ознакомиться с другими примерами можно в разделе справки PowerShell, посвященном конвейерам (https://technet.microsoft.com/en-us/library/hh847902.aspx).