Kiedy należy używać błędu zapisu, a kiedy rzutu? Błędy kończące a nie kończące się

145

Patrząc na skrypt Get-WebFile na PoshCode, http://poshcode.org/3226 , zauważyłem to dziwne dla mnie urządzenie:

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

Jaki jest tego powód w przeciwieństwie do następującego?

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

Albo jeszcze lepiej:

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

Jak rozumiem, w przypadku błędów nie kończących się należy używać funkcji Write-Error, a do usuwania błędów Throw, więc wydaje mi się, że nie należy używać opcji Write-Error, po której następuje Return. Czy jest jakaś różnica?

Bill Barry
źródło
4
Co masz na myśli? Jeśli Write_error pozwala na kontynuowanie skryptu, zrozumiałe jest, że po błędzie Write-Error pojawia się instrukcja return. Błąd został zapisany i wracasz z powrotem do kodu, który wywołał funkcję w pierwszej kolejności. Ponieważ Throw służy do kończenia błędów, zakończy się automatycznie, więc instrukcja return w deklaracji rzutu jest bezużyteczna
Gisli
1
@Gisli: Ważne jest, aby pamiętać, że returnnie nie powrócić do rozmówcy w processblokowym (zaawansowane) funkcji; zamiast tego przechodzi do następnego obiektu wejściowego w potoku. Rzeczywiście, jest to typowy scenariusz generowania błędów nie kończących się: jeśli przetwarzanie dalszych obiektów wejściowych jest nadal możliwe.
mklement0
1
Zwróć uwagę, że Throwgeneruje błąd kończący skrypt , który nie jest tym samym, co błąd kończący instrukcję, wywołany na przykład przez Get-Item -NoSuchParameterlub 1 / 0.
mklement0

Odpowiedzi:

188

Write-Errorpowinien być używany, jeśli chcesz poinformować użytkownika o niekrytycznym błędzie. Domyślnie wszystko, co robi, to wyświetla na konsoli komunikat o błędzie w kolorze czerwonym. Nie zatrzymuje kontynuacji potoku lub pętli. Throwz drugiej strony powoduje tak zwany błąd kończący. Jeśli użyjesz throw, potok i / lub pętla prądowa zostaną zakończone. W rzeczywistości całe wykonanie zostanie zakończone, chyba że użyjesz struktury traplub try/catchdo obsługi błędu zakończenia.

Jest jedna rzecz, na którą należy zwrócić uwagę, jeśli ustawisz $ErrorActionPreference"Stop" i użyjesz Write-Error, spowoduje to błąd zakończenia .

W skrypcie, do którego utworzyłeś łącze, znajdujemy to:

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
   }

Wygląda na to, że autor tej funkcji chciał zatrzymać wykonywanie tej funkcji i wyświetlić komunikat o błędzie na ekranie, ale nie chciał, aby cały skrypt przestał się wykonywać. Autor skryptu mógł użyć, throwjednak oznaczałoby to, że try/catchpodczas wywoływania funkcji musiałbyś użyć a .

returnopuści bieżący zakres, którym może być funkcja, skrypt lub blok skryptu. Najlepiej ilustruje to kod:

# 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 } }

Wyjście dla obu:

1
2
3
4
5

Jedna gotcha tutaj używa returnz ForEach-Object. Nie przerwie przetwarzania, jak można by się spodziewać.

Więcej informacji:

Andy Arismendi
źródło
Ok, więc Throw zatrzyma wszystko, Write-Error + return zatrzyma tylko bieżącą funkcję.
Bill Barry
@BillBarry Zaktualizowałem nieco moją odpowiedź z wyjaśnieniem return.
Andy Arismendi
A co z błędem zapisu, po którym następuje wyjście (1), aby zapewnić powrót odpowiedniego kodu błędu do systemu operacyjnego? Czy to kiedykolwiek jest właściwe?
pabrams
19

Główna różnica między cmdletem Write-Error a słowem kluczowym throw w PowerShell polega na tym, że to pierwsze po prostu drukuje tekst do standardowego strumienia błędów (stderr) , podczas gdy drugie faktycznie kończy przetwarzanie uruchomionego polecenia lub funkcji, która jest następnie obsługiwana przez PowerShell, wysyłając informacje o błędzie do konsoli.

Możesz zaobserwować różne zachowanie tych dwóch w podanych przykładach:

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

