(см. листинг 1).

Собственные возможности PowerShell

С помощью имеющихся в PowerShell операторов -like, -match и -replace пользователи, мало знакомые с программированием, могут без труда сопоставлять и заменять строки. В командной строке PowerShell проще экспериментировать с регулярными выражениями. NET, чем в традиционных языках сценариев. Регулярное выражение (иногда именуемое regex) — строка, содержащая специальные символы или последовательности символов, которые представляют другие символы или их последовательности. Регулярные выражения похожи на универсальные символы, но гораздо выразительнее. Знакомство с регулярными выражениями можно начать с раздела about_regular_expressions в «Справке PowerShell». Для просмотра раздела введите

Get-Help about_regular_expressions

в командной строке PowerShell.

Очевидная область применения регулярных выражений — замена строк в файлах. Например, требуется извлечь только имена компьютеров из вывода команды Net View. Если запустить команду

Net View > List.txt

а затем открыть файл List.txt в программе «Блокнот», то видно, что в нем присутствует много разнообразной избыточной информации, в том числе заголовки столбцов, дополнительные пробелы в конце каждой строки и нижняя строка, в которой сообщается об успешном завершении команды. С помощью регулярных выражений в PowerShell можно извлечь из вывода только имена компьютеров. Например, рассмотрим команду

Get-Content List.txt | Where-Object {$_ -match '^\\\\'} |
   ForEach-Object {$_ -replace
'^\\\\ (\S+).+','$1'}

Хотя команда показана на нескольких строках, ее следует вводить в одной строке в консоли PowerShell. То же относится и к другим командам, разнесенным по нескольким строкам. Команда Get-Content извлекает отдельные строки из List.txt. Затем команда Where-Object с помощью оператора -match проверяет, начинается ли строка с двух обратных косых черт. Такие строки передаются в команду ForEach-Object, где с помощью оператора -replace отбираются только имена компьютеров.

Остановимся подробнее на операторе -match, который используется с последовательностью ^\\\\. Символ каре (^) указывает, что нужно сопоставить начальные символы. Косая обратная черта — управляющий символ для регулярных выражений, поэтому нужно использовать две косые обратные черты (\\) для представления одной косой обратной черты (\). Таким образом, в общей сложности получается четыре косые обратные черты. В таблице 1 описаны этот и другие шаблоны регулярных выражений, используемые в команде.

 

Таблица 1. Шаблоны в коде извлечения

Сценарий Replace-FileString.ps1

Часто при работе с текстовыми файлами бывает полезно заменить строки в файле с использованием регулярных выражений, а затем записать результаты в первоначальный файл. В PowerShell нет команды с такой функциональностью, поэтому я подготовил сценарий Replace-FileString.ps1. Его действие аналогично открытию файла в программе «Блокнот», выполнению операции поиска и замены и сохранению файла. Но, в отличие от «Блокнота», этот сценарий можно использовать для замены строк в нескольких файлах одновременно.

Replace-FileString.ps1 обеспечивает поиск с переходом через разрывы строк, так как для считывания текста вместо команды Get-Content используется метод ReadAllText класса System.IO.File из Windows.NET Framework. В отличие от Get-Content, который считывает строки файла только по одной, метод ReadAllText читает каждый файл как одну строку, поэтому находит строки с разрывами. Из-за того что каждый файл рассматривается как одна строка, обработка очень больших файлов замедляется.

Для Replace-FileString.ps1 требуется PowerShell 2.0. Параметры командной строки для сценария приведены в таблице 2. Кроме параметров, перечисленных в таблице 2, сценарий распознает типовые параметры -Confirm, -Verbose и -WhatIf.

 

Таблица 2. Параметры сценария Replace-FileString.ps1

Существует два обязательных параметра: -Pattern и -Replacement. Также необходимо указать файл, в котором будут выполнены поиск и замена строк. Сделать это можно двумя способами. Первый способ — использовать параметр -Path или -LiteralPath в команде следующего вида:

Replace-FileString
   -Pattern 'this'
   -Replacement 'that'
   -Path Test.txt

Обычно нет нужды использовать параметр -LiteralPath, если только не требуется указать путь или имя файла с символами, часто интерпретируемыми PowerShell как универсальные. Типичный пример — квадратные скобки, [].

Второй способ — направить объекты файла в сценарий с использованием такой команды, как

Get-Item Test.txt | Replace-FileString
   -Pattern 'this' -Replacement ‘that’

Чтобы продемонстрировать использование Replace-FileString.ps1, рассмотрим три примера применения. В них показано, как с помощью сценария преобразовать вывод Net View в список имен компьютеров с разделительными запятыми, заменить данные в. ini-файлах и заменить путь LDAP в наборе сценариев.

