Функции PowerShell могут загружаться одним из двух способов: используя параметры или конвейер. Параметры просты для понимания, потому что вы определяете их как часть функции. К примеру, следующий код объявляет функцию Out-Item с параметром $item:

function Out-Item ($item) {
   …
}

Вы также можете создать функцию следующим образом:

function Out-Item {
   param ($item)
   …
}

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

Get-ChildItem C:\|
   Where-Object {$_.Length -eq 0}

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

function Out-Item {
   process {
      $_
   }
}

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

Как вариант вы можете написать функцию, которая обрабатывает данные на входе из параметра:

function Out-Item {
   param ($item)
   $item
}

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

.

Базовый подход

В листинге 1 показан базовый подход, позволяющий написать функцию, которая может загружать данные как из конвейера, так и из параметра. Этот код использует сценарий, чтобы обработать ввод из конвейера, и оператор param, чтобы обработать ввод из параметра. Обратите внимание на присоединение атрибута СmdletBinding в операторе param функции (фрагмент А) и атрибута Parameter в параметре $item (фрагмент В). Атрибут СmdletBinding, который является новым в PowerShell 2.0, включает режим поведения функции как команды. Это позволяет функции загружать данные, используя как конвейер, так и параметр. Как показано во фрагменте С, функция Out-Item также включает блоки begin и end сценария, чтобы сообщить, сколько данных получает функция. Блоки Begin и End выполняются только раз, тогда как блок обработки сценария выполняется для каждого элемента.

На экране 1 показана функция Out-Item, работающая с загрузкой из конвейера (первая команда) и с вводом параметра (вторая команда). Если вы посмотрите внимательно на результат второй команды, то заметите, как функция Out-Item отвечает, что она обработала только один элемент. Если вы применяете базовый подход, все данные обрабатываются и выводятся за один проход. Это неудобно, если данных много или они загружаются медленно (например, вы извлекаете свойства файлов по медленному сетевому соединению). По этой причине я не рекомендую использовать базовый подход.

 

Экран 1. Результаты вывода листинга 1

Оптимальный подход

Вместо базового подхода, представленного в листинге 1, который получает все данные одновременно, лучше получать все элементы параметра поочередно, как если бы вы использовали конвейер. Чтобы это сделать, вам нужен для функции способ определения, что ввод осуществляется из конвейера. Один вариант — проверить, задействован ли параметр $item (то есть используется ли он при вызове функции). Когда параметр $item задействован, ввод производится из параметра. Когда параметр $item не применяется, функция загружается из конвейера.

Переменная $PSBoundParameters — это хеш-таблица задействованных параметров, и вы можете использовать метод ContainsKey, чтобы проверить, применяется ли параметр $item, как показано в листинге 2. Во фрагменте А функция Out-Item задействует переменную $PSBoundParameters для проверки использования параметра $item. Заметьте, когда вы применяете метод ContainsKey, вы не включаете символ $ в имя параметра. Переменная $PipelineInput будет True, если параметр не задействован, и False, если параметр задействован.

Метка В показывает, как функция использует переменную $PipelineInput. Если ввод осуществляется из конвейера, функция выводит данные ($_). В другом случае функция использует команду ForEachObject, чтобы иметь доступ ко всем элемента параметра.

Установка значения параметра по умолчанию

Вы не можете использовать подход, продемонстрированный в листинге 2, если вам нужно по умолчанию установить значение параметра при использовании такого кода:

function Out-Item {
   [CmdletBinding ()]
   param (
   [Parameter (ValueFromPipeline=$TRUE)]
      $item="Default"
   )
   …
}

Установка переменной $ Pipeline­Input при использовании строки кода из метки A в листинге 2 не сработает, потому что параметр не задействован. В результате функция загружается из конвейера.

Вместо этого вы можете следовать подходу, показанному в листинге 3. Код во фрагменте А выполнит два теста, чтобы проверить, происходит ли ввод из конвейера. Нужно убедиться, что параметр не задействован или что он отсутствует.

На экране 2 виден этот код в действии. Первая команда показывает значение параметра Out-Item функции по умолчанию. Вторая команда задает ввод из конвейера. Третья обеспечивает загрузку из параметра.

 

Экран 2. Результаты работы кода листинга 3

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

  • Если ваша функция будет обрабатывать ввод из конвейера или параметра и параметр не содержит значение по умолчанию, вы можете следовать подходу листинга 2.
  • Если ваша функция будет обрабатывать ввод из конвейера или параметра, содержащего значение по умолчанию, используйте подход, показанный в листинге 3.

Билл Стюарт (bill.stewart@frenchmortuary.com) — системный и сетевой администратор компании French Mortuary, Нью-Мехико

Листинг 1. Базовый подход
function Out-Item {
Начало фрагмента А
  [CmdletBinding()]
Конец фрагмента А
  param(
Начало фрагмента B
  [Parameter(ValueFromPipeline=$TRUE)]
Конец фрагмента B
    $item
  )
Начало фрагмента C
  begin { $n = 0 }
  process {
    $item
    $n++
  }
  end { Write-Host "Output $n item(s)" }
Конец фрагмента С
}
Листинг 2. Подход без использования значения по умолчанию
function Out-Item {
  [CmdletBinding()]
  param(
  [Parameter(ValueFromPipeline=$TRUE)]
    $item
  )
  begin {
Начало фрагмента А
    $PipelineInput = `
      -not $PSBoundParameters.ContainsKey("item")
Конец фрагмента А
    Write-Host "Pipeline input? $PipelineInput"
    $n = 0
  }
  process {
Начало фрагмента B
    if ($PipelineInput) {
      $_
      $n++
    }
    else {
      $item | ForEach-Object {
        $_
        $n++
      }
    }
Конец фрагмента B
  }
  end { Write-Host "Output $n item(s)" }
}
Листинг 3. Подход с использованием значения по умолчанию
function Out-Item {
  [CmdletBinding()]
  param(
  [Parameter(ValueFromPipeline=$TRUE)]
    $item="Default"
  )
  begin {
Начало фрагмента A
    $PipelineInput = `
      (-not $PSBoundParameters.ContainsKey("item")) `
      -and (-not $item)
Конец фрагмента A
    Write-Host "Pipeline input? $PipelineInput"
    $n = 0
  }
  process {
    if ($PipelineInput) {
      $_
      $n++
    }
    else {
      $item | ForEach-Object {
        $_
        $n++
      }
    }
  }
  end { Write-Host "Output $n item(s)" }
}