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

Комментарии (31)


  1. HemulGM
    00.00.0000 00:00
    +4

    Сравнивать PS с Питоном - очень странное занятие

    1. Здесь мне нечего сказать. Переменная есть? Есть. Значение в ней, как во многих языках программирования может быть стандартное (по умолчанию). Мы можем объявить строку и вывести её. Компилятор может даже не ругаться. А здесь переменная объявляется в момент её непосредственного вызова. PS. в js тогда вообще лучше не лезьте

    2. Тип "переменной" определяется содержимым файла! Вот и всё объяснение твоего недоумения. На питоне вы грузите содержимое совершенно иначе и если сделать так же на PS результат будет как и в питоне

    3. И не только файла, но и данными, полученными откуда-то ещё.

    4. А забрать значение из функции не пробовали? Ну там, в переменную и вывести потом? При чем тут результат функции, если вы просто выводите значение на экран?
      Аналогичный код на псевдо языке:

    функция(text1, text2) {
      print(text1);
      return text2;
    }


    1. Tzimie Автор
      00.00.0000 00:00

      1. Так оно и забирается в переменную $res


      1. HemulGM
        00.00.0000 00:00
        +1

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

        https://learn.microsoft.com/ru-ru/powershell/module/microsoft.powershell.core/about/about_return?view=powershell-7.3


  1. gatoazul
    00.00.0000 00:00
    +3

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

    Кто мешает проставить их же злоумышленникам - вообще не понятно.


    1. HemulGM
      00.00.0000 00:00

      О чем именно речь? Всегда запускал скрипты без всяких проблем


      1. Tzimie Автор
        00.00.0000 00:00
        +4

        Execution policy.


        1. HemulGM
          00.00.0000 00:00
          +1

          Одна строчка

          Set-ExecutionPolicy Unrestricted


          1. NAI
            00.00.0000 00:00

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

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

            ...или надо распространить код в текстовом виде и просить коллег сделать Ctrl+C, Ctrl+V в *.ps1.

            По сравнению с bash'ем выглядит как создание проблем на ровном месте.


            1. ildarz
              00.00.0000 00:00
              +5

              Если у вас "корпоративные безопасники" не понимают, что делают - вряд ли это проблема powershell.


              1. NAI
                00.00.0000 00:00

                Безопасники люди простые - чем меньше разрешено, тем меньше риски. Т.е. проблемы индейцев шерифа не волнуют. И вот дальше ваша задача объяснить что эта штука ооочень нужна, она безопасна и все такое прочее.


                1. DikSoft
                  00.00.0000 00:00
                  +2

                  Странный наезд на безопасников. Нужно скрипт на куче машинок выполнить? Подпишите код. Примите политику выполнения "только подписанный код".

                  Это нормально.


                  1. Tzimie Автор
                    00.00.0000 00:00
                    +2

                    А как же bat файлы? Из не надо подписывать)


          1. DikSoft
            00.00.0000 00:00
            +1

            У вас юзеры под локальными админами сидят? Дальше про безопасность можно не говорить.


            1. HemulGM
              00.00.0000 00:00
              +1

              В корпоративной среде для этого есть настройка политики глобально и подписание скрипта. Чтоб всякого рода Пети и Васи не могли их выполнять таки


    1. dartraiden
      00.00.0000 00:00
      +1

      Кто мешает проставить их же злоумышленникам — вообще не понятно.

      Это защита от неопытного пользователя:
      ExecutionPolicy is like a baby door. The ExecutionPolicy keeps babies safe but every grown-up surpasses it easily.
      There are like over 20 ways to surpass the ExecutionPolicy even as a standard user.


      надо или прописывать какие-то непонятные заклинания в реестр, или ставить не менее непонятные ключи при вызове.

      • Good God! — сказал Ойра-Ойра, протирая запорошенные глаза. — Canst thou not come in by usually way as decent people do? Sir… <Ужель обычный путь тебе заказан — путь достойного человека? Сэр… — (англ.)> — добавил он.

      image


  1. alexei_ovsyannikov
    00.00.0000 00:00
    +2

    Зачем PowerShell сравнивать с Python с точки зрения функциональности? Это же совершенно разные инструменты...


  1. roqin
    00.00.0000 00:00
    +1

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

    Да у меня на винде powershell.exe запускается долго и жрёт много памяти (по сравнению с bash). Что он там делает - без понятия. Загадка.


    1. iSeiryu
      00.00.0000 00:00
      +1

      Тут надо версию powershell указать и антивирус, который каждый запуск powershell сканирует.

      Powershell медленный.

      Pwsh (core который) быстрый - на моём i7 загрузился, вместе с моим кастомным профилем с темой, модулями, функциями за 853 миллисекунды и кушает 30мб памяти. Без всех моих обвесов должно быть меньше.


      1. Tzimie Автор
        00.00.0000 00:00

        А можно сравнить с Python? Спасибо


        1. iSeiryu
          00.00.0000 00:00

          Для этого надо питон нативно в систему добавлять, мне боязно. Если таки решусь, то отпишусь.

          Он у меня есть в контейнере и в WSL. Но через эти инструменты нормально загрузку терминала не проверить.


        1. iSeiryu
          00.00.0000 00:00
          +1

          Поставил Python 3.11 из Microsoft Store и добавил его в профиль в Windows Terminal:

          \AppData\Local\Microsoft\WindowsApps\python.exe

          Он загружается тоже где-то за 10мс и кушает 8мб.


      1. Tzimie Автор
        00.00.0000 00:00
        +2

        P.S. мне не кажется, что 0.8s это быстро


        1. iSeiryu
          00.00.0000 00:00

          Убрал все мои обвесы и обе версии powershell загрузились за 10мс.

          C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
          C:\Program Files\WindowsApps\Microsoft.PowerShell_7.3.3.0_x64__8wekyb3d8bbwe\pwsh.exe

          Старый powershell кушает 25мб памяти.

          Новый pwsh кушает 16мб.


      1. Kenya-West
        00.00.0000 00:00

        Ждём от вас ссылку на статью, в которой объясняется, как эти красивости сделать


        1. iSeiryu
          00.00.0000 00:00
          +1

          Прочитал как "кривости" и минуту не мог понять, о каких кривостях речь.

          Для красоты используется вот это https://ohmyposh.dev/

          Для функционала вот эти модули:
          https://github.com/PowerShell/PSReadLine
          https://github.com/dahlbyk/posh-git

          Прочие ссылки:

          https://www.youtube.com/watch?v=n1sFkbPlDww
          https://www.hanselman.com/blog/my-ultimate-powershell-prompt-with-oh-my-posh-and-the-windows-terminal

          Автодополнение - крутая штука. Из истории по умолчанию и можно добавить разных умных дополнений с подсказками для git, docker, kubectl, az, aws, и прочее.

          .
          .

          .


  1. mvv-rus
    00.00.0000 00:00
    +13

    Вы не любите Powershell? Да вы просто не умееете его готовить! ;-)


    Вам не нравится расхлябанность с неописанными переменными? Включите строгий режим (Set-StrictMode -Version Latest).
    Вам не нравится, что Powershell по-разному работает с последовательностями из 0, 1 и нескольких элементов? Используйте операцию @(...)
    Вам не нравится, что Powershell передает результаты функций через конвейер? Да, отучить его от этого нельзя — потому что он все-таки shell. Идеология у него такая. Но к этому можно приноровиться — просто привыкните сразу выбрасывать все лишнее: ставьте [Void] перед вызовами внешних команд, функций и в прочих местах, где значение результата надо проигнорировать.
    И тогда вы наверняка перестанете бояться и полюбите Powershell. Потому что он позволяет ещё много чего. В частности — с легкостью пользоваться практически всей библиотекой времени выполнения .NET (мне, как человеку, хорошо знакомому с C#, это очень полезно).
    PS Лично я полюбил Powershell, потому что много работал с MS Exchange, а там, начиная с Exch2K7, альтернатив просто не было.


    PPS Вы, AFAIK давно работаете с MS SQL. Не поделетесь, чем вы пользовались для написания скриптов до появления в нем Powershell? vbscript/jscript+ADO? Или — isql/osql с последующим мучением разбора текстового вывода средствами cmd? Это я чисто для себя интересуюсь — потому что я в те давние времена этот вопрос для себя так и не решил.


    1. Tzimie Автор
      00.00.0000 00:00

      Сейчас я использую PowerShell, потому что так принято в фирме, где сейчас работаю. Вроде 1000 строк, скоро будет статья)

      Если бы было всё равно, то писал бы на Питоне

      До PowerShell я все писал на SQL, вызовешь команду в xp_cmdshell и парзишь вывод там же)


  1. ildarz
    00.00.0000 00:00
    -1

    Языки программирования РАЗНЫЕ. И особенности каждого из них надо изучать, а не пытаться действовать по кальке с другого языка. Описанные вами "неожиданности" PS либо прямо следуют из логики происходящего, либо просто явным образом описаны в документации (про ваш пример с return там вообще почти цитата в about_return есть).

    Что до неявного преобразования типов - так в той области, где PS обычно используется, это однозначное благо, сильно упрощающее жизнь. А там, где оно вдруг работает не так, как ожидается, всегда можно сделать явное.


  1. mc2
    00.00.0000 00:00
    +4

    Откроем wiki на каждый из "языков" и осилим первое предложение:

    Python is a high-level, general-purpose programming language.


    PowerShell is a task automation and configuration management program from Microsoft, consisting of a command-line shell and the associated scripting language.

    Так что сравнение теплого с мягким.


  1. DikSoft
    00.00.0000 00:00
    +4

    Из всех "неприятностей" реально утомляет в скриптах только одна: проверка, что за тип нам вернули. $null , объект или массив.
    Остальное, как мне кажется, надумано или просто противоречит старым привычкам.

    Плюсы "всё объект" и "всё можно отправить в конвейер" перевешивают минусы. Не надо парсить строки, где у каждого автора "утилиты, которая делает только одно, но отлично" своё, блин, представлние о прекрасном. И где простое True может быть написано десятком разных и порой весьма извращённых вариантов.


  1. Daysleeper
    00.00.0000 00:00
    +1

    Давно работаю с PowerShell и недавно начал осваивать python (без особого энтузиазма). PowerShell прекрасный инструмент и мне очень нравиться. Но у меня нет желания написать статью с кликбейтным названием "ужасы python", только потому что я плохо знаю python. Почему автор решил устроить холивар, для меня осталось загадкой.