Подводные камни Powershell - PullRequest
       30

Подводные камни Powershell

24 голосов
/ 29 апреля 2009

В какие ловушки Powershell вы попадаете? :-)

Шахты:

# -----------------------------------
function foo()
{
    @("text")
}

# Expected 1, actually 4.
(foo).length

# -----------------------------------
if(@($null, $null))
{
    Write-Host "Expected to be here, and I am here."
}

if(@($null))
{
    Write-Host "Expected to be here, BUT NEVER EVER."
}

# -----------------------------------

function foo($a)
{
    # I thought this is right.
    #if($a -eq $null)
    #{
    #    throw "You can't pass $null as argument."
    #}

    # But actually it should be:
    if($null -eq $a)
    {
        throw "You can't pass $null as argument."
    }
}

foo @($null, $null)

# -----------------------------------

# There is try/catch, but no callstack reported.
function foo() 
{
   bar
}

function bar() 
{
  throw "test"
}

# Expected:
#  At bar() line:XX
#  At foo() line:XX
#  
# Actually some like this:
#  At bar() line:XX
foo

Хотелось бы знать ваши, чтобы выгуливать их: -)

Ответы [ 20 ]

13 голосов
/ 29 апреля 2009

Мой личный фаворит

function foo() {
  param ( $param1, $param2 = $(throw "Need a second parameter"))
  ...
}

foo (1,2)

Для тех, кто не знаком с PowerShell, эта строка выбрасывается, потому что вместо передачи 2 параметров фактически создает массив и передает один параметр. Вы должны назвать это следующим образом

foo 1 2
10 голосов
/ 29 апреля 2009

Еще один забавный. Необработанное выражение по умолчанию записывает его в конвейер. Действительно раздражает, когда вы не понимаете, какая-то функция возвращает значение.

