Contents of this directory is archived and no longer updated.

Сегодня хочу поговорить про использование алиасов в скриптах и дать несколько практических советов при сборке текстов из фрагментов.

Алиасы

Семантика языка PowerShell подразумевает использование унифицированного синтаксиса, где название каждой команды явно говорит о том, что она делает. Например, Get-Process. Совершенно очевидно, что эта команда должна делать. Но, порой, эти команды бывают очень длинными и набирать их постоянно в консоли бывает не очень удобно. Например, самый топовый — Get-ChildItem. Это даже не самая длинная команда, просто наиболее часто используемая. Или Foreach-Object. Даже автозавершение команд не всегда спасает ситуацию. Для этого были придуманы алиасы (короткие ссылки на команды), которые очень выгодно использовать в консоли. Так же у команд есть и очень длинные параметры. Например, всякие –InputObject, –Include, –ErrorAction и т.д. PowerShell позволяет сокращать параметры первыми буквами до тех пор, пока эти буквы не будут явно указывать на конкретное название. Например, у команды Get-ChildItem параметр –Include может быть сокращён до –I, а –Exclude до –Ex. Но многие скриптописатели пишут скрипты (и выкладывают их даже где-то) с использованием этих самых алиасов и коротких обозначений параметров.

Хорошо это или плохо? Ответ очевидный — за такое надо бить больно сапогами и по лицу. Использование алиасов приводит нас обратно к одной из проблем оболочки cmd — сразу не скажешь, что делает та или иная команда. Пользователь без соответствующей подготовки вряд ли сходу скажет, что делает команда regsvr32 или что делает ключ /i этой команды. Или вот 2 примера:

gps iex* | spps -f
ls .\ -r -fo | %{cp $_.fullname -des e:\ -ea 0}

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

Get-Process iex* | Stop-Process -Force
Get-ChildItem .\ -Recurse -Force | ForEach-Object {Copy-Item $_.fullname -Destination e:\ -ErrorAction SilentlyContinue}

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

Пользователи PowerGUI Script Editor могут воспользовться адд-оном, написанным одним пошикмвп Шейем Леви (Shay Levy) — Expand Alias, который автоматически разворачивает алиасы в их полное значение.

Кавычки и here strings

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

На этой неделе мне прислали скрипт, который на выходе делает красивую HTML'ку с красивыми стилями и всё такое. И вот какое чудо (не единственное) я увидел:

$a = "<title>Телефонный справочник ООО `"Имя компании`"</title>"

# стили и заголовок окна
$a = $a + "<style>"
$a = $a + "BODY{background-color:#FFF;color:#000;font-family: tahoma; font-size: 8pt; }"
$a = $a + "TABLE{border-width: 1px;border-style: solid;border-color: #BFBFBF;border-collapse: collapse; width: 100%}"
$a = $a + "TH{border-width: 1px;padding: 0px;border-style: solid;border-color: #BFBFBF;background-color:#4E7DD1; color: #FFF}"
$a = $a + "TD{border-width: 1px;padding: 0px;border-style: solid;border-color: #BFBFBF;background-color:#FFF}"
$a = $a + "col#c1 { width: 10%;} col#c2{ width: 10%; }col#c3 {width: 10%;}col#c4 { width: 3%;}"
$a = $a + "col#c5 { width: 10%;} col#c6{ width: 11%; }col#c7 {width: 10%;}col#c8 { width: 25%;}"
$a = $a + "</style>"

Ах, какая красота. Уже в первой строке человек использует бэктик (`) для эскейпа двойных кавычек внутри текстовой строки. Зачем? Я не знаю. Ведь достаточно было заменить наружные кавычки на одинарные и всё стало бы прекрасно:

$a = '<title>Телефонный справочник ООО "Имя компании"</title>'

А какую функцию несёт переменная $a? Мы уже говорили, что название переменной должно отражать её сущность. Это сейчас понятно, что там, а чуть ниже в коде, где она будет использоваться — мы уже забудем, что там было.

Ок, идём дальше. А дальше он добавляет к этой строке другой код HTML (естественно, в виде текста). Я давно замечал, что люди или не знают или не хотят использовать here strings. Ведь можно сделать вот так:

