Pull to refresh

Ужасы PowerShell

Level of difficultyMedium
Reading time3 min
Views11K

Мне часто приходится пользоваться PowerShell. Конечно, его создатели не имели никакого представления о прекрасном и эстетике. Уродливость PowerShell особенна видна при его сравнении, например, с Python. С другой стороны, как говорится, c лица не воду пить - работает и хорошо? Но нет, мне кажется в PowerShell есть по крайней мере пара моментов, которые фатально влияют на его практическое применение.

Расхлябанность

Это пока не фатальный пункт. Даже по сравнению с Python строгости меньше, мы можем забыть присвоить хоть какое-то значение переменной:

PS C:\> Write-host "Value is: $newvar"
Value is:
PS C:\>

Впрочем, даже ключевые слова иногда не проверяются:

Проблемы с наборами из одного элемента

Запишем файл и прочитаем его:

@"
One
Two
Three
"@ | Out-File x.tmp
$f = Get-Content "x.tmp"
Write-Host "Inside the file: $f"

Output:
Inside the file: One Two Three

Get-Content выдает массив строк. Расхлябанность языка приводит к тому, что при выводе они склеиваются - хорошо что хоть через пробел. Поэтому если мы хотим вывести первую строку, то нет проблем:

@"
One
Two
Three
"@ | Out-File x.tmp
$f = (Get-Content "x.tmp")[0]
Write-Host "Inside the file: $f"

Output:
Inside the file: One

Ну или если поменять файл:

@"
One
"@ | Out-File x2.tmp
$f = (Get-Content "x2.tmp")[0]
Write-Host "Inside the file: $f"

Output:
Inside the file: O

Что, простите? Куда делись 'ne'? Ответ шокирует:

(Get-Content "x.tmp").GetType()
(Get-Content "x2.tmp").GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array
True     True     String                                   System.Object

То есть раз в файле есть только одна строка, то зачем заморачиваться массивом, выбросим скобки и оставим только один элемент. Просто замечательно. Устанавливаем с помощью эксперимента в notepad, что string, string<br> это однострочный файл, а вот string<br><br> или string<br><sp> уже многострочные файлы - при этом в notepad все эти варианты визуально неотличимы друг от друга!

Теперь постараемся написать программу, которая выводит первую строку файла:

$f = Get-Content "x.tmp"
if ($f.GetType().Name -eq "Object[]") {
  $firstln = $f[0]
} else {
  $firstln = $f
}
Write-Host $firstln

Правильно? Нет! Если файл существует, но первая строка пуста (там нет даже пробела), то Get-Content возвращает $null! Кстати, найдите в официальной документации Microsoft описание этого поведения. Мне не удалось.

Это не единичный случай

Теперь вас не удивит поведение следующего кода:

$found = invoke-Sqlcmd -ServerInstance "Localhost" -Query "select * from sysdatabases"
$found.getType()
$found = invoke-Sqlcmd -ServerInstance "Localhost" -Query "select top 1 * from sysdatabases"
$found.getType()
$found = invoke-Sqlcmd -ServerInstance "Localhost" -Query "select top 0 * from sysdatabases"
$found.getType()

У меня в коде встречается, например:

$cnt = $found.Rows.Count
if ($found.getType().FullName -eq 'System.Management.Automation.PSCustomObject') 
  { $cnt = 1; }

Программисты Python с завистью глядят на элегантность этого кода, а потом нервно курят в сторонке, понимая, что они лишены эстетического начала и жизнь прошла зря.

Где-то плачут математики, для которых множество из двух элементов, и одного, и нуля элементов - это объекты одного "типа". Но создатели PowerShell похоже считают в парадигме: ноль, один, много.

Контрольный выстрел

Вам еще хочется программировать на PowerShell? Давайте поговорим об этом. Вот еще пример:

function something([string]$file, [string]$p2)  
{
  New-item $file
  return "always OK"
}

$res = something "xxx.tmp" "beta"
Write-host "Result: $res"
$res.GetType()

Что будет в $res? Вы уверены? Давайте проверим:

Какого черта? На самом деле в PowerShell функция возвращает не только то, что в return (это ключевое слово можно и не писать), а собирает все выводы по ходу выполнения. Вывод new-Item прилепился до того, что мы вывели в return.

Если в данном случае мы можем исправить это, дописав | Out-Null после New-Item, то при вызове произвольной сложной функции, написанной Васей Пупкиным, вы вообще не можете ничего гарантировать. Вообще ни-че-го.

По работе и в своем пет проекте мне пришлось написать много скриптов на PowerShell. В какой-то момент я решил замахнуться на Ubuntu и переписал огромное количество скриптов для MSSQL, Postgre и MySQL с PowerShell на Python. Когда это заработало под Ubuntu, я не мог уже остановиться, поставил pwsh для Ubuntu и убедился, что PowerShell хорошо под ней работает, и даже имитирует что все case-insensitive. Наконец я проверил Python-версию скриптов под Windows и только тогда успокоил ее величество ортогональность.

Кстати, Python скрипт везде стартует быстрее. PowerShell тупит доли секунды. Обычно это неважно, но когда на любое действие GUI вызывается скрипт то разница чувствуется.

Tags:
Hubs:
Total votes 24: ↑18 and ↓6+21
Comments53

Articles