Kapan saya harus menggunakan Write-Error vs. Throw? Mengakhiri kesalahan vs. tidak-berhenti

145

Melihat skrip Get-WebFile di PoshCode, http://poshcode.org/3226 , saya perhatikan alat aneh yang ini:

$URL_Format_Error = [string]"..."
Write-Error $URL_Format_Error
return

Apa alasannya dibandingkan dengan yang berikut ini?

$URL_Format_Error = [string]"..."
Throw $URL_Format_Error

Atau lebih baik lagi:

$URL_Format_Error = New-Object System.FormatException "..."
Throw $URL_Format_Error

Seperti yang saya pahami, Anda harus menggunakan Write-Error untuk kesalahan yang tidak berhenti, dan Lempar untuk mengakhiri kesalahan, jadi menurut saya Anda tidak boleh menggunakan Write-Error diikuti oleh Return. Apakah ada perbedaan?

Bill Barry
sumber
4
Maksud kamu apa? Jika Write_error memungkinkan skrip untuk melanjutkan, sangat dapat dimengerti untuk memiliki pernyataan pengembalian setelah Write-Error. Kesalahan telah dituliskan dan Anda kembali ke kode yang disebut fungsi di tempat pertama. Karena Throw adalah untuk menghentikan kesalahan, maka ia akan diakhiri secara otomatis sehingga pernyataan pengembalian dalam deklarasi lemparan tidak berguna
Gisli
1
@Gisli: Sangat penting untuk dicatat bahwa returntidak tidak kembali ke pemanggil dalam processblok fungsi (lanjutan); sebagai gantinya, ia melanjutkan ke objek input berikutnya dalam pipa. Memang, ini adalah skenario khas untuk menghasilkan kesalahan yang tidak berhenti: jika memproses objek input lebih lanjut masih dimungkinkan.
mklement0
1
Catatan yang Throwmenghasilkan galat pemutusan skrip , yang tidak sama dengan galat pemutusan pernyataan yang dipicu, misalnya, oleh Get-Item -NoSuchParameteratau 1 / 0.
mklement0

Jawaban:

188

Write-Errorharus digunakan jika Anda ingin memberi tahu pengguna tentang kesalahan yang tidak penting. Secara default yang dilakukannya hanyalah mencetak pesan kesalahan dalam teks merah pada konsol. Itu tidak menghentikan pipa atau loop dari melanjutkan. Throwdi sisi lain menghasilkan apa yang disebut terminating error. Jika Anda menggunakan lemparan, pipa dan / atau loop saat ini akan dihentikan. Bahkan semua eksekusi akan dihentikan kecuali jika Anda menggunakan struktur trapatau try/catchuntuk menangani kesalahan terminating.

Ada satu hal ke catatan, jika Anda menetapkan $ErrorActionPreferenceuntuk"Stop" dan menggunakan Write-Erroritu akan menghasilkan kesalahan terminating .

Dalam skrip yang ditautkan dengan kami temukan ini:

if ($url.Contains("http")) {
       $request = [System.Net.HttpWebRequest]::Create($url)
}
else {
       $URL_Format_Error = [string]"Connection protocol not specified. Recommended action: Try again using protocol (for example 'http://" + $url + "') instead. Function aborting..."
       Write-Error $URL_Format_Error
    return
   }

Sepertinya penulis fungsi itu ingin menghentikan eksekusi fungsi itu dan menampilkan pesan kesalahan di layar tetapi tidak ingin seluruh skrip berhenti dijalankan. Penulis skrip bisa menggunakan thrownamun itu berarti Anda harus menggunakan try/catchketika memanggil fungsi.

returnakan keluar dari ruang lingkup saat ini yang dapat berupa fungsi, skrip, atau blok skrip. Ini digambarkan dengan kode:

# A foreach loop.
foreach ( $i in  (1..10) ) { Write-Host $i ; if ($i -eq 5) { return } }

# A for loop.
for ($i = 1; $i -le 10; $i++) { Write-Host $i ; if ($i -eq 5) { return } }

Output untuk keduanya:

1
2
3
4
5

Satu gotcha di sini digunakan returndengan ForEach-Object. Itu tidak akan merusak pemrosesan seperti yang mungkin diharapkan.

Informasi lebih lanjut:

Andy Arismendi
sumber
Ok, jadi Throw akan menghentikan semuanya, Write-Error + return hanya akan menghentikan fungsi yang sekarang.
Bill Barry
@ BillBarry Saya memperbarui jawaban saya sedikit dengan penjelasan return.
Andy Arismendi
Bagaimana dengan Write-Error diikuti oleh exit (1) untuk memastikan kode kesalahan yang sesuai dikembalikan ke OS? Apakah itu pantas?
pabrams
19