W tym przykładzie returndodano słowo kluczowe, aby jawnie zatrzymać wykonywanie skryptu po wysłaniu komunikatu o błędzie do konsoli. Z drugiej strony w drugim przykładzie returnsłowo kluczowe nie jest konieczne, ponieważ zakończenie jest niejawnie wykonywane przez throw:

$URL_Format_Error = New-Object System.FormatException "..."
Throw $URL_Format_Error
Enrico Campidoglio
źródło
2
jeśli masz $ ErrorActionPreference = "Stop", Write-Error również zakończy proces.
Michael Freidgeim,
4
Dobra informacja, ale chociaż strumień błędów programu PowerShell jest analogiczny do strumienia stderr opartego na tekście w innych powłokach, podobnie jak wszystkie strumienie programu PowerShell, zawiera obiekty , a mianowicie [System.Management.Automation.ErrorRecord]wystąpienia, które są domyślnie gromadzone w automatycznej $Errorkolekcji ( $Error[0]zawiera najnowszy błąd). Nawet jeśli użyjesz tylko Write-Errorz łańcuchem , ten ciąg zostanie zawinięty w [System.Management.Automation.ErrorRecord]instancję.
mklement0
16

Ważne : istnieją 2 rodzaje błędów kończących , które niestety łączą aktualne tematy pomocy :

  • błędy kończące instrukcję , zgłaszane przez polecenia cmdlet w pewnych sytuacjach, których nie można odzyskać oraz przez wyrażenia, w których występuje wyjątek .NET / błąd wykonania PS; tylko instrukcja jest przerywana, a wykonywanie skryptu jest domyślnie kontynuowane .

  • Skrypt -terminating błędy (dokładniej: runspace kończącego ), jak też wywołane przezThrowlub eskalacji jeden z pozostałych typów błędów poprzez błędów działania preferencji wartości zmiennej / parametrówStop.
    O ile nie zostaną złapane, kończą bieżący obszar roboczy (wątek); to znaczy, przerywają nie tylko bieżący skrypt, ale także wszystkie jego wywołania, jeśli ma to zastosowanie).

Aby uzyskać kompleksowe omówienie obsługi błędów programu PowerShell, zobacz ten problem z dokumentacją usługi GitHub .

Pozostała część tego postu koncentruje się na błędach nierozstrzygających i kończących instrukcje .


Aby uzupełnić istniejące pomocne odpowiedzi, skupiając się na sednie pytania: Jak zdecydować, czy zgłosić błąd kończący czy nie kończący wypowiedzi ?

Raportowanie błędów polecenia cmdlet zawiera pomocne wskazówki; spróbuję pragmatyczne podsumowanie :

