沒有任何腳本或程序能夠保證在任何情況下毫無錯(cuò)誤地執(zhí)行,在外界條件變化的情況下,需要預(yù)防可能出錯(cuò)之處。本文將介紹PowerShell如何依靠全局錯(cuò)誤處理檢測捕捉并處理執(zhí)行腳本或代碼引起的錯(cuò)誤,要注意的是其中的例子包含錯(cuò)誤處理方法和調(diào)試信息。本文還將舉例說明如何在反常條件下、錯(cuò)誤的輸入數(shù)據(jù),以及隱含的錯(cuò)誤情況下捕獲異常。
PowerShell中的錯(cuò)誤(Error)分為終止(terminating)和非終止(nonterminating),第1種類似很多編程語言中的異常(exception),通常是因?yàn)槊罱忉屍饔龅疆惓l件而不能繼續(xù)。一旦拋出這類錯(cuò)誤,后面的代碼執(zhí)行均將結(jié)束。典型的終止錯(cuò)誤由腳本語法錯(cuò)誤、調(diào)用不存在的命令或者類似的錯(cuò)誤引起,開發(fā)人員使用throw語句拋出的錯(cuò)誤也會(huì)終止程序繼續(xù)執(zhí)行。
非終止錯(cuò)誤通常用于標(biāo)識(shí)當(dāng)前命令不能完整執(zhí)行,但是不影響繼續(xù)執(zhí)行后續(xù)代碼。PowerShell提供為錯(cuò)誤保留的管道,類似正常對象輸出管道。此類管道專為非終止錯(cuò)誤準(zhǔn)備,默認(rèn)為用紅色字體顯示所有信息。典型的非終止錯(cuò)誤是文件檢測操作,其中的原因有多種,大多數(shù)情況下會(huì)選擇繼續(xù)執(zhí)行。
1 常見錯(cuò)誤處理參數(shù)
如下代碼在Error-NonTermination.ps1腳本文件中,其中定義了一個(gè)名為“Raise-NonTerminatingError”的函數(shù)用于觸發(fā)非終止錯(cuò)誤:
function Raise-NonTerminatingError { Del nosuchfile.txt } Write-Host “script start” Raise-NonTerminatingError Write-Host “script end”
上例分別在調(diào)用Raise-NonTerminatingError函數(shù)前后向控制臺(tái)輸出診斷信息,圖1所示為執(zhí)行腳本時(shí)的輸出。
圖1 執(zhí)行腳本時(shí)的輸出
雖然在執(zhí)行過程中獲得錯(cuò)誤信息,但是顯示了錯(cuò)誤函數(shù)前后的診斷信息,說明腳本在發(fā)生錯(cuò)誤之后仍然繼續(xù)執(zhí)行。
下面的腳本文件調(diào)用包含終止錯(cuò)誤的Raise-TeminatingError函數(shù):
function Raise-NonTerminatingError { Del nosuchfile.txt } function Raise-TerminatingError { Del nosuchfile-terminating.txt -ErrorAction Stop } Write-Host "script start" Raise-NonTerminatingError Raise-TerminatingError Write-Host "script end"
其中后一個(gè)函數(shù)中刪除不存在文件的操作將終止所有操作,即拋出了終止錯(cuò)誤,圖2所示為執(zhí)行腳本時(shí)的輸出。
圖2 執(zhí)行腳本時(shí)的輸出
在上例中顯示兩次錯(cuò)誤提示,nonterminating函數(shù)嘗試刪除nonexistent文件在先。第2個(gè)錯(cuò)誤拋出之后未顯示這個(gè)函數(shù)后面的調(diào)試信息,說明腳本在執(zhí)行到錯(cuò)誤發(fā)生的臨界狀態(tài)下中止。讀者也可以嘗試交換兩個(gè)函數(shù)的執(zhí)行順序,以查看執(zhí)行結(jié)果。
所有PowerShell的cmdlet均支持一系列錯(cuò)誤處理的參數(shù),用戶可以運(yùn)行任何cmdlet指定在錯(cuò)誤發(fā)生時(shí)產(chǎn)生特定附加輸出信息或者執(zhí)行特定的操作,ErrorAction、ErrorVariable、Debug和Verbose是每個(gè)cmdlet支持的常見錯(cuò)誤處理參數(shù)。
ErrorAction參數(shù)的取值如下。
(1)Continue:默認(rèn)值,報(bào)告錯(cuò)誤并繼續(xù)執(zhí)行后面的代碼。
(2)Stop:報(bào)告錯(cuò)誤并停止執(zhí)行后續(xù)代碼,此選項(xiàng)會(huì)使cmdlet拋出終止錯(cuò)誤,通常會(huì)終止后續(xù)所有代碼的執(zhí)行。這里所說的“通常”意味著也可以使用錯(cuò)誤陷阱捕獲和處理錯(cuò)誤,而不是終止整個(gè)腳本的執(zhí)行。
(3)SilentlyContinue:靜默繼續(xù),忽略錯(cuò)誤并繼續(xù)執(zhí)行后面的代碼,但是不輸出任何錯(cuò)誤信息。圖3所示為忽略文件刪除失敗。
圖3 忽略文件刪除失敗
(4)Inqurie:詢問用戶操作的取舍,由用戶決定下一步如何操作,如圖4所示。
圖4 出錯(cuò)時(shí)詢問用戶需要的操作
選擇Yes或Yes to All,顯示一個(gè)錯(cuò)誤信息并繼續(xù)執(zhí)行;選擇Halt(終止),結(jié)果類似Stop選項(xiàng),則報(bào)告錯(cuò)誤并停止執(zhí)行后續(xù)代碼;選擇Suspend(掛起)選項(xiàng),則臨時(shí)掛起程序。此時(shí)可以在暫時(shí)保留當(dāng)前現(xiàn)場的前提下退出提示符并執(zhí)行某個(gè)操作,然后可以使用exit命令返回到之前保留的現(xiàn)場中。圖5所示為選擇Suspend后的輸出。
圖5 出錯(cuò)時(shí)用戶選擇Suspend對錯(cuò)誤掛起可恢復(fù)出錯(cuò)現(xiàn)場
PowerShell提供的ErrorVariable參數(shù)用于獲取cmdlet產(chǎn)生的錯(cuò)誤對象。當(dāng)程序中使用了SilentlyContinue(靜默繼續(xù))這個(gè)ErrorAction隱藏了錯(cuò)誤輸出時(shí),需要根據(jù)錯(cuò)誤對象了解發(fā)生的錯(cuò)誤。在程序中可以用檢測配置為錯(cuò)誤容器的變量是否包含錯(cuò)誤來驗(yàn)證是否有錯(cuò)誤產(chǎn)生。創(chuàng)建一個(gè)新的腳本文件“ErrorVariable.ps1”,在其中操作錯(cuò)誤變量測試錯(cuò)誤條件,代碼如下:
$files = dir C:\ -ErrorAction SilentlyContinue ` -ErrorVariable errorVar if ($errorVar) { Write-Host "A error occurred getting files!" } del nosuchfile.txt -ErrorAction SilentlyContinue ` -ErrorVariable errorVar if ($errorVar) { $realError = $errorVar[0] $realError | ` Select FullQualifiedErrorId,CategoryInfo,TargetObject | ` Format-List $realError.InvocationInfo | ` Select ScriptNamme,ScripptLineNumber,Line | Format-List }
發(fā)生錯(cuò)誤時(shí)$errorVar變量可以保存一個(gè)集合。第2個(gè)if語句塊獲取錯(cuò)誤對象的屬性并使用Format-List語句打印到控制臺(tái),執(zhí)行腳本時(shí)的輸出圖6所示。
圖6 執(zhí)行腳本時(shí)的輸出
其中獲取的錯(cuò)誤對象是System.Management.Automation.ErrorRecord,F(xiàn)ullQualifiedErrorId是分辨不同錯(cuò)誤的唯一標(biāo)識(shí)。由于不同語言的Windows操作系統(tǒng)中的錯(cuò)誤信息不同,所以兼容性好的腳本不應(yīng)使用字符串操作判斷錯(cuò)誤信息。ObjectNotFound為錯(cuò)誤分類,在類似的錯(cuò)誤中經(jīng)常出現(xiàn)。TargetObject屬性指向發(fā)生錯(cuò)誤時(shí)腳本操作的對象,Exception屬性中包含錯(cuò)誤記錄的真實(shí)的.NET異常。上面的腳本中包含很多冗長的解釋操作,因而輸出了記錄錯(cuò)誤屬性的InvocationInfo對象。通過該對象用戶可以獲得產(chǎn)生錯(cuò)誤的具體腳本行,從而定位到相應(yīng)腳本文件的具體位置。
Verbose和Debug參數(shù)用于產(chǎn)生主要用于腳本和cmdlet調(diào)試的附加調(diào)試信息,應(yīng)輸出到控制臺(tái)。通常情況下不會(huì)輸出此類詳細(xì)的信息,以免給用戶造成誤會(huì),但它對于調(diào)試和排除錯(cuò)誤非常有效。
2 誘捕錯(cuò)誤
在調(diào)用帶有SilentlyContinue的ErrorAction操作后帶一個(gè)if語句塊來檢測是否發(fā)生錯(cuò)誤是調(diào)試較少命令組成的腳本塊時(shí)的較好選擇,這是一種很有效的方法;缺點(diǎn)是限制了調(diào)用cmdlet的操作,僅可捕獲由其他操作拋出的錯(cuò)誤。
PowerShell采用錯(cuò)誤陷阱的語法結(jié)構(gòu)來解決這個(gè)問題,陷阱是處理特定錯(cuò)誤的代碼塊結(jié)構(gòu)。在腳本文件、函數(shù)或者腳本塊中注冊,當(dāng)錯(cuò)誤發(fā)生時(shí)將執(zhí)行的這段代碼塊。當(dāng)輸入錯(cuò)誤的字符串格式時(shí),[datetime]操作符將會(huì)拋出錯(cuò)誤。下面使用陷阱寫一個(gè)腳本文件“Date-Traps.ps1”,在其中將會(huì)遍歷字符串集合并轉(zhuǎn)換為日期。能夠看到PowerShell拋出了System.Management.Autommation.PSInvalidCastException錯(cuò)誤并通過添加陷阱來操作,代碼如下:
trap [System.Management.Automation.PSInvalidCastException] { Write-Host "Error converting a string to date!" $realException = $_.Exception.InnerException Write-Host "Inner Exception: $($realException.GetType())" } $dateStrings = "11/16/2008", "bad/date/format" foreach ($dateString in $dateStrings) { $date = [datetime] $dateString Write-Host $date }
當(dāng)trap語句接受腳本塊時(shí),完全照傳遞的參數(shù)轉(zhuǎn)換并將當(dāng)前的ErrorRecord傳遞給$_變量,Exception屬性包含由Shell拋出的PSInvalidCastException錯(cuò)誤。
如圖7所示,捕獲異常并顯示錯(cuò)誤提示。其中成功轉(zhuǎn)換第1個(gè)數(shù)據(jù)。轉(zhuǎn)換第2個(gè)數(shù)據(jù)觸發(fā)錯(cuò)誤,執(zhí)行陷阱后顯示拋出的異常是System.FormatException。
圖7 捕獲異常并顯示錯(cuò)誤提示
(1)終止錯(cuò)誤并拋出異常
為了隱藏錯(cuò)誤,需要使用continue語句終止錯(cuò)誤輸出,并且執(zhí)行后續(xù)的腳本語句。接下來將上例修改為“Date-TrapsWithContinue.ps1”文件,在其中增加時(shí)間轉(zhuǎn)換操作,并在循環(huán)前后分別增加診斷語句。然后增加continue語句來處理錯(cuò)誤,代碼如下:
trap [System.Management.Automation.PSInvalidCastException] { Write-Host "Error converting a string to date!" $realException = $_.Exception.InnerException Write-Host "Inner Exception: $($realException.GetType())" continue } Write-Host "script begin" $dateStrings = "11/16/2008", "bad/date/format", "03/20/2009" foreach ($dateString in $dateStrings) { $date = [datetime] $dateString Write-Host $date } Write-Host "script end"
執(zhí)行結(jié)果如圖8所示,其中并未包含默認(rèn)的錯(cuò)誤信息,continue語句終止當(dāng)前循環(huán)并開始執(zhí)行下一個(gè)循環(huán)。正如所看到的,并未轉(zhuǎn)換有意在錯(cuò)誤字符串后面放置的“03/20/2009”的字符串。因?yàn)檎麄(gè)循環(huán)本身已經(jīng)由于錯(cuò)誤的存在而被終止,所以執(zhí)行循環(huán)以外的輸出語句。
圖8 執(zhí)行結(jié)果
嚴(yán)重的錯(cuò)誤需要特殊處理,如清理現(xiàn)場并通知用戶,以及停止執(zhí)行后續(xù)代碼,此時(shí)需要使用break語句。將上例修改為“Date-TrapsWithBreak.ps1”文件,其中陷阱部分的代碼如下:
trap [System.Management.Automation.PSInvalidCastException] { Write-Host "Error converting a string to date!" $realException = $_.Exception.InnerException Write-Host "Inner Exception: $($realException.GetType())" break }
執(zhí)行結(jié)果如圖9所示,其中提示用戶錯(cuò)誤格式錯(cuò)誤,并且停止執(zhí)行后續(xù)代碼,這樣用戶可以在改正代碼問題后繼續(xù)。
圖9 執(zhí)行結(jié)果
可以注冊的陷阱數(shù)沒有限制,這樣能夠處理不同的錯(cuò)誤。為了演示如何處理不同數(shù)據(jù)格式的轉(zhuǎn)換錯(cuò)誤,創(chuàng)建一個(gè)名為“Number-Trap.ps1”的腳本。其中嘗試將字符串轉(zhuǎn)換為數(shù)字,并包含零除錯(cuò)誤,代碼如下:
Write-Host "script begin" [int] "not a number" $denominator = 0 $result = 50 / $denominator Write-Host "script end" trap [System.Management.Automation.PSInvalidCastException] { Write-Host "Error converting a string to a number!" continue } trap [System.DivideByZeroException] { Write-Host "Attempted to divide by zero!" continue }
代碼中的錯(cuò)誤會(huì)先后被不同的兩個(gè)陷阱捕獲,并輸出自定義的提示。陷阱聲明的位置并不影響陷阱捕獲異常,如果不考慮代碼的可讀性的話,甚至可以把異常的捕獲放置到正常的代碼行之間。并且定義的陷阱順序也無關(guān)緊要。同時(shí)不需要區(qū)分陷阱的優(yōu)先級來排列陷阱的順序,因?yàn)镻owerShell會(huì)在執(zhí)行代碼之前首先解析代碼并創(chuàng)建所有陷阱。并且忽略用戶以何種順序排列陷阱,都會(huì)按照自己的解析模式來拋出響應(yīng)的異常,感興趣的讀者可以切換陷阱的順序來驗(yàn)證。腳本的執(zhí)行結(jié)果如圖10所示。
圖10 執(zhí)行結(jié)果
很多時(shí)候,用戶并不能總知道所有可能出現(xiàn)錯(cuò)誤的類型,或者需要通過一段代碼作為陷阱拋出所有可能出現(xiàn)的錯(cuò)誤。為簡單地忽略陷阱聲明中的錯(cuò)誤類型,錯(cuò)誤處理中的代碼通過$_變量及其屬性區(qū)分不同錯(cuò)誤。將下面的代碼保存為“Number-GenericTrap.ps1”腳本文件,其中通過輸出FullyQualifiedErrorId、Exception和Exception.InnerException屬性查找和排除錯(cuò)誤:
trap { $exceptionType = $_.Exception.GetType() $innerExceptionType = "No inner exception" if ($_.Exception.InnerException) { $innerExceptionType = $_.Exception.InnerException.GetType() } Write-Host "FullyQualifiedErrorId: $($_.FullyQualifiedErrorId)" Write-Host "Exception: $exceptionType" Write-Host "InnerException: $innerExceptionType" continue } Write-Host "script begin" [int] "not a number" $denominator = 0 $result = 50 / $denominator Write-Host "script end"
執(zhí)行結(jié)果如圖11所示。
能夠看到先后捕獲了PSInvalidCastException和DivideByZeroException錯(cuò)誤,與之前一個(gè)版本中捕獲異常的類型一致。而FullQualifiedErrorId屬性的兩個(gè)錯(cuò)誤結(jié)果均為通用的RuntimeException。為了區(qū)分前后兩個(gè)錯(cuò)誤類型,需要查看Excepiton和Exception.InnerException對象。需要強(qiáng)調(diào)的是Exception.InnerException屬性不一定包含真實(shí)對象,如同前面的DivedByZeroException屬性未包含相應(yīng)對象。
使用通用陷阱可以捕獲所有的錯(cuò)誤記錄對象,并獲取對象Exception屬性中包含的內(nèi)容,然后即可移除通用陷阱并使用特定的異常類型創(chuàng)建特定的錯(cuò)誤陷阱來處理。
圖11 執(zhí)行結(jié)果
其他編程語言提供了可以在特定代碼塊中為特定錯(cuò)誤指定陷阱以捕獲異常的結(jié)構(gòu),在PowerShell中的錯(cuò)誤陷阱通常為當(dāng)前執(zhí)行上下文注冊。如果需要為小段代碼指定特定陷阱,則需要將陷阱的代碼轉(zhuǎn)移到單獨(dú)的腳本塊和函數(shù)中,這樣即可創(chuàng)建嵌套陷阱。在小段代碼中關(guān)注特定錯(cuò)誤的陷阱可以轉(zhuǎn)移到函數(shù)體和腳本塊中,這樣可以將更多的通用處理放置在外面。如果錯(cuò)誤在函數(shù)中發(fā)生,Shell會(huì)在其中查找已注冊的陷阱。如果沒有找到,則在其父作用域中查找。以此類推,直到腳本的全局作用域,從而使得陷阱有了類似變量在腳本中存在的多層作用域的性質(zhì)。為了演示這一點(diǎn),在腳本文件Nested-Traps.ps1中定義一個(gè)函數(shù)Generate-DiviedByZero,其中會(huì)拋出一個(gè)System.DivideByZeroException錯(cuò)誤,并且包含一個(gè)錯(cuò)誤陷阱用于向控制臺(tái)輸出特定的錯(cuò)誤信息;另外在函數(shù)外腳本會(huì)生成一個(gè)字符串轉(zhuǎn)換為數(shù)字的錯(cuò)誤,并在全局錯(cuò)誤陷阱中被捕獲,下面是該腳本的代碼:
trap { $error = $_.Exception.GetType() Write-Host "Caught $error outside function" continue } function Generate-DivideByZero { trap { $error = $_.Exception.GetType() Write-Host "Caught $error inside function" continue } $denominator = 0 $result = 50 / $denominator } Write-Host "script begin" Generate-DivideByZero [int] "not a number" Write-Host "script end"
代碼執(zhí)行時(shí)會(huì)捕獲兩個(gè)錯(cuò)誤,通過控制臺(tái)輸出可以看到兩個(gè)異常分別在函數(shù)體及其父作用域中被捕獲,如圖12所示。
圖12 分作用域設(shè)置陷阱捕獲錯(cuò)誤
如果需要,低級別的陷阱能夠?qū)㈠e(cuò)誤傳遞到高作用域中。這是個(gè)非常有用的特性,允許用戶創(chuàng)建錯(cuò)誤處理鏈。以實(shí)現(xiàn)在子作用域中響應(yīng)錯(cuò)誤,而將錯(cuò)誤通過處理鏈傳遞到上級作用域處理。為此,需要將continue語句替換為break語句。下面創(chuàng)建名為“Nested-TrapsToUpLevel.ps1”的腳本,代碼如下:
trap { $error = $_.Exception.GetType() Write-Host "Caught $error outside function" continue } function Generate-DivideByZero { trap { $error = $_.Exception.GetType() Write-Host "Caught $error inside function" break } $denominator = 0 $result = 50 / $denominator } Write-Host "script begin" Generate-DivideByZero [int] "not a number" Write-Host "script end"
執(zhí)行結(jié)果如圖13所示,從控制臺(tái)的輸出可以看到在函數(shù)體內(nèi)的陷阱和在函數(shù)體外的全局函數(shù)區(qū)中都處理了函數(shù)中的錯(cuò)誤System.DivideByZeroException。
最后討論如何將腳本發(fā)布給用戶使用時(shí)為腳本作者有效的報(bào)告出現(xiàn)的錯(cuò)誤,相對較好的方法是在出現(xiàn)錯(cuò)誤時(shí)將出現(xiàn)錯(cuò)誤的環(huán)境信息輸出為文件,并由用戶發(fā)送給作者盡快修復(fù)。可以通過注冊全局錯(cuò)誤陷阱獲取錯(cuò)誤的相關(guān)信息并輸出到指定的位置,用戶只需要發(fā)送一份郵件即可將出現(xiàn)錯(cuò)誤的堆棧信息反饋給作者。為了演示,創(chuàng)建名為“Log-AllErrors.ps1”的腳本用于嘗試刪除某個(gè)文件。如果文件不存在,則會(huì)出現(xiàn)終止錯(cuò)誤。這時(shí)將會(huì)生成錯(cuò)誤診斷日志,并由用戶發(fā)送診斷文件給腳本作者。代碼如下:
trap { $dumpFile = "error-dump.xml" $message = @" A critical error occurred! The error has been dumped to $dumpFile. Please include that file in all error reports you send to scriptauthor@test.com "@ Write-Host $message -ForegroundColor Red $_ | Export-CliXml $dumpFile break } del veryimportantfile.txt -ErrorAction Stop
該腳本中使用了Export-CliXml cmdlet序列化ErrorRecord對象并將其寫到XML文件中,這樣即可抓取并發(fā)送所有相關(guān)環(huán)境信息。由腳本作者解析并恢復(fù)錯(cuò)誤現(xiàn)場,然后分析腳本錯(cuò)誤。腳本的執(zhí)行結(jié)果如圖14所示。
生成的錯(cuò)誤日志文件error-dump.xml的格式如下所示:
System.Management.Automation.ErrorRecord System.Object ……
圖14 執(zhí)行結(jié)果
當(dāng)用戶將上面生成的腳本錯(cuò)誤日志文件error-dump.xml根據(jù)提示的郵件地址發(fā)送給腳本作者后,腳本作者可能還需要一個(gè)錯(cuò)誤日志解析程序閱讀。下面創(chuàng)建名為“Analy-AllErrorsLog.ps1”的腳本用于解析上述的日志文件,代碼如下:
$e = Import-Clixml error-dump.xml $e.FullyQualifiedErrorId $e.InvocationInfo
執(zhí)行上述腳本的結(jié)果如圖15所示,其中解析出主要的出錯(cuò)信息,包括腳本名、運(yùn)行的路徑及引起錯(cuò)誤的指定行。
圖15 執(zhí)行結(jié)果
(2)不能捕獲的錯(cuò)誤
大多數(shù)情況下,語法和解析錯(cuò)誤算是比較嚴(yán)重的錯(cuò)誤。必須要修正,而不能屏蔽或者忽略,這種異常不可能通過腳本內(nèi)的陷阱捕獲。PowerShell不能捕獲語法和解析錯(cuò)誤,大多數(shù)情況下解析錯(cuò)誤很容易被發(fā)現(xiàn),由非法腳本塊,如不可識(shí)別的語句、錯(cuò)亂的分支、括號(hào)或者引號(hào)引起。PowerShell在解析時(shí)優(yōu)化(parse-time optimizations)嘗試執(zhí)行類似4+2一類的表達(dá)式,對于通過陷阱捕獲任何在表達(dá)式執(zhí)行時(shí)出現(xiàn)的錯(cuò)誤是個(gè)問題。下面創(chuàng)建腳本DivideByZero.ps1,在其中嘗試捕獲DivideByZeroException,代碼如下:
trap { Write-Host "Caught $($_.Exception.GetType())" } Write-Host "Script start" 3 / 0
執(zhí)行這個(gè)腳本返回標(biāo)準(zhǔn)的錯(cuò)誤信息,而不是在腳本陷阱中的提示信息,如圖16所示。
圖16 返回標(biāo)準(zhǔn)的錯(cuò)誤信息
PowerShell中的腳本在執(zhí)行之前會(huì)被讀入做預(yù)解析,文字表達(dá)式會(huì)被事先處理。相關(guān)的對象會(huì)被初始化,并且陷阱也會(huì)被解析準(zhǔn)備在后續(xù)執(zhí)行時(shí)隨時(shí)捕獲異常,然后按照腳本的先后順序執(zhí)行。由于這里文字表達(dá)式是數(shù)字型并在預(yù)解析時(shí)發(fā)現(xiàn)零除錯(cuò)誤,Shell報(bào)錯(cuò),因而控制臺(tái)的輸出語句尚未執(zhí)行,腳本已經(jīng)由于語法錯(cuò)誤而停止執(zhí)行。為了使陷阱起作用,只需要排除文字表達(dá)式在解析時(shí)出現(xiàn)的錯(cuò)誤。可以將0賦值給某個(gè)變量并除一個(gè)數(shù)字,盡管也是零除,但是避免了在解析時(shí)被終止。將修改后的代碼命名為“DivideByZeroTraps.ps1”,代碼如下:
trap { Write-Host "Caught $($_.Exception.GetType())" continue } Write-Host "Script start" $zero = 0 3 / $zero
腳本執(zhí)行結(jié)果如圖17所示。
圖17 執(zhí)行結(jié)果
正如所看到的,陷阱代碼被執(zhí)行,而且執(zhí)行了發(fā)生錯(cuò)誤之前的輸出語句。如果遇到這種問題,并且不能確定是否遇到解析時(shí)錯(cuò)誤,則在腳本開始放置簡單的Write-Host輸出語句。如果輸出語句尚未執(zhí)行報(bào)錯(cuò),而且設(shè)置的陷阱未起作用,則為解析時(shí)錯(cuò)誤,返回腳本查代碼。
3 捕捉非終止性錯(cuò)誤
非終止性錯(cuò)誤不會(huì)終止腳本執(zhí)行,并且不能被錯(cuò)誤陷阱捕獲。為了能夠保存由非終止性錯(cuò)誤生成的ErrorRecord,可以使用ErrorVariable參數(shù)存儲(chǔ)錯(cuò)誤信息用于后續(xù)的訪問。或者使用錯(cuò)誤管道重定向;蛘邫z查全局的$Error變量。使用ErrorVariable需要傳遞額外的參數(shù)給所有cmdlet,并且不能處理其他代碼拋出的非終止性錯(cuò)誤。
錯(cuò)誤重定向類似輸出重定向,它使用2>重定向操作符。圖18所示為要?jiǎng)h除一個(gè)不存在的文件,并將錯(cuò)誤信息輸出到記錄文件中。
圖18 輸出錯(cuò)誤到記錄文件中
如果將錯(cuò)誤重定向到$null變量中,則不需要在硬盤中創(chuàng)建文件,如圖19所示。
圖19 將錯(cuò)誤重定向到$null中
為了方便,通常會(huì)將標(biāo)準(zhǔn)輸出寫入stdout流中,其ID為1;將所有錯(cuò)誤輸出寫入stderr流中,其ID為2。這些定義適用于cmd.exe和linux bash,這樣2>操作符重定向輸出到stderr。即錯(cuò)誤不會(huì)輸出到屏幕,而是寫到文件中。如果指定$null,則不輸出到屏幕,也不寫入到特定文件中
使用全局$Error變量是獲取非終止性錯(cuò)誤的唯一可信賴方式,PowerShell包含一個(gè)$MaximumErrorCount變量用來限制保存錯(cuò)誤記錄最大的條數(shù),默認(rèn)值為256。$Error集合將最后一個(gè)錯(cuò)誤放在最前面,即$Error[0]包含最后一條錯(cuò)誤,上一個(gè)錯(cuò)誤包含在$Error[1]中,可以將這個(gè)集合看做是一個(gè)隊(duì)列。上例中的輸出可以通過如圖20所示的方法讀取錯(cuò)誤信息。
圖20 通過全局變量$Error索引值讀取錯(cuò)誤記錄
可以使用$Error集合創(chuàng)建日志工具來追蹤任何兩個(gè)執(zhí)行點(diǎn)之間錯(cuò)誤,為此需要通過獲取該集合中包含的錯(cuò)誤數(shù)得到基準(zhǔn)點(diǎn),并在執(zhí)行一些操作后處理錯(cuò)誤。下面創(chuàng)建一個(gè)名為“Log-NonTerminatingErrors.ps1”的腳本,其中包含兩個(gè)錯(cuò)誤。并使用不同的技術(shù)終止這兩個(gè)錯(cuò)誤,代碼如下:
trap { continue } Write-Host "setting error baseline" $initialErrorCount = $error.count $denominator = 0 4/$denominator del nosuchfile.txt 2> $null $errorCount = $error.count - $initialErrorCount Write-Host "Errors since baseline" for ($i = $errorCount - 1; $i -ge 0; $i--) { $error[$i] }
零除錯(cuò)誤是個(gè)常見的終止性錯(cuò)誤,在代碼中用陷阱捕獲它并繼續(xù)執(zhí)行下面的代碼。刪除操作失敗,但是其錯(cuò)誤管道信息被重定向到$null。錯(cuò)誤報(bào)告機(jī)制獲取初始化的錯(cuò)誤數(shù)量,最后獲取當(dāng)前錯(cuò)誤數(shù)量。并根據(jù)錯(cuò)誤發(fā)生的先后順序輸出錯(cuò)誤信息,如圖21所示。
圖21 通過全局變量$Error獲取腳本執(zhí)行的錯(cuò)誤記錄
4 拋出錯(cuò)誤
很多情況下腳本由程序來調(diào)用,因?yàn)樾枰沙绦虺跏蓟恍l件,用戶直接調(diào)用并不能提供這些條件。不正常的條件包括錯(cuò)誤的輸入和不具有的執(zhí)行權(quán)限等。對于良好的用戶體驗(yàn)來說需要檢測存在問題的條件,拋出異常提示并調(diào)用代碼處理這些異常。而調(diào)用程序本身需要提示用戶是否處理錯(cuò)誤或繼續(xù)執(zhí)行,或改變輸入數(shù)據(jù),或直接終止程序的執(zhí)行。
(1)拋出終止錯(cuò)誤
終止錯(cuò)誤是由于輸入異;虿痪邆鋱(zhí)行環(huán)境條件而產(chǎn)生的不能繼續(xù)執(zhí)行的錯(cuò)誤,如腳本用于處理特定格式的文本文件,但用戶提供的是二進(jìn)制的.exe可執(zhí)行文件。這時(shí)腳本無法繼續(xù)執(zhí)行,所以需要使用throw語句拋出異常。
為了演示如何拋出終止錯(cuò)誤,這里創(chuàng)建一個(gè)名為“Function-Parameters.ps1”的腳本文件,用于定義需要一個(gè)文件名作為參數(shù)的Find-TextFile函數(shù)。如果提供的文件名后綴不是.txt,則這個(gè)函數(shù)將會(huì)拋出異常,代碼如下:
function Find-TextFile($name) { if ($name -notlike "*.txt") { throw "Find-TextFile: Expecting *.txt files only" } return Get-Item $name } Find-TextFile "Function-Parameters.ps1"
可以看到函數(shù)會(huì)檢查傳遞的文件名后綴是否為.txt,如果不是,則拋出終止錯(cuò)誤。在腳本中在調(diào)用函數(shù)時(shí)使用了一個(gè)非法的文件名,執(zhí)行結(jié)果如圖22所示。
圖22 執(zhí)行結(jié)果
改寫上面的代碼,保存為“Function-ParametersWithTraps.ps1”腳本文件,使用陷阱來捕獲其中的終止錯(cuò)誤,代碼如下:
trap { Write-Host "$($_.Exception.GetType())" continue } function Find-TextFile($name) { if ($name -notlike "*.txt") { throw "Find-TextFile: Expecting *.txt files only" } return Get-Item $name } Find-TextFile "Function-Parameters.ps1"
腳本執(zhí)行結(jié)果如圖23所示。
圖23 執(zhí)行結(jié)果
因?yàn)楹饬繕?biāo)準(zhǔn)不一樣,所以區(qū)分不同類型的錯(cuò)誤成為一個(gè)問題。最好的方法是創(chuàng)建一個(gè)錯(cuò)誤對象,通過拋出它來區(qū)分不同的錯(cuò)誤。重寫上面的代碼,創(chuàng)建名為“Function-ParametersWithExceObj.ps1”的腳本文件,代碼如下:
trap [System.ArgumentException] { Write-Host "$($_.Exception.Message)" continue } function Find-TextFile($name) { if ($name -notlike "*.txt") { $message = "Find-TextFile: Expecting *.txt files only" throw (New-Object System.ArgumentException -arg $message) } return Get-Item $name } Find-TextFile "Function-Parameters.ps1"
腳本執(zhí)行結(jié)果如圖24所示。
圖24 執(zhí)行結(jié)果
需要強(qiáng)調(diào)的是在上面的代碼中使用New-Object cmdlet創(chuàng)建了.NET的System.ArgumentException對象,并傳遞錯(cuò)誤信息作為結(jié)構(gòu)體的參數(shù),這樣允許編寫更多特定的錯(cuò)誤陷阱來捕獲特定的異常。
之前的文章中演示如何在表達(dá)式中使用throw語句,這是通過為函數(shù)參數(shù)指定默認(rèn)值的形式實(shí)現(xiàn)的。如果值沒有提供的話,則拋出相應(yīng)的錯(cuò)誤。這種對工具參數(shù)的巧妙改寫能夠更廣泛地?cái)U(kuò)展標(biāo)準(zhǔn)的.NET異常對象。為了在上例基礎(chǔ)上繼續(xù)演示,創(chuàng)建名為“Function-ParametersWithExceObjExpression.ps1”的腳本文件。在其中添加判斷$name參數(shù)是否為$null的語句,代碼如下:
trap [System.ArgumentException] { Write-Host "$($_.Exception.Message)" continue } function Find-TextFile($name = ` $(throw New-Object System.ArgumentNullException -arg "name")) { if ($name -notlike "*.txt") { $message = "Find-TextFile: Expecting *.txt files only" throw (New-Object System.ArgumentException -arg $message) } return Get-Item $name } Find-TextFile "Function-Parameters.ps1" Find-TextFile
在上面函數(shù)的定義中,通過指定參數(shù)默認(rèn)值表達(dá)式為$name參數(shù)創(chuàng)建新的System.ArgumetNullException對象。如果$name為空,則拋出這個(gè)錯(cuò)誤。在.NET中ArgumentNullException類繼承于ArgumentException類型,即所有的ArgumentNullException對象也都是ArgumentException對象,并且為ArgumentException創(chuàng)建的陷阱也會(huì)捕獲相關(guān)衍生類型的錯(cuò)誤。腳本執(zhí)行結(jié)果如圖25所示。
圖25 執(zhí)行結(jié)果
需要強(qiáng)調(diào)的是為不同調(diào)用獲取了兩個(gè)不同的錯(cuò)誤,ArgumentNullException異常適用于格式化參數(shù)缺失的錯(cuò)誤信息。這樣可提供比較好的用戶體驗(yàn),甚至能夠?yàn)榉怯⒄ZWindows系統(tǒng)提供區(qū)域化的錯(cuò)誤信息。
(2)拋出非終止錯(cuò)誤
并不是所有遇到的錯(cuò)誤對于腳本的執(zhí)行都是致命的。有時(shí)還會(huì)出現(xiàn)存在一定警告的情況下繼續(xù)執(zhí)行腳本業(yè)務(wù)邏輯的情況。也許這些警告信息只是為了幫助客戶端代碼更加的智能,或?yàn)橛脩籼峁⿴椭。Write-Error cmdlet這個(gè)cmdlet可攜帶信息字符串、異;蛘逧rrorRecord對象并將其傳遞到錯(cuò)誤管道中。為了演示其功能,創(chuàng)建名為“Function-ParametersNonTerminating.ps1”的腳本文件,在其中創(chuàng)建名為“Find-TextFile”的函數(shù)用于返回指定路徑的文件。如果文件不存在,則生成非終止性錯(cuò)誤。代碼如下:
function Find-TextFile($name) { if (!(Test-Path $name)) { Write-Error -Message "$name does not exist" return $null } else { return Get-Item $name } } Write-Host "Script start" Find-TextFile "nosuchfile.txt" Write-Host "Script end"
由于在腳本前后分別添加了調(diào)試用輸出,所以在報(bào)錯(cuò)后繼續(xù)執(zhí)行控制臺(tái)輸出語句,腳本執(zhí)行結(jié)果如圖26所示。
圖26 執(zhí)行結(jié)果
也可以通過將Write-Error輸出的錯(cuò)誤管道對象重定向到$null中關(guān)閉錯(cuò)誤提示,如圖27所示。
圖27 關(guān)閉錯(cuò)誤提示
5 總 結(jié)
錯(cuò)誤處理和腳本調(diào)試的主題相互錯(cuò)綜復(fù)雜地關(guān)聯(lián)在一起,加強(qiáng)腳本的錯(cuò)誤處理邏輯后可以實(shí)現(xiàn)代碼的自診斷。這樣即可減少腳本的調(diào)試工作,快速而有效地達(dá)到目標(biāo)需求。同樣也可以通過錯(cuò)誤處理的相關(guān)技術(shù),如錯(cuò)誤陷阱輸出錯(cuò)誤信息來快速調(diào)試程序。
為代碼添加好的錯(cuò)誤處理處理機(jī)制是開發(fā)人員應(yīng)該注意的問題,這樣能夠大大縮短調(diào)試的時(shí)間,而且任何使用代碼的人都可以從中受益。本文介紹了PowerShell如何依靠全局錯(cuò)誤處理檢測捕捉并處理執(zhí)行腳本或代碼引起的錯(cuò)誤,要注意的是其中的例子包含錯(cuò)誤處理方法和調(diào)試信息。本文還舉例說明了如何在反常條件下、錯(cuò)誤的輸入數(shù)據(jù),以及隱含的錯(cuò)誤情況下捕獲異常。
作者: 付海軍
出處:http://fuhj02.cnblogs.com
版權(quán):本文版權(quán)歸作者和博客園共有
轉(zhuǎn)載:歡迎轉(zhuǎn)載,為了保存作者的創(chuàng)作熱情,請按要求【轉(zhuǎn)載】,謝謝