Perbedaan utama antara cmdlet Write-Error dan kata kunci throw di PowerShell adalah bahwa yang pertama hanya mencetak beberapa teks ke aliran kesalahan standar (stderr) , sedangkan yang terakhir benar-benar menghentikan pemrosesan perintah atau fungsi yang sedang berjalan, yang kemudian ditangani oleh PowerShell dengan mengirimkan informasi tentang kesalahan ke konsol.

Anda dapat mengamati perilaku yang berbeda dari keduanya dalam contoh yang Anda berikan:

$URL_Format_Error = [string]"..."
Write-Error $URL_Format_Error
return

Dalam contoh ini returnkata kunci telah ditambahkan untuk secara eksplisit menghentikan eksekusi skrip setelah pesan kesalahan dikirim ke konsol. Dalam contoh kedua, di sisi lain, returnkata kunci tidak diperlukan karena penghentian secara implisit dilakukan oleh throw:

$URL_Format_Error = New-Object System.FormatException "..."
Throw $URL_Format_Error
Enrico Campidoglio
sumber
2
jika Anda memiliki $ ErrorActionPreference = "Stop", Write-Error juga akan menghentikan proses.
Michael Freidgeim
4
Info bagus, tetapi sementara aliran kesalahan PowerShell analog dengan aliran stderr berbasis teks di shell lain, seperti semua aliran PowerShell itu berisi objek , yaitu [System.Management.Automation.ErrorRecord]instance, yang secara default dikumpulkan dalam $Errorkoleksi otomatis ( $Error[0]berisi kesalahan terbaru). Bahkan jika Anda hanya menggunakan Write-Errordengan string , string itu akan dibungkus dalam sebuah [System.Management.Automation.ErrorRecord]instance.
mklement0
16

Penting : Ada 2 jenis kesalahan penghentian , yang sayangnya dikonfigurasikan oleh topik bantuan saat ini :

  • pernyataan- kesalahan yang memudar , seperti yang dilaporkan oleh cmdlet dalam situasi tertentu yang tidak dapat dipulihkan dan dengan ekspresi di mana pengecualian .NET / kesalahan runtime PS terjadi; hanya pernyataan yang diakhiri, dan eksekusi skrip berlanjut secara default .

  • script -terminating errors (lebih akurat: runspace-terminating ), baik dipicu olehThrowatau dengan meningkatkan salah satu dari jenis kesalahan lainnya melalui preferensi-tindakan-variabel / nilai parameterStop.
    Kecuali tertangkap, mereka menghentikan runspace saat ini (utas); yaitu, mereka menghentikan tidak hanya skrip saat ini, tetapi semua peneleponnya juga, jika berlaku).

Untuk ikhtisar komprehensif penanganan kesalahan PowerShell, lihat masalah dokumentasi GitHub ini .

Sisa dari posting ini berfokus pada kesalahan non-terminasi vs pernyataan-terminasi .


Untuk melengkapi jawaban membantu yang ada dengan fokus pada inti pertanyaan: Bagaimana Anda memilih apakah akan melaporkan statement- terminating atau non-terminating error ?

Pelaporan Kesalahan Cmdlet berisi panduan bermanfaat; izinkan saya mencoba ringkasan pragmatis :