$html = @'
<title>Телефонный справочник ООО "Имя компании"</title>
<style>
BODY{background-color:#FFF;color:#000;font-family: tahoma; font-size: 8pt; }
TABLE{border-width: 1px;border-style: solid;border-color: #BFBFBF;border-collapse: collapse; width: 100%}
TH{border-width: 1px;padding: 0px;border-style: solid;border-color: #BFBFBF;background-color:#4E7DD1; color: #FFF}
TD{border-width: 1px;padding: 0px;border-style: solid;border-color: #BFBFBF;background-color:#FFF}
col#c1 { width: 10%;} col#c2{ width: 10%; }col#c3 {width: 10%;}col#c4 { width: 3%;}
col#c5 { width: 10%;} col#c6{ width: 11%; }col#c7 {width: 10%;}col#c8 { width: 25%;}
</style>
'@

Если вам надо в коде разместить набор строк, заключайте их в here strings (с двойными кавычками, если внутри надо экспандить переменные или с одинарными, если это не надо).

Так же, в этом скрипте увидел ещё вот такую прелесть:

$menu = $menu + "<a href=`"#"+$head+"`">"+$head+"</a><br>"

А если мы снова упакуем это в here strings?

$menu += @"
<a href="#$head">$head</a><br>
"@

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

Вот ещё прелесть:

$PatternTo = "<col id=""c1""/><col id=""c2""/><col id=""c3""/><col id=""c4""/><col id=""c5""/><col id=""c6""/><col id=""c7""/><col id=""c8""/>"

И здесь мы видим груду бесполезных кавычек. А давайте это подмножество строк положим в одну строку, заключённую в одинарные кавычки:

$PatternTo = '<col id="c1"/><col id="c2"/><col id="c3"/><col id="c4"/><col id="c5"/><col id="c6"/><col id="c7"/><col id="c8"/>'

эффект тот же самый и минус 16 кавычек.

Отсюда следует вот такое правило: учитесь правильно использовать кавычки. Избегайте эскейпинг кавычек — в 99% случаев это не нужно совсем. Вместо этого надо предусмотреть использование одинарных кавычек или применять here strings, если выхода нет.

$menu= "</style>" + "<TABLE Style=`"border-color:#FFF; border-collapse: separate`"><tr><td Style=`"border-color:#FFF`">" +$menu + "</td></tr></TABLE>"

Например, здесь мы не можем обойтись просто одинарными кавычками, потому что внутри строки надо вставить значение переменной. Значит, нам поможет here strings:

$menu= @"
</style><TABLE Style="border-color:#FFF; border-collapse: separate"><tr><td Style="border-color:#FFF">$menu</td></tr></TABLE>
"@

Работа с большим объёмом текста

В PowerShell при работе с большим объёмом текста (уже начиная от мегабайта) наблюдается ощутимая деградация производительности. Например, ваш код на выходе генерирует большой XML или HTML код, следует избегать операторы конкатенации ($a = $a + "some text" или $a += "some text"). Вместо этого следует использовать StringBuilder:

# создаём объект StringBuilder
$SB = New-Object Text.StringBuilder
# добавляем фрагменты текста в коде
<...>
[void]$SB.Append("some string")
<...>
# выгружаем итоговый текст в файл:
Set-Content -Path $path -Value $SB.ToString()

В качестве примера можете посмотреть мой код в PSFCIV, который заворачивает объекты с конвейера в XML:

function _toxml_ {
# в Begin создаём заголовок XML и открывающийся тег <FCIV>
    Begin {
        $xmlstring = New-Object System.Text.StringBuilder
        [void]$xmlstring.Append('<?xml version="1.0" ?><FCIV>')
    }
# в Process будут по одному поступать объекты, которые описывают файл.
    Process {
# для каждого файла у нас будет один тег <FILE_ENTRY>
        [void]$xmlstring.Append(" <FILE_ENTRY>`n")
# чтобы вручную не создавать и не заполнять вложенные теги мы простым foreach
# перечисляем теги, какие у нас будут в XML и за счёт переменных автоматом
# создаём их в XML и заполняем их данными. Для этого свойства объектов должны
# называться так же, как и теги.
        foreach ($child in ("name", "Size", "TimeStamp", "SHA1", "MD5")) {
            [void]$xmlstring.Append(" <$child>$($_.$child)</$child>`n")
        }
# когда текущий объект обработан, закрываем тег и ждём следующий объект файла
        [void]$xmlstring.Append(" </FILE_ENTRY>`n")
    }
# когда объекты закончились, закрываем первый тег <FCIV> и подаём полученный XML дальше
    End {
        [void]$xmlstring.Append("</FCIV>`n")
        [string]$xmlstring.ToString()
    }
}

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


Share this article:

Comments:

artem

Пошик совсем деформирует мозг. Не так надо: Get-Process iex* А вот так: Get-Process -Name "iex*" Ну и всё остальное в таком же духе.

Vadims Podāns

Да, наверное, так совсем правильно.

Pavel Nagaev

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

Pavel Nagaev

И кстати фотку поменяй, ты уже совсем не такой гламурный.

Vadims Podāns

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

Comments are closed.