Для пользователей Windows, привыкших работать с командной оболочкой Cmd.exe, переход на Windows PowerShell означает смену парадигмы. PowerShell — гораздо более мощная и универсальная среда, однако команды Cmd.exe в большинстве своем не имеют прямых эквивалентов в PowerShell. К примеру, в среде PowerShell по умолчанию используется псевдоним dir, запускающий команду Get-ChildItem, но эта команда функционирует не совсем так, как команда dir оболочки Cmd.exe.

Работая с интерпретатором Cmd.exe, я, пожалуй, чаще всего пользуюсь командой dir; поэтому, когда я запускаю среду PowerShell, то чувствую, что мне не хватает некоторых возможностей этой команды — и прежде всего ее параметров /a (выбор атрибутов) и /o (порядок сортировки). В таблице 1 представлены некоторые примеры команд dir строки Cmd.exe и их эквиваленты в PowerShell. Как можно заметить, все команды PowerShell длиннее и во многих случаях сложнее, нежели соответствующие команды dir интерпретатора Cmd.exe. Чтобы ускорить работу в командной строке PowerShell, я написал сценарий D.ps1, который эмулирует ряд самых полезных средств команды dir.

Знакомство со сценарием D.ps1

Сценарий D.ps1 представлен в листинге. В таблице 2 описываются параметры командной строки D.ps1. Главная особенность, отличающая работу со сценарием D.ps1 от использования команды dir оснастки Cmd.exe, состоит в том, что при указании параметров вместо прямого слэша (/) используется дефис (-). Все параметры сценария являются необязательными. При запуске сценария без параметров он выводит на экран содержимое текущего каталога.

Как и команда dir, сценарий D.ps1 вычисляет количество файлов и каталогов, общую длину файлов и приводит эти показатели в конце своих выходных данных. На экране 1 показан пример выходных данных сценария D.ps1, в которых отображаются хранящиеся в текущем каталоге файлы сценариев, отсортированные по дате. Отметим, что, в отличие от выходных данных команды Get-ChildItem и команды dir, выходные данные D.ps1 не включают в себя пути к отображаемому каталогу. Кроме того, D.ps1 отображает (как и команда Get-ChildItem) атрибуты всех файлов; команда dir этих атрибутов не показывает. В таблице 3 представлены некоторые примеры использования команды D.ps1 и описание каждой команды.

По умолчанию выходные данные D.ps1 представляют собой не объекты файловой системы, а отформатированные строки. Чтобы указать в выходных данных объекты или перечислить элементы, находящиеся не в том месте, где располагается файловая система, следует ввести параметр -defaultoutput, как показано в таблице 2. Если текущий путь не находится внутри файловой системы (например, HKCU:) и вы не указываете путь к списку, D.ps1 — в случае, когда не указан параметр -defaultoutput, — выдает сообщение об ошибке.

Сценарий состоит из инструкции param, которая определяет параметры командной строки сценария, и шести функций: usage, iif, get-attributeflags, get-orderlist, get-providername и main (функция main). В последней строке сценария выполняется функция main, которая по мере надобности вызывает другие функции.

Функции usage и iif

Если в командной строке присутствует параметр -help, функция main выполняет функцию usage, показанную во фрагменте A листинга 1. Функция usage просто выводит сообщение об использовании и завершает сценарий инструкцией exit.

Функция iif обеспечивает краткую запись следующей часто используемой синтаксической конструкции:

if (условие) {$переменная = значение_истина} else {$переменная = значение_ложь}

Используя функцию iif, вместо приведенной выше конструкции можно написать:

$переменная = iif {условие}
   {значение_истина} {значение_ложь}

Функция iif показана во фрагменте B листинга 1. В качестве параметров она использует три блока сценария. Функция выполняет первый блок сценария ($expr); если результат — истина, сценарий выполняет второй блок сценария; в противном случае выполняется третий блок сценария.

Функция attributeflags

Функция main с помощью функции get-attributeflags преобразует аргумент -attributes (строку, содержащую список атрибутов файлов, которые необходимо включить в два двоичных значения либо исключить из них). Тем, кто незнаком с двоичными величинами, следует прочесть врезку «Что такое двоичные значения».

