西西軟件園多重安全檢測下載網(wǎng)站、值得信賴的軟件下載站!
軟件
軟件
文章
搜索

首頁編程開發(fā)其它知識(shí) → PowerShell全局錯(cuò)誤處理機(jī)制處理腳本錯(cuò)誤或異常

PowerShell全局錯(cuò)誤處理機(jī)制處理腳本錯(cuò)誤或異常

相關(guān)軟件相關(guān)文章發(fā)表評論 來源:本站整理時(shí)間:2010/11/29 8:24:12字體大小:A-A+

作者:佚名點(diǎn)擊:448次評論:0次標(biāo)簽: PowerShell 異常

  • 類型:音頻處理大。1M語言:中文 評分:5.1
  • 標(biāo)簽:
立即下載

沒有任何腳本或程序能夠保證在任何情況下毫無錯(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)載】,謝謝

    相關(guān)評論

    閱讀本文后您有什么感想? 已有人給出評價(jià)!

    • 8 喜歡喜歡
    • 3 頂
    • 1 難過難過
    • 5 囧
    • 3 圍觀圍觀
    • 2 無聊無聊

    熱門評論

    最新評論

    發(fā)表評論 查看所有評論(0)

    昵稱:
    表情: 高興 可 汗 我不要 害羞 好 下下下 送花 屎 親親
    字?jǐn)?shù): 0/500 (您的評論需要經(jīng)過審核才能顯示)