PowerShell return
Aug. 30th, 2018 11:16 pmТо, как повершелл ведет себя с возвратом из функций меня СУКА БЕСИТ.
Языки здорового человека либо строго типизируют возврат (C; внезапно bash), либо никак не типизируют (Python). В повершелле можно указать тип возврата директивой OutputType... но она ни к чему не обязывает, я могу указать String и вернуть int.
Далее, языки здорового человека (C, Python) имеют команду return - вот что ей дал, то функция и вернет. Языки здорового человека со странностями (VB, емнип Pascal) это делают путем присвоения значения возврата "имени функции". Шелл здорового человека (bash) умеет возвращать только код возврата - но там функции могут писать в stdout, для перехвата которого есть штатный конструкт (где-то здесь порылся срач между $() и ``).
Сраный повершелл возвращает все, что кем угодно было выведено в stdout ПЛЮС то, что вернули return - этакая ужасная помесь процедурного языка и шелла. Если было несколько такого дела, возвращается массив, элементами которого являются выводы - даже если этот вывод $null. Вот только что на выходе из функции, которая должна возвращать НИЧЕГО, я поимел @($null, $null) и с горящей задницей искал, откуда это лезет. Нашел и пофиксил, конечно, но осадок остался.
Мораль сей басни такова: если вы пишете на PSH не в шелл-стиле и не в BEGIN-PROCESS-END стиле, а в старом добром процедурном (если вы в анамнезе решеточник, питонист или, оужас, сишник), то внимательно следите за выводом. Вызов любого командлета или функции, которые хотя бы теоретически могут что-то вернуть, но который вам нафиг не нужен, должен быть либо присвоен мусорной переменной, либо отправлен конвейером в Out-Null. Иначе дебаггинг гарантирован.
Да, чтобы два раза не вставать, ну вот на хрена запилено пицот (ладно, 5 или 6) потоков вывода вместо старых добрых stderr и stdout, если нельзя сделать | Out-Verbose или | Out-Error , а только Write-Error или Write-Verbose - и это при том, что "конвейером объектов" размахивают все адепты.
Языки здорового человека либо строго типизируют возврат (C; внезапно bash), либо никак не типизируют (Python). В повершелле можно указать тип возврата директивой OutputType... но она ни к чему не обязывает, я могу указать String и вернуть int.
Далее, языки здорового человека (C, Python) имеют команду return - вот что ей дал, то функция и вернет. Языки здорового человека со странностями (VB, емнип Pascal) это делают путем присвоения значения возврата "имени функции". Шелл здорового человека (bash) умеет возвращать только код возврата - но там функции могут писать в stdout, для перехвата которого есть штатный конструкт (где-то здесь порылся срач между $() и ``).
Сраный повершелл возвращает все, что кем угодно было выведено в stdout ПЛЮС то, что вернули return - этакая ужасная помесь процедурного языка и шелла. Если было несколько такого дела, возвращается массив, элементами которого являются выводы - даже если этот вывод $null. Вот только что на выходе из функции, которая должна возвращать НИЧЕГО, я поимел @($null, $null) и с горящей задницей искал, откуда это лезет. Нашел и пофиксил, конечно, но осадок остался.
Мораль сей басни такова: если вы пишете на PSH не в шелл-стиле и не в BEGIN-PROCESS-END стиле, а в старом добром процедурном (если вы в анамнезе решеточник, питонист или, оужас, сишник), то внимательно следите за выводом. Вызов любого командлета или функции, которые хотя бы теоретически могут что-то вернуть, но который вам нафиг не нужен, должен быть либо присвоен мусорной переменной, либо отправлен конвейером в Out-Null. Иначе дебаггинг гарантирован.
Да, чтобы два раза не вставать, ну вот на хрена запилено пицот (ладно, 5 или 6) потоков вывода вместо старых добрых stderr и stdout, если нельзя сделать | Out-Verbose или | Out-Error , а только Write-Error или Write-Verbose - и это при том, что "конвейером объектов" размахивают все адепты.
(no subject)
Date: 2018-08-31 11:19 am (UTC)-
Правда чтоле?
function goga1
{
write-host 34566
$123 = "N1"
return $123
}
$a1 = @(goga1)
cls
write-host $a1
Что будет на экране ?
(no subject)
Date: 2018-08-31 01:05 pm (UTC)Вот, смотри:
function foo { write-output "xyzzy" return "plugh" } $foo = foo $foo xyzzy plugh(no subject)
Date: 2018-08-31 03:17 pm (UTC)Get-Location
return "plugh"
}
$foo = @(foo1)
cls
$foo[-1]
(но это я уже нагуглил) https://stackoverflow.com/questions/10286164/function-return-value-in-powershell/10288256
(no subject)
Date: 2018-09-03 10:37 am (UTC)Одна из задниц повершелла в том, что его явно делали с оглядкой одновременно на баш, питон и решетку (последнее и так было неизбежно в силу решеточности) и с обратной совместимостью с cmd. В результате ConvertFrom-Json вместо Hashtable выдает PSCustomObject (а вот питоновский json.load дает dict, что в моих приложениях удобнее), (@("123") | convertto-json | convertfrom-json) -eq "123", функции весь свой мусор выводят в вывод, curl сделали алиасом на Invoke-WebRequest, сравнение у нас -ne, а не != и т.д. и т.п.
Короче, получилось нечто вроде бы похожее и вроде бы в чем-то совместимое по парадигме, но стоит увлечься, и ты выстрелил себе в ногу, потому что питоновская парадигма сменилась на баш, или наоборот, или еще как. Я не исключаю, что python-shell для линукса выглядел бы не менее коряво - но из всех языков, на которых я когда-либо писал (Basic/VB/VBA/VBScript, Pascal, C, Java, bash, awk, Python), PSH выглядит наиболее упорото.
Вот даже предлагаемое тобой оборачивание с точки зрения синтаксиса выглядит упорото. Получается, что если $foo не массив, то @($foo) работает как конструктор, а если массив, то как каст. Сравни с питоном:
(no subject)
Date: 2018-08-31 11:21 am (UTC)-
$a1 = @(goga1) и нефиг
(no subject)
Date: 2018-08-31 01:11 pm (UTC)При всем упарывании PSH в PROCESS и конвейеры, работать с возвратом любой функции как с массивом - это, имхо, извращение.
(no subject)
Date: 2018-08-31 03:19 pm (UTC)-
вопрос привычки. Я пару раз так наябывался, что есть сервер с 1 сервисом foo1, а есть с двумя, foo1 и foo2. И ну его разбираться чего мне там вернут - один сервис или пачку.
(no subject)
Date: 2019-03-05 11:38 pm (UTC)(no subject)
Date: 2019-03-07 01:43 pm (UTC)Можно, конечно, сказать, что люди на поше должны писать в пошевской парадигме и никак иначе (кстати, питоновцы часто любят такую риторику), но есть нюанс. Если бы я был фулл-тайм разрабом на поше, я был бы обеими руками за, наверное. А когда ты вообще не разраб, а прогаешь на нескольких языках, и из них только пош отличается таким интересным поведением при возврате, то это просто неудобно.
(no subject)
Date: 2019-03-07 06:17 pm (UTC)(no subject)
Date: 2019-03-06 12:20 am (UTC)(no subject)
Date: 2019-03-06 12:12 am (UTC)Это костные языки старых язвенников, место которым в суперсекурном низу. Нахер оно не нужно в скриптовом языке - это бы дико ограничивало возможности.
Банальнейший пример - [string] и [string[]]
> старых добрых stderr и stdout
Потому что они сраное говно.
>ПЛЮС то, что вернули return
Это потому что кто-то нихера не читал мануал:
get-help about_Return РАЗДЕЛ about_Return КРАТКОЕ ОПИСАНИЕ Выход из текущей области действия, которая может быть функцией, скриптом или блоком скриптов. ПОЛНОЕ ОПИСАНИЕ Ключевое слово Return служит для выхода из функции, скрипта или блока скриптов. Его можно использовать для выхода из области действия в определенной точке, для вывода значения или для указания на окончание области действия. Пользователи, знакомые с языками, подобными C или C#, могут использовать ключевое слово Return, чтобы сделать явной логику выхода из области действия. В Windows PowerShell результаты выражений выводятся даже при отсутствии выражений с ключевым словом Return. В таких языках как C или C# выводится только значение или значения, указанные ключевым словом Return.Если недоходит, перевожу:
НЕ НАДО использовать return для вывода результата функции. В идеале вообще return не должно быть в коде.
>даже если этот вывод $null
Если ты словил $null в выводе функции - это значит тот, кто эту функцию писал - крупно облажался. Нет, не потому что "не проверил а не $null ли идёт в вывод", а потому что если нихрена не надо выводить в пайплайн - то и не надо ничего выводить. А если таки пришёл $null - значит кто-то создал пустой объект, ничего с ним не сделал, а потом швырнул в пайплайн.
>если вы пишете на PSH не в шелл-стиле
если вы пишите на PS в стиле убогово текстового говна 40 летней давности - не надо удивляться, что что-то идёт не так как привыкли.
>но который вам нафиг не нужен, должен быть либо присвоен мусорной переменной, либо отправлен конвейером в Out-Null. Иначе дебаггинг гарантирован.
Старый язвенник детектед.
Нужно проверять что пришло. Если должен придти объект нужного типа - то проверять тип, если объект с определённым свойством - то
if ($object.property) {}.Ах, да, старые язвенники часто пишут херню вида
(no subject)
Date: 2019-03-07 01:37 pm (UTC)1) По поводу типизации меня бесит OutputType. Моя позиция в том, что мы либо типизируем вывод, либо нет. А вот так, что "это будет int, но это не точно" - это херня какая-то. Может, я идиот и не познал дзен, но я не дзен-буддист, мне можно.
2) Мануал был читан. По поводу того, что $null возвращать плохо - идите жаловаться разрабам PowerCLI, в глубинах которого это было поймано, или в идеалах опенсорса переписывайте... ой черт, это же не опенсорс. Короче, мне надо было работать с конкретным API с его идиосинкразиями, а не философски срать в комментах. Да, я говнокодер, но мне не шашечки, мне ехать.
3) Далее, с эзотерической стороны возвращать $null не плохо и не хорошо. Вопрос только в том, насколько принимающая сторона готова его обработать, или хотя бы ожидает.
4) Отсутствие return в коде, наверное, хорошо. Но, во-первых, старые привычки дохнут медленно, во-вторых, когда у вас во-от такенная кодобаза, где оно везде так, а вам надо запилить что-то свое, и у вас на это не пицот часов в трекере, вы не будете компостировать мозг главному разрабу, что так не пишут, и что он козел. Голимый стандарт кода все лучше разнобоя.
5) Касательно убогого текстового говна. Вы, гражданин, даже не говно по сравнению с теми, кто писал баш. Вы просто пустое место. И я пустое место, но я себя не выпячиваю. И по сравнению со всем рабочим и использующимся кодом, написанным на баше, все ваши поделия разве что капля в море. Знайте свое место, все-таки. Вы не Дийкстра, не Керниган, не Вирт, не Торвальдс и не ван Россум. Так, programmer drone в лучшем случае.
6) Старые язвенники пишут if($var = Get-Something), поскольку старых язвенников, в отличие от вас, видимо, учили, что if(malloc) - это еще куда ни шло, а вот просто malloc - это выстрел себе в ногу.
7) По поводу Write-Verbose и Write-Error, спустя пару недель после поста я осознал, что они вполне себе принимают конвейер. Но поскольку это не блог Спольского, а я просто яд сцеживал, то переписывать смысла не видел. А то, что Скиф дал ссылку на этот пост - ну так я от своих слов не отказываюсь, и поскольку не считаю себя пупом земли, без проблем признаю свою ошибку. Ох, сколько их было, есть и еще будет.
А засим давайте так. Вы хотели посраться, я посрался, особенно в пункте 5. Мне, впрочем, все равно, что вы там думаете про баш или что еще - это ваше дело, мне с вами не детей крестить, а из возраста холиваров я уже вышел.
Так что предлагаю градус дискуссии все же понизить, и срач на этом закончить. Впрочем, у нас свободная страна, и если вы хотите сраться дальше, я вас в этом праве никоим образом не планирую поражать.
(no subject)
Date: 2019-03-07 06:11 pm (UTC)Ну почему посраться - так, поднасрать ласково, да поучить уму-разуму. Можно на ты, чай не первый раз пути пересекаются.
> Моя позиция в том, что мы либо типизируем вывод, либо нет
Ну дихотомия мировоззрений это прекрасно, конечно, но есть сермяжная рутина. Я уже говорил про то, что PS - скриптовый язык, и ему костность и академичность - вредна.
Для начала посмотрим в офдоку: https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_functions_outputtypeattribute?view=powershell-6
The OutputType attribute value is only a documentation note. It is not derived from the function code or compared to the actual function output. As such, the value might be inaccurate.Упс, нет там сторогой типизации вывода вообще. Этот параметр сугубо для человеков (и для всяких IntelliSense) для документации.
> не познал дзен
Дзен простой - нет никакого смысла строго типизировать вывод. Почему? Потому что это скриптовый, интерактивный язык для решения рутинных задач, на котором пишут в самом натуральном Agile стиле, да так, что все Agile программизды плачут горячими слезами: надо что-то решить СЕЙЧАС - вот консоль/нотепад/ISE, пиши дебажь/рефлексируй интерактивно, решай задачу. А не как по классике, нарисуй UML, построй классы, собери три тысячи конструкторов...
Я выше привёл пример с массивами: это хорошая иллюстрация, почему строгая типизация вывода бессмысленна в поше.
Т.е. к примеру делаем функцию
function Get-WinAsteriskFiles { [CmdletBinding()] [OutputType([System.IO.FileInfo])] Param() gci -Path $env:windir -Filter Win* } $gci = Get-WinAsteriskFilesПо идее, мы должны получать только FileInfo, и
($gci[$gci.GetUpperBound(0)]).GetType()нам возвращает то что мы хотим:Но вот что делать, если у нас там не только
[System.IO.FileInfo]?И вот простой пример тут же:
($gci[0]).GetType()Упс, уже DirectoryInfo. Вот что с ним сделать? Матернуться и стопорнуть эксепшеном? А что с остальными делать, где тип соответствует? Продолжить их выкидывать в пайплайн? Не продолжать?
А что делать если у нас filter/advanced function и мы вообще в блоке
process { }это всё обрабатываем? А есть ещё автомагия приведения типов, и тут ещё на пару Кб можно настрочить примеров.В общем - нафиг это всё не нужно (в поше).
Если сильно хочется ООП и типизации - то это делается ОЧЕНЬ просто.
Вариант А:
Когда выкидываем результат наружу - жёстко кастуем его,
[int]$FuncOutputВариант Б:
На входе функций чётко указываем какие типы принимают параметры.
Всё, проблема решена.*
> Короче, мне надо было работать с конкретным API с его идиосинкразиями
Ну так это же частности, а не стандартное поведение. Я когда работал с HPE iLO Cmdlets - тоже много и забористо матерился, но проблема то была не в поше, а в том, что писали этот сборник куча разных людей.
> возвращать $null не плохо и не хорошо. Вопрос только в том
Безусловно. И пош тут не указ совершенно.
> во-вторых, когда у вас во-от такенная кодобаза
Для этого есть куча вариантов, начиная с Plaster для нового кода и заканчивая какеготамнепомнюсейчас который будет орать, если ретурн есть в коде.
Т.е. вопрос даже не в том, что ретурн плохо, а-яй-яй, а в том, что бы старые привычки понимали, что в нём уже нет необходимости.
> Касательно убогого текстового говна
Это всё прекрасно, но нужно чётко понимать, что всё это великолепие - результат ограничений систем 40 летней давности. Да, много всего прекрасного (и не очень), но это всё крутится вокруг текста и совершенно не имеет смысла, когда есть возможность работать с объектами и свойствами. Поэтому человек, который будет писать в поше в шелл-стайл будет страдать, материться и постоянно вопить "а на кой ляд мне этот пош вообще сдался, я лучше *sh поставлю и всё по привычке сделаю". И не материться он сможет только
познав дзеннаучившись новому.Я, собсно, переводил шелл-скрипты в пош, как прямым методом (написав на поше парсер), так и ручками, переписывая логику. И сравнивая до и после - видно, что в шелл-скриптах постоянно приходится на ровном месте делать кучу телодвижений, вызванных именно ограничениями самого шелла и парадигмы "всё текст"**.
> Старые язвенники пишут if($var = Get-Something)
Ага, а потом плачутся в бложеках, что что-то пошло не так. Особенно весело когда
if ($var = Get-Something) { Do-Something } else { Handle-Error }, а Get-Something выдаёт stop exception. Проходили, не стоит так делать.> спустя пару недель после поста я осознал, что они вполне себе принимают конвейер
Отлично, значит дзен постигается потихоньку.
> а я просто яд сцеживал, то переписывать смысла не видел
Вполне разумный подход. Иногда правда это вызывает демонов (хе-хе), но это частности.
> без проблем признаю свою ошибку
Да по сути - это даже не ошибка, просто незнание.
> холиваров
Тут вопрос не холивара, тут вопрос понимания. Я с пошем с V1, и
имею тайное знаниезнаю много нюансов, которые человеку "со стороны" просто неизвестны, просто потому что он сейчас с ним познакомился, а не десять лет назад. Соответственно, зачастую могу объяснить "почему так".В том числе, почему, по сравнению с пошем, шелл - текстовая какашка.
>Так что предлагаю градус дискуссии все же понизить, и срач на этом закончить
Я в первом (и тем что выше) абзаце уже обозначил свою позицию, но конкретизирую:
я за конструктивный срач, который будет полезен обоим участником; и я не из тех людей, у которых повышенная выработка серотонина при макании людей в грязь (иначе бы первый коммент был из целиком из "ЛОХ, RTFM"). Но я категорически нескромен и фамильярен, и моя манера общения у некоторых может вызывать оторопь.
Поскольку "у нас свободная страна", захотите меня спросить - велкам, не захотите - велкам.
ЗЫ: сейчас вспомнил - была у меня какая-то задачка, что в зависимости от условий выполнения функция выкидывала совершенно разные типы. Не скажу что это здравый подход, но строгая типизация в этом случае заставила бы очень сильно раздувать код. Из примеров на вскидку могу предложить варианты с путями - если путь ещё не существует, то не получится вернуть [System.IO.FileSystemInfo], только [string]; если же есть - то можно сразу с готовым объектом работать.
* да, только в своём коде. Но опять-таки, если приходится работать с чужим дурным кодом (который может быть вообще в .dll скомпилен, так что даже исходник не глянешь), то пош предоставляет возможность выдернуть (через тот же GetType(), например) только то, что нужно.
** моя любимая загадка на этот счёт - какой errorlevel будет, если yum сможет поставить только часть пакетов, указанных ему для установки. И вторая задача - как понять что именно не поставилось.
(no subject)
Date: 2019-03-07 08:04 pm (UTC)Еще раз, я не являюсь фанатом строгой типизации. То есть когда я писал курсачи на сишке, мне она не мешала. Когда я писал на VB (и до сих пор пишу), я когда включал Option Explicit, когда нет. Когда писал Dim foo As String, когда нет. Когда пишу на питоне или поше, то не имею типизации и в ус не дую.
Да, еще, я не ООП-шник ни разу. Как уже, возможно, явствует из дискурса, я процедурщик, каким-то куском мозга застрявший в 80-х. То есть я понимаю ООП (и писал на Java и не испытывал по этому поводу никаких эмоций), я имею эзотерическое мнение, что ООП часто тащат туда, где без него проще, но не более того. Возможно, у меня просто ни разу не было куска данных, тесно сцепленного с выводком функций.
И вот это процедурное мышление, возможно, и объясняет мою любовь к return. Это офигенно дисциплинирует тебя, когда у тебя возврат из функции явно выделен. Я даже в баш-функциях всегда писал "возвратное" echo строго перед exit. Если ты вернулся к коду, который ты не трогал месяцами или годами, тебе надо существенно меньше парить мозг вопросом, кто на ком стоял, и комментарии стиль все-таки не заменяют, а дополняют (точнее, являются неотъемлемой частью).
Ну и еще тут какое дело - пош серьезно я начал учить, будучи вписан в мегапроект (по говнокоду, в основном) на поше, который писали экс-решеточники. Как человек, в молодости сравший не только Паскаль, но и плюсы, я при виде фигурных скобочек испытал экстаз и начал кодить... ну как привык давно на сях. Потом, когда начал понимать, что тащемта надо чуть по-другому, или даже не чуть, я был связан стилем, а потом, когда мое робкое предложение "а давайте мы как-то распилим utility.psm1 на 3К строк, а то несопровождаемый же" отмели, махнул рукой.
Мой ранний пош написан в сишном стиле, мой средний пош уже ближе к пошу здорового человека, сейчас я бы писал, наверное, почти все на BEGIN-PROCESS-END... но я на поше писать почти перестал из-за засилья пошников и критического дефицита башистов и питонистов.
Кстати о баше. Баш, ящетаю, все-таки офигенен, пусть ему и 40 лет в обед. Но когда я дошел до того, как там реализованы ассоциативные массивы, я икнул и решил, что уйду-ка я от этой черной магии на питон. Собственно, именно наличие связки баш-питон в любом дистрибутиве здорового человека и настраивает меня критически против желающих тащить пош на линух.
А вот с исключениями вообще боль. Я их не очень люблю, наверное, потому что в сях их не было :). Грешен, я очень часто не ловлю их вообще или ловлю совсем наверху, но немалая доля моего хозяйства вполне допускает логику: "максимально быстро сдохнуть, если что-то пошло не так" или "сдохнуть, чуть прибрав за собой". Все те разы, когда я их осмысленно юзал, парадигма не сильно отличалась от работы с кодом ошибки. Но в чуть более сложном проекте я прекрасно понимаю плюсы концентрации логики работы с ошибками вместо того, чтобы тащить ее на каждый уровень стека вызовов.
А с yum пример хороший - редко кто глубоко курит маны, а парсинг диагностического вывода задача непростая, особенно когда никто не задавался той целью, чтобы он просто парсился.
(no subject)
Date: 2019-03-07 08:17 pm (UTC)Именно. Так же юзает всякий IntelliSense для посказок, бывает полезно.
>наличие связки баш-питон
Баш-питон2 или Баш-питон3? ;)
>настраивает меня критически против желающих тащить пош на линух.
Пош на линуксе мне хорош тем, что я по прежнему могу работать с объектами. Т.е. мне, конечно, пришлось сначала регескпами парсить тот же yum, зато после этого я точно могу сказать что стоит, что нет. Не то, чтобы это было сильно нужно в реальной жизни (один хрен ты будешь ставить всё что нужно, и пока не поставишь - дальше не пойдёшь), но удобнее во сто крат.
>А вот с исключениями вообще боль
Ещё больнее будет, когда будет понятно, что часть вещей стопорятся, например когда нет связи с сервером, а часть нет - выдают кто $null, а кто и полноценный объект с Count = 0...
>вот это процедурное мышление, возможно, и объясняет мою любовь к return. Это офигенно дисциплинирует тебя
Ну вот поэтому return в поше и есть.
>А с yum пример хороший
А с этим ещё веселей. Если ни один пакет не поставился (их тупо нет, репа не подключена) - вернёт "плохой" эррорлевел; а если хоть один пакет поставился - вернёт "хороший" эррорлевел. Т.е. по errorlevel Ты просто никогда не узнаешь, что что-то непоставилось. Просто потому, что у тебя только 1 метод сообщения информации.