Представленная во фрагменте C функция get-attributeflags прежде всего создает таблицу хеширования, содержащую символы атрибутов и ассоциированные с ними величины двоичной маски .NET. Затем она создает строку на базе ключей хеш-таблицы, выполняет операцию над каждым символом в строке атрибута и с помощью оператора switch принимает решение относительно того, следует ли устанавливать разряды в возвращаемых значениях. Если символом атрибута не является тире или символ некорректен, функция возвращает ошибку, и выполнение сценария завершается. Последняя строка функции get-attributeflags возвращает два двоичных значения главной функции; эти значения позднее используются сценарием.

Функция get-orderlist

Во фрагменте D представлена функция get-orderlist. Функция main использует функцию get-orderlist для того, чтобы возвратить список таблиц хеширования, определяющих порядок сортировки листинга каталогов. Функция main передает функции get-orderlist три параметра: аргумент -order (строка, содержащая требуемый порядок сортировки), поле имени (свойство, используемое в процессе сортировки по имени) и поле времени (свойство, применяемое в процессе сортировки по дате). Каждая хеш-таблица имеет два ключа: Expression и Ascending. Ключ expression в каждой хеш-таблице — это выражение для сортировки, а ключ Ascending может содержать либо значение $TRUE (для сортировки в порядке возрастания), либо $FALSE (для сортировки в порядке убывания).

Функция get-orderlist действует так же, как функция get-attributeflags. Она создает таблицу хеширования, содержащую символы порядка сортировки, формирует строку на основе ключей хеш-таблицы, выполняет операцию над каждым символом в строке порядка сортировки и использует оператор switch с целью возвращения хеш-таблицы для каждого корректного символа. Если символ порядка сортировки некорректен, функция выдает ошибку. Функция main использует таблицу хеширования (или таблицы, если возвращается не одна, а несколько таблиц), возвращенную функцией get-orderlist с командой Sort-Object на более поздних этапах выполнения сценария.

Функция get-providername

Главная функция использует функцию get-providername, представленную во фрагменте E, для определения провайдера маршрута (например, FileSystem, Registry, Certificate). Эта функция возвращает пустую строку, если путь не существует. Первым делом данная функция задает значение переменной $result в виде пустой строки; затем с помощью функции iif она устанавливает значение переменной $pathArg равным -literalpath или -path, в зависимости от состояния параметра сценария -literalpath. Далее эта функция присваивает переменной $ErrorActionPreference значение SilentlyContinue, с тем чтобы среда PowerShell не выдавала сообщений об ошибке в случае возникновения таковой.

Далее функция обращается к команде Test-Path, чтобы выяснить, существует ли путь. Однако она не выполняет команду Test-Path непосредственно. Для того чтобы команда могла поддерживать параметр сценария -literalpath, функция использует команду Invoke-Expression. Если путь существует, сценарий использует команду Invoke-Expression (опять-таки с целью обеспечения совместимости с параметром -literalpath) для того, чтобы выполнить команду Get-Item и выбрать первый возвращаемый объект. Затем функция назначает переменной $result свойство Name объекта PSProvider. Последняя строка функции выдает в качестве выходных данных переменную $result.

Функция main

Функция main представляет собой основное тело сценария. Прежде всего, если присутствует параметр -help, функция main вызывает функцию usage, которая будет отображать информацию об использовании и завершает выполнение сценария. Если же параметр -help отсутствует, функция проверяет наличие параметра -path. При отсутствии последнего функция исходит из того, что пользователь хочет получить перечень элементов текущего каталога.

Далее функция main обращается к функции iif, задача которой — присвоить переменной $pathArg значение -path или -literalpath в зависимости от того, имеется ли в сценарии параметр -literalpath. После этого функция выясняет, имеется ли параметр -attributes. Если такой параметр присутствует, функция main вызывает функцию get-attributeflags, которая считывает два двоичных значения, соответствующие параметру -attributes.

Если имеется параметр -timefield, функция main с помощью оператора switch проверяет первый символ параметра (т. е. переменной $TimeField). Если аргумент начинается с символа a, c или w, это указывает на параметр LastAccessTime, CreationTime или LastWriteTime, соответственно. Если аргумент -timefield начинается не с символов a, c или w, функция main выдает сообщение об ошибке, и выполнение сценария завершается. Если же параметр -timefield отсутствует, функция main определяет значение переменной $TimeField равным LastWriteTime. Чтобы определить, какое свойство файла следует отобразить или использовать в процессе сортировки, функция использует переменную $TimeField.