Пример применения 1

Предположим, что требуется преобразовать вывод Net View в список имен компьютеров с разделителями запятыми. Сначала можно использовать команду Net View с операторами -match и -replace для формирования списка имен компьютеров, например:

Net View | Where-Object
   {$_ -match '^\\\\'} |
   ForEach-Object {
   $_ -replace
   '^\\\\ (\S+).+','$1'} |
   Out-File Computers.txt

Описания шаблонов регулярных выражений можно найти в таблице 1. Файл Computers.txt содержит имена компьютеров из команды Net View, по одному имени в строке. Затем можно заменить в файле переходы на новую строку запятыми с помощью команды

Replace-FileString -Pattern '\r\n'
   -Replacement ','
   -Path Computers.txt

Эта команда заменяет запятыми все символы \r\n (возврат каретки и перевод строки) в файле и выводит файл. При необходимости можно добавить параметр -Overwrite, чтобы заменить исходный файл измененной копией, например:

Replace-FileString -Pattern '\r\n'
   -Replacement ','
   -Path Computers.txt
   -Overwrite

Пример применения 2

Предположим, у нас имеется приложение клиент-сервер MyApplication. Несколько компьютеров в сети используют клиентское приложение для подключения к серверу appserver1 через TCP-порт 7840. Чтобы устранить уязвимое место, администратор информационной защиты распорядился выполнять серверный компонент приложения на другом сервере (appserver2) и использовать другой порт (8740). Клиентское приложение хранит имя сервера и номер TCP-порта в файле C:\Program Files\My Application\MyApp.ini. На экране 1 показана соответствующая часть MyApp.ini для компьютера с именем acct15.

 

Экран 1. MyApp.ini

Нельзя просто передать обновленный экземпляр файла MyApp.ini на соответствующие клиентские компьютеры, так как этот файл содержит сведения о клиенте (в данном случае имя компьютера). Но можно использовать Replace-FileString.ps1 для обновления. ini-файлов в компьютерах.

Сначала необходимо поместить имена компьютеров, на которых выполняется клиентское приложение, в текстовый файл Clients.txt, по одному имени на строке (экран 2). Затем можно использовать команду в листинге 2, чтобы изменить файлы MyApp.ini.

 

Экран 2. Clients.txt

В этой команде Get-Content извлекает имя каждого компьютера из Clients.txt. Команда ForEach-Object получает файл MyApp.ini с каждого компьютера с помощью команды Get-Item.

Наконец, команда использует полученный объект-файл как входной для сценария Replace-FileString.ps1. С помощью шаблона и строк замены, описанных в таблице 3, наряду с параметром -Overwrite сценарий вносит изменения в MyApp.ini. Механизм регулярных выражений. NET не поддерживает управляющие последовательности (такие, как \r и \n) в строке замены, поэтому в команде используются круглые скобки и символы $1 для вставки разрыва строки.

 

Таблица 3. Шаблон для примера применения 2

Пример применения 3

Предположим, набор сценариев VBScript для управления системой был загружен из веб-узла в папку C:\SampleScripts на компьютере. Все сценарии содержат типовой LDAP-путь DC=fabrikam, DC=com. Вместо того чтобы редактировать сценарии по одному, можно использовать Replace-FileString.ps1, чтобы заменить типовой путь LDAP на путь LDAP для конкретной сети. Для этого достаточно выполнить команду

Replace-FileString
   -Pattern 'DC=fabrikam, DC=com'
   -Replacement 'your LDAP path'
   -Path C:\SampleScripts\*.vbs
   -Overwrite

Где ‘your LDAP path’ — путь LDAP конкретной сети.

Регулярные выражения — удобное средство изменения файлов

В PowerShell нет собственной команды для замены строк в файлах, но этот изъян можно устранить с помощью Replace-FileString.ps1. Сценарий позволяет просто заменять строки в одном или нескольких файлах с использованием регулярных выражений.

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

Листинг 1. Replace-FileString.ps1
# Written by Bill Stewart (bstewart@iname.com)
# Replaces strings in files using a regular expression. Supports
# multi-line searching and replacing.
# requires -version 2

<#
.SYNOPSIS
Replaces strings in files using a regular expression.

.DESCRIPTION
Replaces strings in files using a regular expression. Supports
multi-line searching and replacing.

.PARAMETER Pattern
Specifies the regular expression pattern.

.PARAMETER Replacement
Specifies the regular expression replacement pattern.

.PARAMETER Path
Specifies the path to one or more files. Wildcards are permitted. Each file is read entirely into memory to support multi-line searching and replacing, so performance may be slow for large files.