Ogólną ideą niekończący błędów jest umożliwienie „Odporne na uszkodzenia” przetwarzanie dużych zbiorów wejściowych : niewydolność przetworzyć podzbiór obiektów wejściowych nie powinna (domyślnie) abort the - potencjalnie długotrwały - proces jako całość , co pozwala na sprawdzenie błędów i późniejsze ponowne przetworzenie tylko uszkodzonych obiektów - zgodnie z raportami błędów zgromadzonymi w zmiennej automatycznej $Error.

  • Zgłoś błąd NIE ZAKOŃCZENIE , jeśli polecenie cmdlet / funkcja zaawansowana:

    • akceptuje MULTIPLE obiektów wejściowych , poprzez wejście potoku i / lub parametry z wartościami tablicy, AND
    • występują błędy dla SPECIFIC obiektów wejściowych , AND
    • te błędy NIE ZAPOBIEGAJĄ PRZETWARZANIU DALSZYCH obiektów wejściowych W ZASADZIE (w sytuacjach może nie być żadnych obiektów wejściowych i / lub poprzednie obiekty wejściowe mogły już zostać pomyślnie przetworzone).
      • W zaawansowanych funkcjach służy $PSCmdlet.WriteError()do zgłaszania błędu nie kończącego się ( Write-Errorniestety nie powoduje $?to ustawienia $Falsew zakresie wywołującego - zobacz ten problem na GitHubie ).
      • Obsługa błędu niepowodującego zakończenia: $?informuje, czy ostatnie polecenie zgłosiło przynajmniej jeden błąd niepowodujący zakończenia.
        • Zatem $?bycie $Falsemoże oznaczać, że dowolny (niepusty) podzbiór obiektów wejściowych nie został poprawnie przetworzony, prawdopodobnie cały zbiór.
        • Zmienna preferencji $ErrorActionPreferencei / lub wspólny parametr polecenia cmdlet -ErrorActionmogą modyfikować zachowanie niekończących błędów (tylko) pod względem zachowania wyjścia błędu oraz tego, czy błędy niekończące powinny być eskalowane do błędów kończących skrypt .
  • Zgłoś błąd STATEMENT-TERMINATING we wszystkich innych przypadkach .

    • W szczególności, jeśli błąd wystąpi w poleceniu cmdlet / funkcji zaawansowanej, która akceptuje tylko SINGLE lub NO obiekt wejściowy i wyprowadza NO lub SINGLE obiekt wyjściowy lub przyjmuje tylko dane wejściowe parametrów, a podane wartości parametrów uniemożliwiają sensowne działanie.
      • W funkcjach zaawansowanych należy użyć $PSCmdlet.ThrowTerminatingError(), aby wygenerować błąd kończący instrukcję.
      • Zwróć uwagę, że Throwsłowo kluczowe generuje błąd kończący skrypt, który przerywa cały skrypt (technicznie: bieżący wątek ).
      • Obsługa błędu kończącego instrukcję: Można użyć try/catchprocedury obsługi lub trapinstrukcji (której nie można używać z błędami nie kończącymi instrukcji ), ale należy zauważyć, że nawet domyślne błędy kończące instrukcję nie uniemożliwiają uruchomienia reszty skryptu. Podobnie jak w przypadku błędów nie kończących instrukcji , $?odzwierciedla, $Falseczy poprzednia instrukcja wywołała błąd kończący instrukcję.

Niestety, nie wszystkie podstawowe polecenia cmdlet programu PowerShell działają według następujących reguł :

  • Chociaż jest mało prawdopodobne, że się nie powiedzie, New-TemporaryFile(PSv5 +) zgłosi błąd nie kończący się, jeśli się nie powiedzie, pomimo braku akceptowania danych wejściowych potoku i wytwarzania tylko jednego obiektu wyjściowego - zostało to poprawione co najmniej od wersji PowerShell [Core] 7.0, jednak: zobacz ten GitHub problem .

  • Resume-JobPomoc twierdzi, że przekazanie nieobsługiwanego typu zadania (takiego jak zadanie utworzone za pomocą Start-Job, które nie jest obsługiwane, ponieważ Resume-Jobdotyczy tylko zadań przepływu pracy) powoduje błąd zakończenia, ale nie jest to prawdą w przypadku PSv5.1.

mklement0
źródło
8

Write-Errorumożliwia konsumentowi funkcji pominięcie komunikatu o błędzie za pomocą -ErrorAction SilentlyContinue(alternatywnie -ea 0). Chociaż throwwymagatry{...} catch {..}

Aby skorzystać z try ... catch with Write-Error:

try {
    SomeFunction -ErrorAction Stop
}
catch {
    DoSomething
}
Rynant
źródło
Dlaczego w ogóle musisz wywoływać błąd zapisu, jeśli chcesz pominąć komunikat o błędzie?
Michael Freidgeim
8

Dodatek do odpowiedzi Andy'ego Arismendiego :

To, czy błąd zapisu kończy proces, czy nie, zależy od $ErrorActionPreferenceustawienia.

Skryptów nietrywialnych, $ErrorActionPreference = "Stop"jest zalecanym ustawieniem szybko zawieść.

„Domyślne zachowanie programu PowerShell w odniesieniu do błędów, które polega na kontynuowaniu w przypadku błędu ... wydaje się bardzo podobne do VB6„ Po wznowieniu błędu ”-ish

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

Jednak powoduje Write-Errorzakończenie połączeń.

Aby użyć błędu zapisu jako niezakończonego polecenia niezależnie od innych ustawień środowiska, można użyć wspólnego parametru -ErrorAction z wartością Continue:

 Write-Error "Error Message" -ErrorAction:Continue
Michael Freidgeim
źródło
0

Jeśli odczytanie kodu jest poprawne, to masz rację. Kończenie błędów powinno być używane throw, a jeśli masz do czynienia z typami .NET, pomocne jest również przestrzeganie konwencji wyjątków .NET.

yzorg
źródło