Если используются параметры -fullname или -recurse, функция main вызывает функцию iif, которая устанавливает для переменной $nameField значение FullName; в противном случае переменной задается значение Name. Функция использует переменную $nameField для определения того, какое свойство следует использовать при отображении или сортировке файлов.

На данный момент функция main обработала параметры командной строки сценария и готова выполнить составную команду Get-ChildItem. Однако сценарий может реализовать лишь параметры -attributes и -order, передавая выходные данные Get-ChildItem по конвейеру другим командам. Функция main решает эту задачу, формируя строку, которая содержит конвейер. Как следует из кода, представленного во фрагменте F, функция main формирует строку конвейера следующим образом:

  1. Если присутствует параметр -recurse, функция присоединяет его к конвейеру.
  2. Если присутствует параметр -attributes, функция присоединяет к конвейеру параметр -force.
  3. Если оба двоичных значения атрибута не равны нулю, функция main присоединяет к конвейеру блок сценария Where-Object; последний не является необходимым, если пользователь хочет увидеть все файлы вне зависимости от их атрибутов. В блоке сценария Where-Object используются методы сопоставления, описанные во врезке «Что такое двоичные значения». С их помощью создается фильтр, который включает или исключает атрибуты файлов в зависимости от двух двоичных значений, возвращаемых функцией get-attributeflags.
  4. При наличии параметра -order функция main присоединяет к конвейеру составную команду Sort-Object.

