Hàm trả về giá trị trong PowerShell


188

Tôi đã phát triển chức năng PowerShell thực hiện một số hành động liên quan đến việc cung cấp các trang SharePoint Team. Cuối cùng, tôi muốn hàm trả về URL của trang web được cung cấp dưới dạng Chuỗi vì vậy ở cuối chức năng của tôi, tôi có đoạn mã sau:

$rs = $url.ToString();
return $rs;

Mã gọi hàm này trông giống như:

$returnURL = MyFunction -param 1 ...

Vì vậy, tôi đang mong đợi một Chuỗi, tuy nhiên không phải vậy. Thay vào đó, nó là một đối tượng của kiểu System.Man Quản lý.Automation.PSMethod. Tại sao nó trả về kiểu đó thay vì kiểu Chuỗi?


2
Chúng ta có thể xem khai báo hàm không?
zdan

1
Không, không phải là chức năng gọi, chỉ cho chúng tôi cách / nơi bạn đã khai báo "MyFactor". Điều gì xảy ra khi bạn làm: Chức năng Get-Item: \
MyFactor

1
Đề xuất một cách khác để giải quyết vấn đề này: stackoverflow.com/a/42842865/992301
Feru

Tôi nghĩ rằng đây là một trong những câu hỏi PowerShell quan trọng nhất về lỗi tràn stack liên quan đến các hàm / phương thức. Nếu bạn đang dự định học tập kịch bản PowerShell, bạn nên đọc toàn bộ trang này và hiểu kỹ về nó. Bạn sẽ ghét chính mình sau này nếu bạn không.
Người chim

Câu trả lời:


271

PowerShell thực sự có ngữ nghĩa hoàn trả lập dị - ít nhất là khi nhìn từ góc độ lập trình truyền thống hơn. Có hai ý tưởng chính để quấn đầu bạn:

  • Tất cả đầu ra được nắm bắt và trả lại
  • Từ khóa return thực sự chỉ ra một điểm thoát hợp lý

Do đó, hai khối script sau đây sẽ thực hiện chính xác cùng một điều:

$a = "Hello, World"
return $a

 

$a = "Hello, World"
$a
return

Biến $ a trong ví dụ thứ hai được để lại dưới dạng đầu ra trên đường ống và, như đã đề cập, tất cả đầu ra được trả về. Trong thực tế, trong ví dụ thứ hai, bạn có thể bỏ hoàn toàn trả về và bạn sẽ có hành vi tương tự (trả về sẽ được ngụ ý khi hàm hoàn thành và thoát tự nhiên).

Không có thêm định nghĩa hàm của bạn, tôi không thể nói lý do tại sao bạn nhận được một đối tượng PSMethod. Tôi đoán là bạn có thể có một vài dòng không bị bắt và đang được đặt trên đường ống đầu ra.

Cũng đáng lưu ý rằng bạn có thể không cần những dấu chấm phẩy đó - trừ khi bạn đang lồng nhiều biểu thức trên một dòng.

Bạn có thể đọc thêm về ngữ nghĩa trả về trên trang about_Return trên TechNet hoặc bằng cách gọi help returnlệnh từ chính PowerShell.


13
vậy có cách nào để trả về giá trị scaler từ hàm không?
ChiliYago

10
Nếu hàm của bạn trả về hàm băm, thì nó sẽ xử lý kết quả như vậy, nhưng nếu bạn "dội lại" bất cứ thứ gì trong thân hàm, bao gồm cả đầu ra của các lệnh khác, và đột nhiên kết quả bị trộn lẫn.
Luke Puplett

5
Nếu bạn muốn xuất nội dung ra màn hình từ bên trong một chức năng mà không trả lại văn bản đó như một phần của kết quả chức năng, hãy sử dụng lệnh write-debugsau khi đặt biến $DebugPreference = "Continue"thay vì"SilentlyContinue"
BeowulfNode42

6
Điều này đã giúp, nhưng stackoverflow.com/questions/22663848/ này đã giúp nhiều hơn. Kết quả không mong muốn của các lệnh / hàm gọi trong hàm được gọi thông qua "... | Out-Null" và sau đó chỉ cần trả về thứ bạn muốn.
Straff

