Nilai pengembalian fungsi di PowerShell

188

Saya telah mengembangkan fungsi PowerShell yang melakukan sejumlah tindakan yang melibatkan penyediaan situs Tim SharePoint . Pada akhirnya, saya ingin fungsi untuk mengembalikan URL situs yang disediakan sebagai String sehingga pada akhir fungsi saya, saya memiliki kode berikut:

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

Kode yang memanggil fungsi ini terlihat seperti:

$returnURL = MyFunction -param 1 ...

Jadi saya mengharapkan String, namun tidak. Sebaliknya, ini adalah objek bertipe System.Management.Automation.PSMethod. Mengapa mengembalikan tipe itu dan bukan tipe String?

ChiliYago
sumber
2
Bisakah kita melihat deklarasi fungsi?
zdan
1
Tidak, bukan pemanggilan fungsi, tunjukkan pada kami bagaimana / di mana Anda mendeklarasikan "Fungsi Saya". Apa yang terjadi ketika Anda melakukannya: Fungsi Get-Item: \ MyFunction
zdan
1
Menyarankan cara alternatif untuk mengatasi ini: stackoverflow.com/a/42842865/992301
Feru
Saya pikir ini adalah salah satu pertanyaan PowerShell yang paling penting pada stack overflow yang berkaitan dengan fungsi / metode. Jika Anda berencana mempelajari skrip PowerShell, Anda harus membaca seluruh halaman ini dan memahaminya secara menyeluruh. Anda akan membenci diri sendiri nanti jika tidak.
Birdman

Jawaban:

271

PowerShell benar-benar mengembalikan semantik - setidaknya jika dilihat dari perspektif pemrograman yang lebih tradisional. Ada dua ide utama untuk membungkus kepala Anda:

  • Semua output ditangkap, dan dikembalikan
  • Kata kunci kembali benar-benar hanya menunjukkan titik keluar yang logis

Dengan demikian, dua blok skrip berikut akan melakukan hal yang sama persis secara efektif:

$a = "Hello, World"
return $a

 

$a = "Hello, World"
$a
return

Variabel $ a dalam contoh kedua dibiarkan sebagai output pada pipeline dan, sebagaimana disebutkan, semua output dikembalikan. Bahkan, dalam contoh kedua Anda bisa menghilangkan pengembalian sepenuhnya dan Anda akan mendapatkan perilaku yang sama (pengembalian akan tersirat sebagai fungsi secara alami melengkapi dan keluar).

Tanpa lebih banyak definisi fungsi Anda, saya tidak bisa mengatakan mengapa Anda mendapatkan objek PSMethod. Dugaan saya adalah bahwa Anda mungkin memiliki sesuatu beberapa baris di atas yang tidak ditangkap dan ditempatkan pada pipa keluaran.

Perlu juga dicatat bahwa Anda mungkin tidak membutuhkan titik koma itu - kecuali jika Anda bersarang banyak ekspresi pada satu baris.

Anda dapat membaca lebih lanjut tentang semantik kembali di halaman about_Return di TechNet, atau dengan menjalankan help returnperintah dari PowerShell itu sendiri.

Goyuix
sumber
13
jadi apakah ada cara untuk mengembalikan nilai scaler dari suatu fungsi?
ChiliYago
10
Jika fungsi Anda mengembalikan hashtable, maka ia akan memperlakukan hasilnya seperti itu, tetapi jika Anda "menggemakan" apa pun di dalam tubuh fungsi, termasuk output dari perintah lain, dan tiba-tiba hasilnya bercampur.
Luke Puplett
5
Jika Anda ingin menampilkan hal-hal ke layar dari dalam suatu fungsi tanpa mengembalikan teks itu sebagai bagian dari hasil fungsi, gunakan perintah write-debugsetelah mengatur variabel $DebugPreference = "Continue"sebagai gantinya"SilentlyContinue"
BeowulfNode42
7
Ini membantu, tetapi stackoverflow.com/questions/22663848/… ini bahkan lebih membantu. Pipa hasil yang tidak diinginkan dari panggilan perintah / fungsi dalam fungsi yang dipanggil melalui "... | Out-Null" dan kemudian kembalikan hal yang Anda inginkan.
Straff
1
@LukePuplett echoadalah masalah bagi saya! Titik kecil itu sangat penting. Saya mengembalikan hashtable, mengikuti yang lainnya, tapi tetap saja nilai pengembaliannya tidak seperti yang saya harapkan. Dihapus echodan bekerja seperti pesona.
Ayo I
54

Bagian dari PowerShell ini mungkin merupakan aspek yang paling bodoh. Output luar yang dihasilkan selama suatu fungsi akan mencemari hasilnya. Terkadang tidak ada output, dan kemudian dalam beberapa kondisi ada beberapa output yang tidak terencana, selain nilai pengembalian yang Anda rencanakan.

