Я хотел бы писать в консоль [хост], а также отправлять разрешенное значение на выходные каналы при любых обстоятельствах.
Желаемый результат:
==== FOOBAR ====
Input: <input>
<output>
==== END OF FOOBAR ====
Самое близкое, что я получил к решению, следующее:
function Write-OutAndReturn {
param(
[Parameter(ValueFromPipeline = $true)]
$Value
)
process {
$ConsoleDevice = if ($IsWindows) { '\\.\CON' } else { '/dev/tty' }
Write-Host "====== SECTION ===== = "
Write-Host "Input: " -NoNewline
Write-Host $MyInvocation.BoundParameters.Value
$(if ($Value -is [scriptblock]) {
$Value.Invoke()
} else { $Value }) | Tee-Object -FilePath $ConsoleDevice
Write-Host "====== END SECTION ===== = "
}
}
Где выход $Result = Write-OutAndReturn { 1 + 1 } равен:
====== SECTION ======
Input: 1 + 1
2
====== END SECTION ======
Что желательно; и $Result теперь содержит 2
Однако, когда функция вызывается только (Write-OutAndReturn { 1 + 1 }), она генерирует следующее:
====== SECTION ======
Input: 1 + 1
2
2
====== END SECTION ======
Где второй 2 нежелателен
Есть ли более семантический способ справиться с этим поведением или обеспечить соблюдение краткости?





В вашем примере есть 3 возможных выхода:
все в конвейере можно передать другому командлету или захватить, назначив его переменной (как: $Result = Write-OutAndReturn { 1 + 1 })
Write-Host ────────> Console <═╕<─┐
┌──> File │ │
Tee-Object ═════╡ (or CON) ─┘ │ not processed
│ │
(Write-Output) ─┴──> Pipeline ────┘
Путаница возникает из-за функции Write-Output (или неявного вывода PowerShell):
Если
Write-Outputявляется последней командой в конвейере, объекты отображаются в консоли.
Это означает, что все в конвейере, которое не захвачено в переменной или передано другому командлету, будет действовать так же, как если бы вы выполнили write-host. Чтобы захватить все, что в данный момент пишется в консоль, вам потребуется удалить командлет write-host (или заменить их на Write-Output).
В вашем примере, если $IsWindows равно true, Tee-Object дважды выведет на консоль:
-Filepath'\\.\CON'Спасибо за быстрый ответ iRon. Возможно, я неправильно понимаю, основываясь на том, что я прочитал в документах, Tee-Object stores the output in a file or variable and also sends it down the pipeline; разве это не делает его явным и не отрицает триггер «последнего оператора»? Пример желаемого вывода чрезмерно упрощен, в конечном итоге будет другой поток управления для регистрации информации помимо этого. Замена только на выходе нежелательна. $Result не нуждается в этой информации. В идеале эхо будет происходить всегда, но использование разрешенного значения может не срабатывать.
@soulshined - любой незахваченный вывод будет возвращен в родительскую область в конвейере, и если он не захватит его, он будет передан родительской области этой области и т. д. Если он достигнет области верхнего уровня и не будет захвачен. будет передано в Out-Default, который обычно отображается на консоли. Если вы не хотите, чтобы выходной поток каскадировался вверх, вам нужно как-то его захватить. Некоторые варианты $null = … и … | Out-Null. Если вы хотите подавить вывод конвейера на консоль из вызова верхнего уровня Write-OutAndReturn, вам нужно как-то его захватить.
Хорошо понял! Это имеет смысл спасибо. Я полагаю, что нет способа условно проверить это внутри функции? По крайней мере, я ничего не могу быстро найти. Это не было бы односторонним Out-Null, просто если бы оно не было захвачено. И просто чтобы уточнить, я не хочу подавлять какие-либо консольные эхо внутри функции, я всегда хочу, чтобы они повторяли эти операторы, но я не всегда хочу использовать выходную переменную
@iron - в качестве теста я только что попробовал $null = 1..100 | write-output и ничего не записал в консоль, поэтому в документах говорится: «Если Write-Output является последней командой в конвейере, объекты отображаются в консоли». может быть, просто плохо сформулировано или я читаю это вне контекста, но это не так, как безоговорочное отдельное утверждение…
@soulshined - функция не может узнать, будет ли ее вывод захвачен вызывающим абонентом. Если известно, что функция отправляет выходные данные конвейера, а вызывающая сторона хочет предотвратить их всплытие, то это собственная работа вызывающей стороны, которая должна обрабатывать вывод путем захвата вывода…
@mclayton, ты прав. Я просто цитирую документ Write-Output здесь: Writes the specified objects to the pipeline. If Write-Output is the last command in the pipeline, the objects are displayed in the console. (когда я найду время, я верну это)
Я полагаю, что я прошу эффективно эмулировать -PassThru, за исключением того, что всегда правильно регистрируйте операторы записи-хоста? Но на самом деле это невозможно в данном контексте, насколько я понимаю ваш отзыв? Просто нет способа Write-Output $Value (неявно или нет) только один раз в зависимости от того, захвачено оно или нет.
@soulshined — предположим, вы нашли способ определить, захвачены ли выходные данные или нет на месте вызова. Теперь проблема в том, как узнать, что намерение вызывающего абонента не состоит в том, чтобы намеренно оставить его незахваченным, потому что он хочет передать вывод родительскому конвейеру? Вы можете подавить вывод своей функции, когда вызывающая сторона действительно этого хотела...
справедливое замечание @mclayton, вероятно, не решение для будущего. Я думаю, что в конечном итоге я просто добавлю переключатель -PassThru и позволю вызывающему делегировать. Не конец света просто хотел, чтобы это был запрос «все в одном». Большое спасибо за ваше руководство, ребята!
Вместо
$(if ($Value -is [scriptblock]) {
$Value.Invoke()
} else { $Value }) | Tee-Object -FilePath $ConsoleDevice
Вы могли бы попробовать
$result = if ($Value -is [scriptblock]) { $Value.Invoke() } else { $Value }
Write-Host $result
$result
который в основном является бедняком Tee-Object, который отправляет значение хосту и выходному потоку.
Это означает, что вы можете увидеть $result на консоли во второй раз из выходного потока, если вы не захватите возвращаемое значение из Write-OutAndReturn, но это в значительной степени то, что вы ожидаете от незахваченных значений, которые достигают корневой области (сценария)…
Чтобы решить эту проблему, вы могли бы потенциально сделать строку $result необязательной (и «отключенной» по умолчанию) и добавить переключатель -PassThru, когда вызывающая сторона действительно хочет, чтобы значение было отправлено в выходной поток…
$result = if ($Value -is [scriptblock]) { $Value.Invoke() } else { $Value }
Write-Host $result
if ( $PassThru ) { $result }
В качестве отступления: лучше избегать использования метода
.Invoke()для выполнения блока скрипта в коде PowerShell, поскольку он меняет семантику вызова в нескольких отношениях. Вместо этого используйте& $scriptBlock [arg1 ...]. См. этот ответ для получения дополнительной информации.