Как вернуть одно и только одно значение из функции PowerShell?
Из этого вопроса о переполнении стека я узнал, что семантика возврата PowerShell отличается, скажем, от семантики возврата С#. Цитата из вышеупомянутого вопроса:
PowerShell имеет действительно дурацкую семантику возврата - по крайней мере, если смотреть с более традиционной точки зрения программирования. Есть две основные идеи, чтобы обернуть вашу голову: весь вывод фиксируется и возвращается. Ключевое слово return действительно просто указывает на логическую точку выхода.
Давайте посмотрим на этот пример:
function Calculate
{
echo "Calculate"
return 11
}
$result = Calculate
Если вы подтвердите $result
вы поймете, что что-то не так. Вы ожидаете это:
11
Но реальность, которую вы на самом деле видите, другая:
Calculate
11
Таким образом, вместо того, чтобы возвращать только предполагаемое возвращаемое значение, вы фактически возвращаете массив.
Вы могли бы избавиться от операторов echo и не загрязнять возвращаемое значение, но если вы вызываете другую функцию из вашей функции, которая что-то повторяет, то у вас снова проблемы.
Как я могу написать функцию PowerShell, которая возвращает только одну вещь? Если встроенные функции PowerShell возвращают только одно значение, почему я не могу этого сделать?
Обходной путь, который я использую сейчас, и я хотел бы избавиться от:
function Calculate
{
# Every function that returns has to echo something
echo ""
return 11
}
# The return values is the last value from the returning array
$result = (Calculate)[-1]
Ответы
Ответ 1
Несколько уже предоставленных ответов не полностью отвечают на ваш вопрос, потому что они не учитывают других командлетов, которые вы могли бы назвать - как вы указываете, - которые могут "загрязнять" ваш вывод. Ответ от @Laezylion, по сути, правильный, но я считаю, что он нуждается в дальнейшей разработке.
Понятно, что вы понимаете, что ваше текущее решение - заставить каждую функцию возвращать массив, а затем принимать последний элемент - не очень надежное решение. (На самом деле, я думаю, что безопасно назвать это kludge. :-) Правильный подход - и прямой ответ на ваш вопрос - ясен... хотя на первый взгляд это звучит как тавтология:
Question: How do you ensure a function returns only one thing?
Answer: By ensuring that it returns only one thing.
Позвольте мне разъяснить. В PowerShell есть действительно два вида функций/командлетов: (A) те, которые возвращают данные, и (B) те, которые не возвращают данные, но могут сообщать о ходе или диагностических сообщениях. Проблемы возникают, как вы видели, когда пытаетесь их смешивать. И это может произойти непреднамеренно. Таким образом, ваша ответственность, как автор функции, понимать каждую функцию/вызов cmdlet внутри вашей функции: в частности, возвращает ли она что-либо, будь то истинное возвращаемое значение или просто диагностический вывод? Вы ожидаете выхода из функций типа A, но вы должны быть осторожны с любыми функциями типа B. Для любого, кто возвращает что-то, вы должны либо присвоить его переменной, либо передать ее в конвейер, либо... заставить ее уйти. (Существует несколько способов заставить его уйти: передать его в Out-Null, отбросить его на пустое, перенаправить его на $ null или присвоить ему значение $ null. См. Сообщение Jason Archers Stack Overflow, в котором оценивается производительность каждого из этих ароматы, предлагая вам уклоняться от Out-Null.)
Да, этот подход требует больших усилий, но при этом вы получаете более надежное решение. Для более подробного обсуждения этой самой проблемы эти функции A/B и т.д. См. В вопросе 1 о множестве PowerShell Pitfalls, недавно опубликованном на Simple-Talk.com.
Ответ 2
Вы также можете просто присвоить null все, а затем вернуть нужную переменную:
Function TestReturn {
$Null = @(
Write-Output "Hi there!"
1 + 1
$host
$ReturnVar = 5
)
return $ReturnVar
}
Ответ 3
Не используйте echo
для записи информации, используйте Write-Host
, Write-Verbose
или Write-Debug
в зависимости от сообщения.
echo
является псевдонимом для Write-Output
, который отправит его как объект по конвейеру. Write-Host/Verbose/Debug
но только консольные сообщения.
PS P:\> alias echo
CommandType Name ModuleName
----------- ---- ----------
Alias echo -> Write-Output
Пример:
function test {
Write-Host calc
return 11
}
$t = test
"Objects returned: $($t.count)"
$t
#Output
calc
Objects returned: 1
11
Кроме того, если вы вернете значение в последней строке в своей функции, вам не нужно использовать return
. Написание объекта/значения само по себе достаточно.
function test {
Write-Host calc
11
}
Разница в том, что return 11
пропустит строки после того, как вы их используете в середине функции.
function test {
return 11
12
}
test
#Output
11
Ответ 4
Стоит отметить:
Любой неуправляемый вывод команды внутри вашей функции в конечном итоге будет возвращен.
Я использую, чтобы получить эту проблему с функцией, управляющей контентом XML:
Function Add-Node(NodeName){
$NewXmlNode = $Xml.createElement("Item")
$Xml.Items.appendChild($NewXmlNode) | out-null
}
Если вы удалите Out-Null, возвращением вашей функции станут базовые свойства узла... (Вы также можете управлять кодом выхода команды appendChild... это зависит от мотивации) :)
Ответ 5
Вы можете заменить echo
(псевдоним Write-Output) на Write-Host:
PS> function test{ Write-Host "foo";"bar"}
PS> $a = test
test
PS >$a
bar
Ответ 6
echo
- псевдоним для Write-Output, который отправляет объект следующей команде.
Использование Write-Host должно помочь:
PS:> function Calculate
>> {
>> # Every function that returns has to echo something
>> Write-Host "test"
>> return 11
>> }
>>
PS:> $A = Calculate
test
PS:> $A
11
Ответ 7
@Майкл Соренс ответ полезен, но он предлагает стратегию дизайна, которая кажется довольно обременительной. Я не хочу выделять функции обработки данных из функций, сообщающих о прогрессе или диагностических сообщениях. Читая его блог-постер, я обнаружил, что менее опасный подход к вопросу ОП. Здесь ключевое предложение из сообщения в блоге:
Функция PowerShell возвращает весь незагруженный вывод.
Так что, если мы возьмем наш выход?
function Calculate
{
$junkOutput = echo "Calculate"
return 11
}
$result = Calculate
Теперь $result
просто содержит 11, как и предполагалось.
Ответ 8
Использование круглых скобок кажется критичным при вызове функции, которая принимает параметры. Этот пример заполняет DataTable из SQL Server и возвращает [int] из первой строки, четвертого столбца. Обратите внимание на скобки вокруг фактического вызова функции, назначенного переменной [int] $ tt1
function InvokeSQL-GetOneCount { param( [string]$cn, [string]$prj )
$sql = "SELECT * FROM [dbo].[dummytable] WHERE ProjectNumber = '$prj' ORDER BY FormID, PartID;"
$handler = [System.Data.SqlClient.SqlInfoMessageEventHandler] { param($sender, $event) Handle-Message -message $event.Message -fcolor "yellow" -bcolor "black"; };
$conn = new-Object System.Data.SqlClient.SqlConnection($cn)
$conn.add_InfoMessage($handler);
$conn.FireInfoMessageEventOnUserErrors = $true;
$cmd = New-Object System.Data.SqlClient.SqlCommand;
$cmd.Connection = $conn;
$cmd.CommandType = [System.Data.CommandType]::Text;
$cmd.CommandText = $sql;
$cmd.CommandTimeout = 0;
$sa = New-Object System.Data.SqlClient.SqlDataAdapter($cmd);
$dt = New-Object System.Data.DataTable("AllCounts");
$sa.Fill($dt) | Out-Null;
[int]$rval = $dt.Rows[0][3];
$conn.Close();
return $rval;
}
clear-host;
[int]$tt1 = (InvokeSQL-GetOneCount -cn "Server=[your server];DataBase=[your catalog];Integrated Security=SSPI" -prj "MS1904");
write-host $tt1;