Почему (возврат) и возврат отличаются? - PullRequest
2 голосов
/ 09 июля 2020

Я тестировал функцию и экспериментировал с тем, что можно сделать с return, и наткнулся на странную проблему: в PowerShell 5.1 и Pw Sh 7.1 командлет return, похоже, не работал в группе :

PS C:\Users\Neko> return "test"
test
PS C:\Users\Neko> (return "test")
return : The term 'return' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct
and try again.
At line:1 char:2
+ (return "test")
+  ~~~~~~
    + CategoryInfo          : ObjectNotFound: (return:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

Если я помещаю return в группу, PowerShell перестает распознавать его.

Я понимаю, что возврат выводится на консоль и не имеет "вывода" на скажем, например, Write-Host,

PS C:\Users\Neko> $test = return "test"
test
PS C:\Users\Neko> $test
PS C:\Users\Neko> $test = Write-Host "test"
test
PS C:\Users\Neko> $test
PS C:\Users\Neko>

, но я не могу понять, почему наличие return в выражении группировки вызывает такую ​​проблему.


Почему используется return в группах, вызывающих странную ошибку, и как я могу это исправить?

Этого не происходит в подвыражениях,

PS C:\Users\Neko> $(return "test")
test

Ответы [ 3 ]

2 голосов
/ 09 июля 2020

return - это оператор , тогда как внутри () разрешены только команды и выражения , оператор группировки .

  • Все конструкции на основе ключевых слов являются операторами ; чтобы назвать несколько: другие управление потоком операторы (exit, throw, ...) и циклы и условия (if, while, switch, ...) - см. about_Language_Keywords список всех ключевых слов в PowerShell.

Чтобы сгруппировать (встроить в другой оператор) a оператор (потенциально даже несколько единиц, разделенных ;, как обычно) - вам понадобится $(), оператор подвыражения .

  • Обратите внимание, что имя оператора несколько неудачное; «Оператор подвыражения» был бы более наглядным.

  • Основное использование $(...) находится внутри расширяемых строк ("..."), и обычно не требуется где-либо еще; вместо этого следует использовать (...), когда это возможно, чтобы избежать потенциальных побочных эффектов (и предотвратить визуальный беспорядок) - см. этот ответ .

Тем не менее, в этом конкретном случае c, как указано в about_Return help topi c, цель return - выйти из охватывающей области (функция, скрипт, или блок скрипта), поэтому использование его в качестве правой части присвоения переменной никогда не имеет смысла.

Причина ограничения не может-использовать-оператор-как-выражение коренится в основах грамматики PowerShell , как это резюмировано членом команда PowerShell в этом выпуске GitHub (выделено мной):

PowerShell имеет конвейеры, содержащиеся в операторах, а не в операторах, содержащихся в конвейерах . Так было всегда, и способ поместить оператор в конвейер - использовать синтаксис $(...) - его единственная цель - существовать вне строк в языке.

Два два наиболее важных последствия для конечных пользователей:

  • Вы не можете использовать оператор в конвейере :

    • То есть что-то вроде foreach ($i in 1..3) { $i } | % { $_ + 1 } действительно не работает. [1]
    • Хотя вы могли используйте $(...) в качестве обходного пути ($(foreach ($i in 1..3) { $i }) | % { $_ + 1 }, это лишает потоковых преимуществ конвейера; то есть вывод из foreach l oop сначала полностью собирается в памяти. до того, как результаты будут отправлены по конвейеру.
    • Чтобы получить обычное потоковое поведение (прохождение вывода через конвейер один за другим), заключите l oop в блоке сценария ({ ... }) и вызовите его с помощью &, оператора вызова [2] :
      • & { foreach ($i in 1..3) { $i } } | % { $_ + 1 }
    • (Использование выражений в конвейере работает, но отлично: 1..3 | % { $_ + 1 })
  • Вы не можете использовать операторы управления потоком return, exit и throw с && и ||, операторами цепочки конвейера :

    • То есть что-то вроде ls nosuchfile || exit 1 не работает не .
    • Вместо этого вы должны использовать $(...): ls nosuchfile || $(exit 1)

К сожалению, эти ограничения нельзя снять, если необходимо поддерживать обратную совместимость ; еще раз процитируем из ранее связанной проблемы GitHub:

[Снятие этих ограничений означает] огромное изменение в грамматике PowerShell (фактически создавая вилку в языке)

  • Хотя PowerShell на сегодняшний день (начиная с версии 7.0) имеет твердую приверженность обратной совместимости, которая предотвращает серьезные критические изменения, теперь начались дебаты о том, разрешать ли такие изменения и как (осторожно) управлять ими: см. этот выпуск GitHub .

  • Если такие изменения в конечном итоге будут разрешены, можно, наконец, решить многие давние неприятности - см. эту проблему GitHub .

[1] Обратите внимание, что foreach также является встроенным псевдонимом для ForEach-Object командлета , но в показанной команде используется foreach l oop оператор - см. about_ForEach .

[2] Если у оператора есть побочные эффекты, которые должен видеть вызывающий абонент, используйте ., оператор поиска точек вместо &.

2 голосов
/ 09 июля 2020

@ Neko Musume - по вашему запросу.

Потому что согласно MS Docs на топах c ...

About_Return | MS Docs

... его можно использовать только так, как определено. Значение «Выйти из текущей области, которая может быть функцией, сценарием или блоком сценария.», Таким образом, использование скобок, как вы пытаетесь сделать, не соответствует этим критериям.

1 голос
/ 09 июля 2020

Примечание: этот ответ дополняет мой (уже длинный) другой , чтобы ответить на некоторые из ваших анализов в вопросе:

Я понимаю это return выводится на консоль и не имеет «вывода», например, например, Write-Host

Это Write-Host, которое не выводится в данные смысл : то есть не записывает в поток успешного вывода (номер потока 1 - см. about_Redirection)

Вместо этого он записывает на хост (дисплей; в PSv5 + он делает это через поток 6, информационный поток), минуя поток успешного вывода, предотвращение захвата или перенаправления вывода - см. этот ответ для получения справочной информации, в том числе когда уместно использование Write-Host (а не Write-Output / неявный вывод).

Напротив, return отправляет вывод из своего (необязательного) операнда [1] в поток успешного вывода am .

Что касается удивительного поведения:

PS> $test = return "test" test

Вы попали в крайний случай (обсуждается в этой закрытой проблеме GitHub ): что-то, что синтаксически разрешено , но не имеет смысла делать : оператор return эффективно сокращает присвоение : это есть, "test" отправляется непосредственно в выходной поток успеха (1), а присвоение игнорируется , потому что оператор управления потоком return выходит из охватывающей области - не только заявление! - до того, как произойдет присвоение .

Вы можете убедиться, что return действительно все еще нацелен на успешный выходной поток следующим образом:

PS> $testOuter = & { $test = return "test" }; "[$testOuter]"
[test]   # proof that $testOuter was able to capture the `return` value.

Однако этой проблемы легко избежать: любое выражение или команду (конвейер) можно напрямую использовать в качестве RHS.

# Do NOT use `return` in the RHS of an assignment.
PS> $test = "test"; "[$test]"
[test]

[1] Хотя использование return с выражением (например, return $foo), вероятно, является наиболее распространенным, обратите внимание, что он поддерживает весь конвейер в качестве своего операнд (например, return Get-Date | % Year).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...