Перед некоторыми ссылками на переменные в строке конвейера функция предусматривает использование символа экранирования «обратная кавычка» (`). Это необходимо для того, чтобы в среде PowerShell упомянутые переменные не раскрывались в строках.

Далее функция main устанавливает, используется ли параметр -defaultoutput. В случае положительного ответа функция выполняет составную команду Get-ChildItem с использованием команды Invoke-Expression; затем выполняется инструкция возвращения, которая завершает работу функции main, после чего завершается выполнение сценария.

Если же параметр -defaultoutput не используется, функция main создает строку, содержащую отформатированное строковое выражение с оператором -f. Позднее функция использует команду Invoke-Expression с целью вывода этой строки для каждого отображаемого ею элемента файловой системы. Отформатированное строковое выражение содержит поля в выражении, оператор -f и следующие свойства для каждого элемента:

  • режим (Mode);
  • дата (Date);
  • время (Time);
  • длина (Length);
  • владелец файла (при наличии оператора -q);
  • имя (Name).

После этого функция main инициализирует три переменных цикла ($dirCount, $fileCount и $sizeTotal) со значением «нуль» и с помощью цикла foreach проходит по каждому пути, указанному в параметре -path. Внутри цикла foreach функция с помощью оператора switch определяет, что делать с результатами выполнения функции get-providername. Если провайдером является FileSystem, функция использует команду Invoke-Expression для обращения к команде Get-ChildItem с текущим путем и конвейером; после этого результаты передаются блоку сценария ForEach-Object.

Внутри блока сценария ForEach-Object функция main устанавливает, используется ли параметр -bare. Если этого параметра нет, функция вызывает форматированное строковое выражение, созданное ею же ранее. Предположим, обрабатываемый элемент не является каталогом (т. е. свойство элемента Attributes не имеет установленного разряда Directory); тогда функция увеличивает значение переменных $fileCount и $sizeTotal; в противном случае функция увеличивает значение переменной $dirCount.

Другой случай: параметр -bare используется. В этой ситуации функция main выдает только свойство элемента Name или FullName (в зависимости от значения переменной $nameField). Если путь не находится в файловой системе или не существует, функция main выдает сообщение об ошибке с помощью команды Write-Error и переходит к следующему пути в рамках цикла foreach.

По завершении блока сценария ForEach-Object функция main вновь проверяет код на наличие параметра -bare. При отсутствии параметра функция выдает отформатированную строку, содержащую переменные $fileCount, $sizeTotal и $dirCount, если значение переменной $dirCount или $fileCount отлично от нуля.

Главное — производительность

Ограничения, присущие команде Get-ChildItem оболочки PowerShell, не должны приводить к снижению темпа вашей работы, если вы привыкли пользоваться командой dir командной оболочки Cmd.exe. Возьмите на вооружение сценарий D.ps1. Укажите его в Path, и составление списков каталогов в системе будет занимать гораздо меньше времени.

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


Таблица 1. Команды Cmd.exe и их эквиваленты в оболочке PowerShell

Таблица 2. Параметры командной строки сценария D.ps1

Таблица 3. Пример команд с D.ps1

Экран 1. Результаты работы D.ps1

Листинг. D.ps1

Листинг. D.ps1 (продолжение)


Что такое двоичные значения

В сценарии D.ps1, который приводится в основной статье, двоичные значения используются для передачи данных из одних функций в другие. Двоичное значение - это компактный способ хранения списка логических значений (истина/ложь). Их можно представлять скорее как двоичные массивы, а не дискретные числа. Каждый разряд в таком числе устанавливается равным единице (включен) или нулю (отключен). Двоичные значения можно сочетать с маской с помощью двоичных операторов (в среде Windows PowerShell это операторы -band, - bor, - bnot и -bxor); такое сочетание позволяет считывать, устанавливать, восстанавливать двоичные значения и переключать их из одного состояния в другое. Маска - это число, которое обозначает представляющие для вас интерес разряд или разряды. Значения масок всегда являются степенями двойки.

Атрибуты файлов - хороший пример того, как операционная система использует двоичные значения. Следующая команда PowerShell формирует список .NET System.IO.FileAttributes и ассоциированное значение маски для каждого атрибута:

[Enum]:: GetValues ([System.IO.
FileAttributes]) | select-object @
{"Name" = "Name"; «Expression" = {$_}},
@{"Name" = "Value"; "Expression" = {[Int]
$_}} | format-table -auto

На экране A представлена эта команда и ее выходные данные.

Здесь видно, что маска, равная 1, представляет атрибут «только для чтения», маска 2 (10 в двоичном счислении) представляет атрибут «скрытый», 4 (100 в двоичном счислении) - «системный атрибут», 16 (1000 в двоичном счислении) представляет атрибут «каталог» и т. д. Для создания многоразрядной маски можно использовать несколько значений в сочетании с оператором -bor. Так, маска 2-or 32 сочетает скрытый (2) атрибут и атрибут архивирования (32). Получить атрибуты файла можно, считывая его свойство Attributes, которое на самом деле представляет собой .NET-тип System.IO.FileAttributes. Если вы представляете свойство как целое число, используя [Int], то получаете двоичное значение свойства Attributes. Рассмотрим следующий пример:

PS > $attrs = (get-item c: est.txt).
AttributesPS > $attrsReadOnly,
ArchivePS > [Int] $attrs33

Первая команда считывает атрибуты файла c: est.txt; вторая команда отображает переменную $attrs, которую PowerShell выводит как список атрибутов; а третья команда отображает переменную $attrs в виде целого числа (т. е. ее соответствующее двоичное значение).

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

PS > [System.IO.FileAttributes]:: ReadOnly
-eq 1 TruePS > [System.IO.
FileAttributes]:: Hidden -eq 2 True

В публикуемых ниже формулах описываются способы установления и изменения разрядов в двоичном значении.

  • Проверка на наличие хотя бы одного установленного разряда: (bitmap -band mask) -ne 0
  • Проверка, являются ли все разряды установленными: (bitmap -band mask) -eq bitmap
  • Проверка, являются ли все разряды неустановленными: (bitmap -band mask) -eq 0
  • Установка одного или нескольких разрядов: bitmap = bitmap -bor mask
  • Восстановление одного или нескольких разрядов: bitmap = bitmap -band (-bnot mask)
  • Перевод одного или нескольких разрядов из одного состояния в другое: bitmap = bitmap -bxor mask

С помощью переменной $attrs из предыдущего примера мы можем использовать такие фрагменты кода, как:

  • ($attrs -band 1) -ne 0 # Истинно, поскольку бит только для чтения установлен
  • ($attrs -band 2) -ne 0 # Ложно, поскольку скрытый бит не установлен
  • ($attrs -band 32) -ne 0 # Истинно, поскольку бит архивирования установлен
  • ($attrs -band (1-or 32)) -eq $attrs # Истинно, поскольку установлен как бит только для чтения, так и бит архивирования

В сценарии D.ps1, который описан в основной статье, двоичные значения и маски используются в функции get-attributeflags и в функции main.

Экран A. Атрибуты и связанные с ними значения масок в PowerShell