.PARAMETER LiteralPath
Specifies the path to one or more files. The value of the this parameter is used exactly as it is typed. No characters are interpreted as wildcards. Each file is read entirely into memory to support multi-line searching and replacing, so performance may be slow for large files.

.PARAMETER CaseSensitive
Specifies case-sensitive matching. The default is to ignore case.

.PARAMETER Multiline
Changes the meaning of ^ and $ so they match at the beginning and end, respectively, of any line, and not just the beginning and end of the entire file. The default is that ^ and $, respectively, match the beginning and end of the entire file.

.PARAMETER UnixText
Causes $ to match only linefeed (\n) characters. By default, $ matches carriage return+linefeed (\r\n). (Windows-based text files usually use \r\n as line terminators, while Unix-based text files usually use only \n.)

.PARAMETER Overwrite
Overwrites a file by creating a temporary file containing all replacements and then replacing the original file with the temporary file. The default is to output but not overwrite.

.PARAMETER Force
Allows overwriting of read-only files. Note that this parameter cannot
override security restrictions.

.PARAMETER Encoding
Specifies the encoding for the file when -Overwrite is used. Possible values are: ASCII, BigEndianUnicode, Unicode, UTF32, UTF7, or UTF8. The default value is ASCII.

.INPUTS
System.IO.FileInfo.
.OUTPUTS
System.String without the -Overwrite parameter, or nothing with the
-Overwrite parameter.

.LINK
about_Regular_Expressions

.EXAMPLE
C:\>Replace-FileString.ps1 '(Ferb) and (Phineas)' '$2 and $1' Story.txt This command replaces the string 'Ferb and Phineas' with the string 'Phineas and Ferb' in the file Story.txt and outputs the file. Note that the pattern and replacement strings are enclosed in single quotes to prevent variable expansion.

.EXAMPLE
C:\>Replace-FileString.ps1 'Perry' 'Agent P' Ferb.txt -Overwrite
This command replaces the string 'Perry' with the string 'Agent P' in
the file Ferb.txt and overwrites the file.
#>

[CmdletBinding(DefaultParameterSetName="Path",
               SupportsShouldProcess=$TRUE)]
param(
  [parameter(Mandatory=$TRUE,Position=0)]
    [String] $Pattern,
  [parameter(Mandatory=$TRUE,Position=1)]
    [String] [AllowEmptyString()] $Replacement,
  [parameter(Mandatory=$TRUE,ParameterSetName="Path",
    Position=2,ValueFromPipeline=$TRUE)]
    [String[]] $Path,
  [parameter(Mandatory=$TRUE,ParameterSetName="LiteralPath",
    Position=2)]
    [String[]] $LiteralPath,
    [Switch] $CaseSensitive,
    [Switch] $Multiline,
    [Switch] $UnixText,
    [Switch] $Overwrite,
    [Switch] $Force,
    [String] $Encoding="ASCII"
)

begin {
  # Throw an error if $Encoding is not valid.
  $encodings = @("ASCII","BigEndianUnicode","Unicode","UTF32","UTF7",
                 "UTF8")
  if ($encodings -notcontains $Encoding) {
    throw "Encoding must be one of the following: $encodings"
  }

  # Extended test-path: Check the parameter set name to see if we
  # should use -literalpath or not.
  function test-pathEx($path) {
    switch ($PSCmdlet.ParameterSetName) {
      "Path" {
        test-path $path
      }
      "LiteralPath" {
        test-path -literalpath $path
      }
    }
  }

  # Extended get-childitem: Check the parameter set name to see if we
  # should use -literalpath or not.
  function get-childitemEx($path) {
    switch ($PSCmdlet.ParameterSetName) {
      "Path" {
        get-childitem $path -force
      }
      "LiteralPath" {
        get-childitem -literalpath $path -force
      }
    }
  }

  # Outputs the full name of a temporary file in the specified path.
  function get-tempname($path) {
    do {
      $tempname = join-path $path ([IO.Path]::GetRandomFilename())
    }
    while (test-path $tempname)
    $tempname
  }

  # Use '\r$' instead of '$' unless -UnixText specified because
  # '$' alone matches '\n', not '\r\n'. Ignore '\$' (literal '$').
  if (-not $UnixText) {
    $Pattern = $Pattern -replace '(?
Листинг 2. Команда для изменения файлов MyApp.ini
Get-Content Clients.txt | ForEach-Object {
  Get-Item "\\$_\C$\Program Files\My Application\MyApp.ini" } |
  Replace-FileString -Pattern 'Server=appserver1(\r\n)Port=7840'
  -Replacement 'Server=appserver2$1Port=8740' -Overwrite