Как вернуть одно и только одно значение из функции 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;