Jadi, saya menghapus tugas dari panggilan fungsi asli, sehingga output berakhir di layar, dan kemudian melangkah sampai sesuatu yang saya tidak rencanakan untuk muncul di jendela debugger (menggunakan PowerShell ISE ).

Bahkan hal-hal seperti pemesanan variabel dalam lingkup luar menyebabkan output, seperti [boolean]$isEnabledyang akan memuntahkan False keluar kecuali Anda membuatnya [boolean]$isEnabled = $false.

Satu lagi yang bagus adalah $someCollection.Add("thing")yang mengeluarkan jumlah koleksi baru.

Luke Puplett
sumber
2
Mengapa Anda hanya memesan nama alih-alih melakukan praktik terbaik menginisialisasi variabel dengan benar dengan nilai yang ditentukan secara eksplisit.
BeowulfNode42
2
Maksud saya maksud Anda bahwa Anda memiliki blok deklarasi variabel pada awal setiap lingkup untuk setiap variabel yang digunakan dalam lingkup itu. Anda harus tetap, atau mungkin sudah, menginisialisasi semua variabel sebelum menggunakannya dalam bahasa apa pun termasuk C #. Juga penting melakukan [boolean]$adi PowerShell tidak benar-benar mendeklarasikan variabel $ a. Anda dapat mengkonfirmasi ini dengan mengikuti baris itu dengan baris $a.gettype()dan membandingkan output itu ketika Anda mendeklarasikan dan menginisialisasi dengan string $a = [boolean]$false.
BeowulfNode42
3
Anda mungkin benar, saya hanya lebih suka desain yang lebih eksplisit untuk mencegah penulisan yang tidak disengaja.
Luke Puplett
4
Berasal dari perspektif programmer, walaupun saya seorang PS-n00b, saya tidak begitu yakin saya menemukan ini yang terburuk dari banyak aspek menjengkelkan dari sintaks PS. Bagaimana mungkin ada orang yang berpikir lebih baik menulis "x-gt y" daripada "x> y"?!? Tentunya setidaknya operator built-in bisa menggunakan sintaks yang lebih ramah manusia?
The Dag
5
@ TheDag karena itu adalah shell pertama, bahasa pemrograman kedua; >dan <sudah digunakan untuk pengalihan aliran I / O ke / dari file. >=menulis stdout ke file bernama =.
TessellatingHeckler
38

Dengan PowerShell 5 kami sekarang memiliki kemampuan untuk membuat kelas. Ubah fungsi Anda menjadi kelas, dan kembali hanya akan mengembalikan objek segera sebelum itu. Ini adalah contoh sederhana yang nyata.

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

Jika ini adalah fungsi, output yang diharapkan adalah

Hello World
808979

tetapi sebagai kelas satu-satunya hal yang dikembalikan adalah bilangan bulat 808979. Kelas semacam jaminan bahwa itu hanya akan mengembalikan tipe yang dinyatakan atau tidak berlaku.

DaSmokeDog
sumber
6
Catatan. Ini juga mendukung staticmetode. Menuju sintaksis[test_class]::return_what()
Sancarn
1
"Jika ini adalah fungsi, output yang diharapkan adalah 'Hello World \ n808979" - Saya tidak berpikir itu benar? Nilai output selalu hanya nilai dari pernyataan terakhir, dalam kasus Anda return 808979, berfungsi atau tidak.
amn
1
@ am, terima kasih! Anda sangat benar, contohnya hanya akan mengembalikannya jika itu menghasilkan keluaran di dalam fungsi. Itulah yang mengganggu saya. Kadang-kadang fungsi Anda dapat menghasilkan output dan output ditambah nilai apa pun yang Anda tambahkan dalam pernyataan pengembalian akan dikembalikan. Kelas seperti yang Anda tahu hanya akan mengembalikan tipe yang dideklarasikan atau dibatalkan. Jika Anda lebih suka menggunakan fungsi maka satu metode mudah adalah dengan menetapkan fungsi ke variabel dan jika lebih dari nilai kembali dikembalikan var adalah array dan nilai kembali akan menjadi item terakhir dalam array.
DaSmokeDog
1
Nah, apa yang Anda harapkan dari perintah yang dipanggil Write-Output? Ini bukan output "konsol" yang dimaksud di sini, ini adalah output untuk fungsi / cmdlet. Jika Anda ingin membuang barang di konsol ada Write-Verbose, Write-Hostdan Write-Errorfungsi, antara lain. Pada dasarnya, Write-Outputlebih dekat ke returnsemantik, daripada ke prosedur keluaran konsol. Jangan menyalahgunakannya, gunakan Write-Verboseuntuk kata kerja, itulah gunanya .
amn
1
@ amn Saya sangat sadar ini bukan konsol. Itu adalah cara cepat untuk menunjukkan cara pengembalian penawaran dengan output yang tidak diharapkan di dalam fungsi vs kelas. dalam suatu fungsi semua output + nilai yang Anda kembalikan semuanya dikembalikan. NAMUN hanya nilai yang ditentukan dalam kembalinya kelas yang berlalu paket. Coba jalankan sendiri.
DaSmokeDog
31