1
@LukePuplett echolà vấn đề đối với tôi! Điểm nhỏ đó là rất rất quan trọng. Tôi đã trả lại một hashtable, theo sau mọi thứ khác, nhưng giá trị trả về không như tôi mong đợi. Loại bỏ echovà làm việc như một nét duyên dáng.
Ayo I

53

Phần này của PowerShell có lẽ là khía cạnh ngu ngốc nhất. Bất kỳ đầu ra không liên quan nào được tạo ra trong một hàm sẽ làm ô nhiễm kết quả. Đôi khi không có bất kỳ đầu ra nào, và trong một số điều kiện, có một số đầu ra không có kế hoạch khác, ngoài giá trị trả lại theo kế hoạch của bạn.

Vì vậy, tôi loại bỏ nhiệm vụ khỏi lệnh gọi chức năng ban đầu, để đầu ra kết thúc trên màn hình, rồi bước qua cho đến khi một cái gì đó tôi không có kế hoạch bật ra trong cửa sổ trình gỡ lỗi (sử dụng PowerShell ISE ).

Ngay cả những thứ như dự trữ các biến trong phạm vi bên ngoài cũng gây ra đầu ra, như [boolean]$isEnabledvậy sẽ gây khó chịu khi nhổ sai trừ khi bạn thực hiện [boolean]$isEnabled = $false.

Một điều tốt nữa là $someCollection.Add("thing")nhổ ra bộ sưu tập mới.


2
Tại sao bạn chỉ cần đặt trước một tên thay vì thực hiện tốt nhất việc khởi tạo đúng biến với giá trị được xác định rõ ràng.
BeowulfNode42

2
Tôi hiểu bạn có nghĩa là bạn có một khối khai báo biến ở đầu mỗi phạm vi cho mỗi biến được sử dụng trong phạm vi đó. Bạn vẫn nên hoặc có thể đã khởi tạo tất cả các biến trước khi sử dụng chúng trong bất kỳ ngôn ngữ nào, kể cả C #. Cũng quan trọng là làm [boolean]$atrong powershell không thực sự khai báo biến $ a. Bạn có thể xác nhận điều này bằng cách theo dòng đó với dòng $a.gettype()và so sánh đầu ra đó với khi bạn khai báo và khởi tạo với chuỗi $a = [boolean]$false.
BeowulfNode42

3
Bạn có thể đúng, tôi chỉ thích một thiết kế rõ ràng hơn để ngăn chặn việc viết ngẫu nhiên.
Luke Puplett

4
Xuất phát từ quan điểm của một lập trình viên, mặc dù tôi là một PS-n00b, tôi không chắc là tôi thấy đây là điều tồi tệ nhất trong nhiều khía cạnh khó chịu của cú pháp PS. Làm thế nào trên trái đất có ai nghĩ rằng nên viết "x -gt y" hơn là "x> y"?!? Chắc chắn ít nhất các toán tử tích hợp có thể đã sử dụng cú pháp thân thiện với con người hơn?
The Dag

5
@TheDag vì nó là vỏ đầu tiên, ngôn ngữ lập trình thứ hai; ><đã được sử dụng để chuyển hướng luồng I / O đến / từ các tệp. >=ghi stdout vào một tập tin gọi là =.
TessellatingHeckler

38

Với PowerShell 5, giờ đây chúng ta có khả năng tạo các lớp. Thay đổi chức năng của bạn thành một lớp và trả về sẽ chỉ trả về đối tượng ngay trước nó. Đây là một ví dụ đơn giản thực sự.

class test_class {
    [int]return_what() {
        Write-Output "Hello, World!"
        return 808979
    }
}
$tc = New-Object -TypeName test_class
$tc.return_what()

Nếu đây là một chức năng, đầu ra dự kiến ​​sẽ là

Hello World
808979

nhưng với tư cách là một lớp, thứ duy nhất được trả về là số nguyên 808979. Một lớp giống như một sự đảm bảo rằng nó sẽ chỉ trả về kiểu được khai báo hoặc void.


6
Ghi chú. Nó cũng hỗ trợ staticcác phương pháp. Dẫn đến cú pháp[test_class]::return_what()
Sancarn

1
"Nếu đây là một chức năng, đầu ra dự kiến ​​sẽ là 'Hello World \ n808979" - Tôi không nghĩ đó là chính xác? Giá trị đầu ra luôn chỉ là giá trị của câu lệnh cuối cùng, trong trường hợp của bạn return 808979, có chức năng hay không.
amn

