Мне часто приходится пользоваться 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 вызывается скрипт то разница чувствуется.