Sebagai solusinya saya telah mengembalikan objek terakhir dalam array yang Anda dapatkan kembali dari fungsi ... Ini bukan solusi yang bagus, tetapi lebih baik daripada tidak sama sekali:

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

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

Secara keseluruhan, cara menulis yang sama secara garis lurus adalah:

$b = (someFunction $someParameter $andAnotherOne)[-1]
Jonesy
sumber
7
$b = $b[-1]akan menjadi cara yang lebih sederhana untuk mendapatkan elemen terakhir atau array, tetapi lebih sederhana lagi adalah dengan tidak menampilkan nilai yang tidak Anda inginkan.
Duncan
@Duncan Dan bagaimana jika saya ingin menampilkan barang-barang dalam fungsi, sementara pada saat yang sama saya juga menginginkan nilai kembali?
Utkan Gezer
@ TuAppelsin jika Anda ingin menulis hal-hal ke terminal kemudian gunakan write-hostuntuk output secara langsung.
Duncan
7

Saya melewati objek Hashtable sederhana dengan anggota hasil tunggal untuk menghindari kegilaan kembali karena saya juga ingin menampilkan ke konsol. Bertindak melalui referensi dengan referensi.

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

Anda dapat melihat contoh penggunaan nyata di proyek GitHub saya unpackTunes .

Apa yang Akan Menjadi Keren
sumber
4

Sulit untuk mengatakan tanpa melihat kode. Pastikan fungsi Anda tidak mengembalikan lebih dari satu objek dan Anda menangkap setiap hasil yang dibuat dari panggilan lain. Apa yang Anda dapatkan untuk:

@($returnURL).count

Pokoknya, dua saran:

Keluarkan objek ke string:

...
return [string]$rs

Atau cukup lampirkan dalam tanda kutip ganda, sama seperti di atas tetapi lebih pendek untuk mengetik:

...
return "$rs"
Shay Levy
sumber
1
Atau bahkan hanya "$rs"- returnhanya diperlukan ketika kembali lebih awal dari fungsi. Meninggalkan pengembalian adalah idiom PowerShell yang lebih baik.
David Clarke
4

Deskripsi fungsi hasil Luke skenario ini tampaknya benar. Saya hanya ingin memahami akar permasalahan dan tim produk PowerShell akan melakukan sesuatu tentang perilaku tersebut. Tampaknya cukup umum dan telah menghabiskan banyak waktu debugging.

Untuk mengatasi masalah ini saya telah menggunakan variabel global daripada mengembalikan dan menggunakan nilai dari pemanggilan fungsi.

Inilah pertanyaan lain tentang penggunaan variabel global: Mengatur variabel PowerShell global dari fungsi di mana nama variabel global adalah variabel yang diteruskan ke fungsi

Ted B.
sumber
3

Berikut ini hanya mengembalikan 4 sebagai jawaban. Ketika Anda mengganti ekspresi tambah untuk string itu mengembalikan string pertama.

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

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

StartingEnd(StartingMain)

Ini juga bisa dilakukan untuk array. Contoh di bawah ini akan mengembalikan "Teks 2"

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

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

StartingEnd(StartingMain)

Perhatikan bahwa Anda harus memanggil fungsi di bawah fungsi itu sendiri. Kalau tidak, saat pertama kali dijalankan, ia akan mengembalikan kesalahan yang tidak tahu apa itu "StartingMain".

Edwin
sumber
2

Jawaban yang ada sudah benar, tetapi kadang-kadang Anda tidak benar-benar mengembalikan sesuatu secara eksplisit dengan a Write-Outputatau a return, namun ada beberapa nilai misteri dalam hasil fungsi. Ini bisa menjadi output dari fungsi builtin likeNew-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

Semua kebisingan tambahan dari pembuatan direktori sedang dikumpulkan dan dipancarkan dalam output. Cara mudah untuk mengurangi ini adalah dengan menambahkan | Out-Nullke akhir New-Itempernyataan, atau Anda dapat menetapkan hasilnya ke variabel dan tidak menggunakan variabel itu. Akan terlihat seperti ini ...

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-Itemmungkin yang lebih terkenal dari ini, tetapi yang lain termasuk semua StringBuilder.Append*()metode, serta SqlDataAdapter.Fill()metode.

Pelit
sumber