function example() {
  param ( $p1 ) {
  if ( $p1 ) {
    42
  }
  "done"
}

PS> example $true 
42
"done"
10 голосов
/ 01 мая 2009
$files = Get-ChildItem . -inc *.extdoesntexist
foreach ($file in $files) {
    "$($file.Fullname.substring(2))"
}

Сбой с:

You cannot call a method on a null-valued expression.
At line:3 char:25
+ $file.Fullname.substring <<<< (2)

Исправьте это так:

$files = @(Get-ChildItem . -inc *.extdoesntexist)
foreach ($file in $files) {
    "$($file.Fullname.substring(2))"
}

Суть в том, что оператор foreach будет зацикливаться на скалярном значении, даже если это скалярное значение равно $ null. Когда Get-ChildItem в первом примере ничего не возвращает, $ files получает $ null. Если вы ожидаете, что команда вернет массив элементов, но есть вероятность, что она вернет только 1 элемент или ноль элементов, поместите @ () вокруг команды. Тогда вы всегда получите массив - будь то 0, 1 или N элементов. Примечание. Если элемент уже является массивом, добавление @() не имеет никакого эффекта - он все равно будет тем же самым массивом (т. Е. Нет дополнительной оболочки-массива).

7 голосов
/ 08 мая 2009
# The pipeline doesn't enumerate hashtables.
$ht = @{"foo" = 1; "bar" = 2}
$ht | measure

# Workaround: call GetEnumerator
$ht.GetEnumerator() | measure
6 голосов
/ 29 апреля 2009
3 голосов
/ 01 октября 2011

в функции ...

  • Тонкости обработки конвейерного ввода в функции в отношении использования $_ или $input и в отношении блоков begin, process и end.
  • Как обрабатывать шесть основных классов эквивалентности входных данных, доставляемых в функцию (без ввода, ноль, пустая строка, скаляр, список, список с нулевым и / или пустым) - для обоих прямой ввод и конвейер введите - и получите то, что ожидаете .
  • Правильный синтаксис вызова для отправки нескольких аргументов функции.

Я обсуждаю эти и другие подробности в своей статье на Simple-Talk.com Вниз по кроличьей норе - исследование по конвейерам, функциям и параметрам PowerShell , а также прилагаю настенную диаграмму - здесь приведен краткий обзор, показывающий различные ловушки синтаксиса вызова для функции, принимающей 3 аргумента: function syntax pitfalls


На модулях ...

Эти пункты изложены в моей статье на Simple-Talk.com Далее по кроличьей норе: модули PowerShell и инкапсуляция .

  • Точка поиска файла внутри скрипта с использованием относительного пути относительно вашего текущего каталога - не каталога, в котором находится скрипт! Чтобы относиться к сценарию, используйте эту функцию, чтобы найти каталог сценария: [Обновление для PowerShell V3 +: просто используйте встроенную переменную $PSScriptRoot!]

    function Get-ScriptDirectory
    { Split-Path $script:MyInvocation.MyCommand.Path }
    
  • Модули должны храниться как ...Modules\name\name.psm1 или ...\Modules\any_subpath\name\name.psm1. То есть вы не можете просто использовать ...Modules\name.psm1 - имя непосредственного родителя модуля должно совпадать с базовым именем модуля. На этой диаграмме показаны различные режимы сбоев при нарушении этого правила:

module naming requirements


2015.06.25 Справочная таблица ловушек

Simple-Talk.com только что опубликовал последний из моих триумвиратов углубленных статей о подводных камнях PowerShell. Первые две части в форме викторины, которая поможет вам оценить выбранную группу ловушек; последняя часть представляет собой настенную диаграмму (хотя для этого понадобится комната с довольно высокими потолками), содержащую 36 наиболее распространенных ошибок (некоторые из которых адаптированы из ответов на этой странице), где приведены конкретные примеры и обходные пути для большинства. Подробнее здесь .

3 голосов
/ 11 августа 2011

Существуют некоторые приемы построения командных строк для утилит, которые не были созданы с учетом Powershell:

  • Чтобы запустить исполняемый файл, имя которого начинается с цифры, предварите его амперсандом (&).

& 7zip.exe

  • Чтобы запустить исполняемый файл с пробелом в любом месте пути, предварите его амперсандом (&) и заключите в кавычки, как в любой строке. Это означает, что строки в переменной также могут быть выполнены.

# Executing a string with a space. & 'c:\path with spaces\command with spaces.exe'

# Executing a string with a space, after first saving it in a variable. $a = 'c:\path with spaces\command with spaces.exe' & $a

  • Параметры и аргументы передаются в устаревшие утилиты позиционно. Поэтому важно процитировать их так, как утилита ожидает их увидеть. Как правило, можно заключить в кавычки, когда он содержит пробелы или не начинается с буквы, цифры или тире (-).

C:\Path\utility.exe '/parameter1' 'Value #1' 1234567890

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

$b = 'string with spaces and special characters (-/&)' utility.exe $b

  • В качестве альтернативы может использоваться расширение массива для передачи значений.

$c = @('Value #1', $Value2) utility.exe $c

  • Если вы хотите, чтобы Powershell ждал завершения приложения, вы должны использовать выходные данные, либо передав их по конвейеру, либо используя Start-Process.

# Saving output as a string to a variable. $output = ping.exe example.com | Out-String

# Piping the output. ping stackoverflow.com | where { $_ -match '^reply' }

# Using Start-Process affords the most control. Start-Process -Wait SomeExecutable.com

  • Из-за способа отображения своего вывода некоторые служебные программы командной строки будут зависать при запуске внутри Powershell_ISE.exe, особенно в ожидании ввода от пользователя. Эти утилиты обычно работают нормально при запуске в консоли Powershell.exe.
3 голосов
/ 29 мая 2009

Скажем, у вас есть следующий XML-файл:

<Root>
    <Child />
    <Child />
</Root>

Запустите это:

PS > $myDoc = [xml](Get-Content $pathToMyDoc)
PS > @($myDoc.SelectNodes("/Root/Child")).Count
2
PS > @($myDoc.Root.Child).Count
2

Теперь отредактируйте файл XML, чтобы у него не было дочерних узлов, а только корневой узел, и снова запустите эти операторы:

PS > $myDoc = [xml](Get-Content $pathToMyDoc)
PS > @($myDoc.SelectNodes("/Root/Child")).Count
0
PS > @($myDoc.Root.Child).Count
1

Это 1 раздражает, когда вы хотите перебрать коллекцию узлов, используя foreach тогда и только тогда, когда они есть. Вот как я узнал, что вы не можете использовать нотацию свойства (точка) свойства обработчика XML как простой ярлык. Я верю, что происходит то, что SelectNodes возвращает коллекцию 0. Когда @ 'ed, он преобразуется из XPathNodeList в Object [] (проверьте GetType ()), но длина сохраняется. Динамически генерируемое свойство $ myDoc.Root.Child (которое по существу не существует) возвращает $ null. Когда $ null равен @ ', он становится массивом длины 1.

3 голосов
/ 29 апреля 2009

Вот кое-что, что я недавно наткнулся (PowerShell 2.0 CTP):

$items = "item0", "item1", "item2"

$part = ($items | select-string "item0")

$items = ($items | where {$part -notcontains $_})

как вы думаете, что $ items будет в конце скрипта?

Я ожидал "item1", "item2", но вместо этого значение $ items: "item0", "item1", "item2".

2 голосов
/ 30 апреля 2009

Функции 'foo' и 'bar' выглядят эквивалентно.

function foo() { $null  }
function bar() { }

Е.Г.

(foo) -eq $null
# True

(bar) -eq $null
# True

Но:

foo | %{ "foo" }
# Prints: foo

bar | %{ "bar" }
# PRINTS NOTHING

Возвращение $ null и возвращение ничего не эквивалентно работе с каналами.


Этот вдохновлен Китом Хиллом примером ...

function bar() {}

$list = @(foo)
$list.length
# Prints: 0

# Now let's try the same but with a temporal variable.
$tmp = foo
$list = @($tmp)
$list.length
# Prints: 1
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...