Gagasan umum di balik kesalahan non-terminating adalah untuk memungkinkan pemrosesan "toleran-kesalahan" dari set input besar : kegagalan untuk memproses subset objek input tidak boleh (secara default) membatalkan proses - berpotensi berjalan lama - secara keseluruhan , memungkinkan Anda untuk memeriksa kesalahan dan hanya memproses ulang objek yang gagal nanti - seperti yang dilaporkan melalui catatan kesalahan yang dikumpulkan dalam variabel otomatis $Error.

  • Laporkan kesalahan NON-TERMINASI , jika cmdlet / fungsi lanjutan Anda:

    • menerima objek input GANDA , melalui input pipa dan / atau parameter bernilai array, DAN
    • kesalahan terjadi untuk objek input SPESIFIK , DAN
    • kesalahan ini JANGAN MENCEGAH PENGOLAHAN objek input LEBIH LANJUT DALAM PRINSIP ( situasional , mungkin tidak ada objek input yang tersisa dan / atau objek input sebelumnya mungkin telah berhasil diproses).
      • Dalam fungsi lanjutan, gunakan $PSCmdlet.WriteError()untuk melaporkan kesalahan yang tidak berakhir ( Write-Error, sayangnya, tidak menyebabkan $?diatur ke $Falsedalam ruang lingkup penelepon - lihat masalah GitHub ini ).
      • Menangani kesalahan non-terminasi: $?memberi tahu Anda apakah perintah terbaru melaporkan setidaknya satu kesalahan non-terminasi.
        • Jadi, $?menjadi $Falsedapat berarti bahwa setiap subset objek input (tidak kosong) tidak diproses dengan benar, mungkin seluruh set.
        • Variabel preferensi $ErrorActionPreferencedan / atau parameter cmdlet umum -ErrorActiondapat memodifikasi perilaku kesalahan yang tidak berhenti (hanya) dalam hal perilaku output kesalahan dan apakah kesalahan yang tidak berhenti harus ditingkatkan ke script yang menghapus script .
  • Laporkan kesalahan PENGAKHIRAN PERNYATAAN dalam semua kasus lainnya .

    • Khususnya, jika kesalahan terjadi dalam fungsi cmdlet / lanjutan yang hanya menerima objek input TUNGGAL atau NO dan output TIDAK atau objek output TUNGGAL atau hanya mengambil input parameter dan nilai parameter yang diberikan mencegah operasi yang berarti.
      • Dalam fungsi lanjutan, Anda harus menggunakan $PSCmdlet.ThrowTerminatingError()untuk menghasilkan kesalahan penghentian pernyataan.
      • Perhatikan bahwa, sebaliknya, Throwkata kunci menghasilkan kesalahan penghentian skrip yang membatalkan seluruh skrip (secara teknis: utas saat ini ).
      • Menangani kesalahan penghentian pernyataan: try/catchPawang atau trappernyataan dapat digunakan (yang tidak dapat digunakan dengan kesalahan yang tidak berhenti ), tetapi perhatikan bahwa bahkan kesalahan pemutusan pernyataan secara default tidak mencegah sisa skrip agar tidak berjalan. Seperti halnya kesalahan non-terminasi , $?mencerminkan $Falsejika pernyataan sebelumnya memicu kesalahan terminasi-pernyataan.

Sayangnya, tidak semua cmdlet inti PowerShell sendiri sesuai dengan aturan ini :

  • Meskipun tidak mungkin gagal, New-TemporaryFile(PSv5 +) akan melaporkan kesalahan non-terminasi jika gagal, meskipun tidak menerima input pipa dan hanya menghasilkan satu objek output - ini telah diperbaiki setidaknya dari PowerShell [Core] 7.0, namun: lihat GitHub ini masalah .

  • Resume-JobBantuan menyatakan bahwa meneruskan jenis pekerjaan yang tidak didukung (seperti pekerjaan yang dibuat dengan Start-Job, yang tidak didukung, karena Resume-Jobhanya berlaku untuk pekerjaan alur kerja) menyebabkan kesalahan penghentian, tetapi itu tidak berlaku pada PSv5.1.

mklement0
sumber
8

Write-Errormemungkinkan konsumen fungsi untuk menekan pesan kesalahan dengan -ErrorAction SilentlyContinue(sebagai alternatif -ea 0). Sementara throwmembutuhkan atry{...} catch {..}

Untuk menggunakan coba ... tangkap dengan Write-Error:

try {
    SomeFunction -ErrorAction Stop
}
catch {
    DoSomething
}
Rynant
sumber
Mengapa Anda perlu memanggil Write-Error sama sekali, jika Anda ingin menekan pesan kesalahan?
Michael Freidgeim
8

Tambahan untuk jawaban Andy Arismendi :

Apakah Write-Error menghentikan proses atau tidak tergantung pada $ErrorActionPreferencepengaturan.

Untuk skrip non-sepele, $ErrorActionPreference = "Stop"adalah pengaturan yang disarankan untuk gagal cepat.

"Perilaku default PowerShell sehubungan dengan kesalahan, yaitu melanjutkan kesalahan ... terasa sangat VB6" On Error Resume Next "-ish"

(dari http://codebetter.com/jameskovacs/2010/02/25/the-exec-problem/ )

Namun, itu membuat Write-Errorpanggilan berakhir.

Untuk menggunakan Write-Error sebagai perintah non-terminating terlepas dari pengaturan lingkungan lainnya, Anda dapat menggunakan parameter umum -ErrorAction dengan nilai Continue:

 Write-Error "Error Message" -ErrorAction:Continue
Michael Freidgeim
sumber
0

Jika pembacaan kode Anda benar maka Anda benar. Mengakhiri kesalahan harus digunakan throw, dan jika Anda berurusan dengan tipe .NET maka akan sangat membantu untuk juga mengikuti konvensi pengecualian .NET.

yzorg
sumber