Ужасы PowerShell

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

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

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

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

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

24b50b126146d52da32f50684df6b9b3.png

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

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

@"
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
это однострочный файл, а вот string

или string
уже многострочные файлы — при этом в 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()

2f3e49064d8a1b459dddef5738ead0d1.png

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

$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? Вы уверены? Давайте проверим:

a92ad72480a03eeccd062d3311527cab.png

Какого черта? На самом деле в 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 вызывается скрипт то разница чувствуется.

© Habrahabr.ru