1
@amn Cảm ơn bạn! Bạn rất đúng ví dụ sẽ chỉ trả về rằng nếu nó tạo đầu ra trong hàm. Đó là điều làm tôi khó chịu. Đôi khi chức năng của bạn có thể tạo đầu ra và đầu ra đó cộng với bất kỳ giá trị nào bạn thêm vào trong câu lệnh return được trả về. Các lớp như bạn biết sẽ chỉ trả về kiểu khai báo hoặc void. Nếu bạn sử dụng các hàm thì một phương thức đơn giản là gán hàm cho một biến và nếu nhiều hơn giá trị trả về được trả về thì var là một mảng và giá trị trả về sẽ là mục cuối cùng trong mảng.
DaSmokeDog

1
Vâng, bạn mong đợi gì từ một lệnh được gọi là Write-Output? Đây không phải là đầu ra "bàn điều khiển" được đề cập ở đây, đây là đầu ra cho hàm / cmdlet. Nếu bạn muốn kết xuất các thứ trên bàn điều khiển Write-Verbose, Write-Hostvà các Write-Errorchức năng, trong số những thứ khác. Về cơ bản, Write-Outputgần với returnngữ nghĩa hơn là thủ tục đầu ra của bàn điều khiển. Đừng lạm dụng nó, sử dụng Write-Verbosecho sự dài dòng, đó là những gì nó làm.
23/2/19

1
@amn Tôi rất ý thức đây không phải là bàn điều khiển. Đó thực sự là một cách nhanh chóng để hiển thị cách giao dịch hoàn trả với đầu ra bất ngờ bên trong một hàm so với một lớp. trong một hàm tất cả đầu ra + giá trị bạn trả về đều được trả lại. TUY NHIÊN chỉ giá trị được chỉ định trong trả về của một lớp được thông qua gói. Hãy thử chạy chúng cho chính mình.
DaSmokeDog

30

Như một cách giải quyết, tôi đã trả về đối tượng cuối cùng trong mảng mà bạn lấy lại từ hàm ... Đó không phải là một giải pháp tuyệt vời, nhưng tốt hơn là không có gì:

someFunction {
   $a = "hello"
   "Function is running"
   return $a
}

$b = someFunction
$b = $b[($b.count - 1)]  # Or
$b = $b[-1]              # Simpler

Nói chung, một cách viết giống nhau hơn một dòng có thể là:

$b = (someFunction $someParameter $andAnotherOne)[-1]

7
$b = $b[-1]sẽ là một cách đơn giản hơn để có được phần tử cuối cùng hoặc mảng, nhưng thậm chí đơn giản hơn sẽ không chỉ là các giá trị đầu ra mà bạn không muốn.
Duncan

@Duncan Và nếu tôi muốn xuất nội dung trong hàm thì đồng thời tôi cũng muốn có giá trị trả về?
Utkan Gezer

@ThoAppelsin nếu bạn muốn viết nội dung cho thiết bị đầu cuối thì sử dụng write-hostđể xuất trực tiếp.
Duncan

7

Tôi vượt qua một đối tượng Hashtable đơn giản với một thành viên kết quả duy nhất để tránh sự điên rồ trở lại vì tôi cũng muốn xuất ra giao diện điều khiển. Nó hành động thông qua thông qua tham chiếu.

function sample-loop($returnObj) {
  for($i = 0; $i -lt 10; $i++) {
    Write-Host "loop counter: $i"
    $returnObj.result++
  }
}

function main-sample() {
  $countObj = @{ result = 0 }
  sample-loop -returnObj $countObj
  Write-Host "_____________"
  Write-Host "Total = " ($countObj.result)
}

main-sample

Bạn có thể thấy việc sử dụng ví dụ thực tế tại unpackTunes dự án GitHub của tôi .


4

Thật khó để nói mà không nhìn vào mã. Đảm bảo rằng chức năng của bạn không trả về nhiều hơn một đối tượng và bạn nắm bắt bất kỳ kết quả nào được thực hiện từ các cuộc gọi khác. Bạn nhận được gì cho:

@($returnURL).count

Dù sao, hai gợi ý:

Truyền đối tượng thành chuỗi:

...
return [string]$rs

Hoặc chỉ cần đặt nó trong dấu ngoặc kép, giống như trên nhưng ngắn hơn để gõ:

...
return "$rs"

1
Hoặc thậm chí chỉ "$rs"- returnchỉ được yêu cầu khi quay lại sớm từ chức năng. Thoát khỏi sự trở lại là thành ngữ PowerShell tốt hơn.
David Clarke

4

Mô tả của Luke về các kết quả chức năng trong các tình huống này dường như là đúng. Tôi chỉ muốn hiểu nguyên nhân gốc rễ và nhóm sản phẩm PowerShell sẽ làm gì đó về hành vi. Nó dường như khá phổ biến và khiến tôi mất quá nhiều thời gian gỡ lỗi.

Để giải quyết vấn đề này, tôi đã sử dụng các biến toàn cục thay vì trả về và sử dụng giá trị từ lệnh gọi hàm.

Đây là một câu hỏi khác về việc sử dụng các biến toàn cục: Đặt biến PowerShell toàn cầu từ một hàm trong đó tên biến toàn cục là một biến được truyền cho hàm


3

Sau đây chỉ đơn giản trả về 4 như một câu trả lời. Khi bạn thay thế các biểu thức thêm cho chuỗi, nó sẽ trả về chuỗi đầu tiên.

Function StartingMain {
  $a = 1 + 3
  $b = 2 + 5
  $c = 3 + 7
  Return $a
}

Function StartingEnd($b) {
  Write-Host $b
}

StartingEnd(StartingMain)

Điều này cũng có thể được thực hiện cho một mảng. Ví dụ dưới đây sẽ trả về "Văn bản 2"

Function StartingMain {
  $a = ,@("Text 1","Text 2","Text 3")
  Return $a
}

Function StartingEnd($b) {
  Write-Host $b[1]
}

StartingEnd(StartingMain)

Lưu ý rằng bạn phải gọi hàm bên dưới hàm. Mặt khác, lần đầu tiên chạy, nó sẽ trả về một lỗi mà nó không biết "StartedMain" là gì.


2

Các câu trả lời hiện tại là chính xác, nhưng đôi khi bạn không thực sự trả lại một cái gì đó rõ ràng bằng a Write-Outputhoặc a return, nhưng có một số giá trị bí ẩn trong kết quả hàm. Đây có thể là đầu ra của hàm dựng sẵn nhưNew-Item

PS C:\temp> function ContrivedFolderMakerFunction {
>>    $folderName = [DateTime]::Now.ToFileTime()
>>    $folderPath = Join-Path -Path . -ChildPath $folderName
>>    New-Item -Path $folderPath -ItemType Directory
>>    return $true
>> }
PS C:\temp> $result = ContrivedFolderMakerFunction
PS C:\temp> $result


    Directory: C:\temp


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----         2/9/2020   4:32 PM                132257575335253136
True

Tất cả tiếng ồn thêm của việc tạo thư mục đang được thu thập và phát ra trong đầu ra. Cách dễ dàng để giảm thiểu điều này là thêm | Out-Nullvào cuối New-Itemcâu lệnh hoặc bạn có thể gán kết quả cho một biến và chỉ không sử dụng biến đó. Nó sẽ trông như thế này ...

PS C:\temp> function ContrivedFolderMakerFunction {
>>    $folderName = [DateTime]::Now.ToFileTime()
>>    $folderPath = Join-Path -Path . -ChildPath $folderName
>>    New-Item -Path $folderPath -ItemType Directory | Out-Null
>>    # -or-
>>    $throwaway = New-Item -Path $folderPath -ItemType Directory 
>>    return $true
>> }
PS C:\temp> $result = ContrivedFolderMakerFunction
PS C:\temp> $result
True

New-Itemcó lẽ là nổi tiếng hơn trong số này, nhưng những người khác bao gồm tất cả các StringBuilder.Append*()phương pháp, cũng như SqlDataAdapter.Fill()phương pháp.


0

Là một thay thế cho returntôi sẽ đề nghị Write-Output.

Write-Output có thể lấy đầu vào từ đường ống và chuyển tiếp xuống đường ống hoặc in ra bàn điều khiển nếu đó là lệnh cuối cùng.

Tuy nhiên, Write-Outputkhông kết thúc tập lệnh như returnsẽ làm, đó có thể là một lợi thế nếu đầu ra nên được sử dụng, chuyển tiếp, vv trong một vòng lặp.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.