วิธีที่ดีกว่าในการตรวจสอบว่ามีเส้นทางอยู่หรือไม่ใน PowerShell


124

ฉันไม่ชอบไวยากรณ์ของ:

if (Test-Path $path) { ... }

และ

if (-not (Test-Path $path)) { ... }
if (!(Test-Path $path)) { ... }

โดยเฉพาะอย่างยิ่งมีวงเล็บมากเกินไปและอ่านไม่ได้มากเมื่อตรวจสอบว่า "ไม่มีอยู่จริง" สำหรับการใช้งานทั่วไป วิธีที่ดีกว่านี้คืออะไร?

ปรับปรุง:วิธีการแก้ปัญหาปัจจุบันของฉันคือการใช้นามแฝงสำหรับexistและnot-existตามที่อธิบายไว้ที่นี่

ปัญหาที่เกี่ยวข้องในที่เก็บ PowerShell: https://github.com/PowerShell/PowerShell/issues/1970


2
คุณสามารถใช้ได้try{ Test-Path -EA Stop $path; #stuff to do if found } catch { # stuff to do if not found }
Eris

1
ปัญหาที่เกี่ยวข้อง: github.com/PowerShell/PowerShell/issues/1970
orad

คำตอบ:


131

หากคุณต้องการทางเลือกอื่นสำหรับไวยากรณ์ cmdlet โดยเฉพาะสำหรับไฟล์ให้ใช้File.Exists()เมธอด. NET:

if(![System.IO.File]::Exists($path)){
    # file with path $path doesn't exist
}

ในทางกลับกันหากคุณต้องการนามแฝงเชิงลบสำหรับวัตถุประสงค์ทั่วไปTest-Pathนี่คือวิธีที่คุณควรทำ:

# Gather command meta data from the original Cmdlet (in this case, Test-Path)
$TestPathCmd = Get-Command Test-Path
$TestPathCmdMetaData = New-Object System.Management.Automation.CommandMetadata $TestPathCmd

# Use the static ProxyCommand.GetParamBlock method to copy 
# Test-Path's param block and CmdletBinding attribute
$Binding = [System.Management.Automation.ProxyCommand]::GetCmdletBindingAttribute($TestPathCmdMetaData)
$Params  = [System.Management.Automation.ProxyCommand]::GetParamBlock($TestPathCmdMetaData)

# Create wrapper for the command that proxies the parameters to Test-Path 
# using @PSBoundParameters, and negates any output with -not
$WrappedCommand = { 
    try { -not (Test-Path @PSBoundParameters) } catch { throw $_ }
}

# define your new function using the details above
$Function:notexists = '{0}param({1}) {2}' -f $Binding,$Params,$WrappedCommand

notexistsตอนนี้จะทำงานตรงเหมือนTest-Pathแต่มักจะกลับมาผลตรงข้าม:

PS C:\> Test-Path -Path "C:\Windows"
True
PS C:\> notexists -Path "C:\Windows"
False
PS C:\> notexists "C:\Windows" # positional parameter binding exactly like Test-Path
False

ในขณะที่คุณได้แสดงให้เห็นแล้วว่าตัวเองตรงข้ามเป็นเรื่องง่ายมากเพียงนามแฝงexistsเพื่อTest-Path:

PS C:\> New-Alias exists Test-Path
PS C:\> exists -Path "C:\Windows"
True

1
หาก$pathเป็น "พิเศษ" เช่นเดียวกับผู้ให้บริการ Powershell (คิดว่า HKLM: \ SOFTWARE \ ... ) สิ่งนี้จะล้มเหลวอย่างน่าสังเวช
Eris

4
@Eris ถามโดยเฉพาะเพื่อตรวจสอบว่ามีไฟล์อยู่หรือไม่
Mathias R.Sessen

1
แน่นอนและการสร้าง cmdlet ใหม่ในทันทีนั้นเรียบร้อย เกือบจะไม่สามารถเข้าถึงได้เหมือนนามแฝง แต่ก็ยังคงเรียบร้อยจริงๆ :)
Eris

ดี! ฉันคิดว่า PS ควรเพิ่มการรองรับเนทีฟสำหรับสิ่งนี้
orad

4
@orad ฉันสงสัยอย่างจริงจังว่าคุณจะให้พวกเขาทำอย่างนั้น "เกินไปวงเล็บหลายคน" เป็นอย่างมากเหตุผลอัตนัยและไม่ได้บุญจริงๆเบี่ยงเบนไปจากการออกแบบภาษา / สเปค FWIW ฉันยังเห็นด้วยกับโครงสร้าง if / else ที่ @briantist เสนอว่าเป็นทางเลือกที่ดีกว่าหากคุณเกลียดวงเล็บมากขนาดนั้น:if(Test-Path $path){}else{ # do your thing }
Mathias R. Jessen

38

โซลูชันนามแฝงที่คุณโพสต์นั้นฉลาด แต่ฉันจะโต้แย้งกับการใช้งานในสคริปต์ด้วยเหตุผลเดียวกับที่ฉันไม่ชอบใช้นามแฝงใด ๆ ในสคริปต์ มีแนวโน้มที่จะเป็นอันตรายต่อการอ่าน

หากนี่คือสิ่งที่คุณต้องการเพิ่มในโปรไฟล์ของคุณเพื่อให้คุณสามารถพิมพ์คำสั่งด่วนหรือใช้เป็นเชลล์ฉันก็เห็นว่ามันสมเหตุสมผล

คุณอาจพิจารณาวางท่อแทน:

if ($path | Test-Path) { ... }
if (-not ($path | Test-Path)) { ... }
if (!($path | Test-Path)) { ... }

อีกวิธีหนึ่งสำหรับวิธีการเชิงลบหากเหมาะสมกับรหัสของคุณคุณสามารถตรวจสอบเชิงบวกจากนั้นใช้elseสำหรับค่าลบ:

if (Test-Path $path) {
    throw "File already exists."
} else {
   # The thing you really wanted to do.
}

1
ฉันเหมือนท่อที่นี่ Falseแต่การตรวจสอบเสนอของคุณสำหรับเชิงลบไม่ถูกต้องโดยไม่ต้องวงเล็บหรือมันมักจะประเมิน คุณต้องทำเช่นif (-not ($path | Test-Path)) { ... }.
orad

1
@ คุณถูกต้อง! จริงๆแล้วนั่นเป็นผลเสียของการวางท่อในกรณีนั้น ฉันถูกกล่อมให้เข้าสู่ความรู้สึกปลอดภัยที่ผิดพลาดโดยที่มันไม่ทิ้งข้อยกเว้นเมื่อมันล้มเหลว เรียกวิธีเดิมว่าเป็นข้อยกเว้นทำให้จับปัญหาได้ง่ายขึ้น
briantist

10

เพิ่มนามแฝงต่อไปนี้ ฉันคิดว่าสิ่งเหล่านี้ควรจะพร้อมใช้งานใน PowerShell โดยค่าเริ่มต้น:

function not-exist { -not (Test-Path $args) }
Set-Alias !exist not-exist -Option "Constant, AllScope"
Set-Alias exist Test-Path -Option "Constant, AllScope"

ด้วยเหตุนี้คำสั่งเงื่อนไขจะเปลี่ยนเป็น:

if (exist $path) { ... }

และ

if (not-exist $path)) { ... }
if (!exist $path)) { ... }

4
หากคุณต้องการให้ทีม PowerShell เพิ่มนามแฝง "มีอยู่" คุณควรส่งคำขอคุณลักษณะผ่าน Microsoft Connect
Mathias R.Sessen

1
แม้ว่าฉันจะตอบเอง แต่ฉันก็ยอมรับคำตอบของ @ mathias-r-jessen เพราะจัดการพารามิเตอร์ได้ดีกว่า
orad

2

อีกทางเลือกหนึ่งคือการใช้IO.FileInfoซึ่งให้ข้อมูลไฟล์มากมายทำให้ชีวิตง่ายขึ้นเพียงแค่ใช้ประเภทนี้:

PS > mkdir C:\Temp
PS > dir C:\Temp\
PS > [IO.FileInfo] $foo = 'C:\Temp\foo.txt'
PS > $foo.Exists
False
PS > New-TemporaryFile | Move-Item -Destination C:\Temp\foo.txt
PS > $foo.Refresh()
PS > $foo.Exists
True
PS > $foo | Select-Object *


Mode              : -a----
VersionInfo       : File:             C:\Temp\foo.txt
                    InternalName:
                    OriginalFilename:
                    FileVersion:
                    FileDescription:
                    Product:
                    ProductVersion:
                    Debug:            False
                    Patched:          False
                    PreRelease:       False
                    PrivateBuild:     False
                    SpecialBuild:     False
                    Language:

BaseName          : foo
Target            : {}
LinkType          :
Length            : 0
DirectoryName     : C:\Temp
Directory         : C:\Temp
IsReadOnly        : False
FullName          : C:\Temp\foo.txt
Extension         : .txt
Name              : foo.txt
Exists            : True
CreationTime      : 2/27/2019 8:57:33 AM
CreationTimeUtc   : 2/27/2019 1:57:33 PM
LastAccessTime    : 2/27/2019 8:57:33 AM
LastAccessTimeUtc : 2/27/2019 1:57:33 PM
LastWriteTime     : 2/27/2019 8:57:33 AM
LastWriteTimeUtc  : 2/27/2019 1:57:33 PM
Attributes        : Archive

รายละเอียดเพิ่มเติมในบล็อกของฉัน


1

หากต้องการตรวจสอบว่ามีเส้นทางไปยังไดเร็กทอรีหรือไม่ให้ใช้เส้นทางนี้:

$pathToDirectory = "c:\program files\blahblah\"
if (![System.IO.Directory]::Exists($pathToDirectory))
{
 mkdir $path1
}

หากต้องการตรวจสอบว่ามีเส้นทางไปยังไฟล์หรือไม่โดยใช้สิ่งที่@Mathiasแนะนำ:

[System.IO.File]::Exists($pathToAFile)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.