System.IO.FileInfo和相对路径

7

我想知道是否有人能帮助我理解为什么在处理相对路径时,System.IO.FileInfo 在 Windows 和 Linux 上的行为不同。

示例

  • 在 Linux 上
PS /home/user/Documents> ([System.IO.FileInfo]'./test.txt').FullName
/home/user/Documents/test.txt
  • 在Windows上
PS C:\Users\User\Documents> ([System.IO.FileInfo]'.\test.txt').FullName
C:\Users\User\test.txt

编辑

为了澄清上述内容,System.IO.FileInfo 在 Windows 或 Linux 上处理相对路径没有区别。问题与 [System.IO.Directory]::GetCurrentDirectory() 未被 Push-LocationSet-Location 更新有关。

以下是一个简单的示例:

PS /home/user> [System.IO.Directory]::GetCurrentDirectory()
/home/user
PS /home/user> cd ./Documents/
PS /home/user/Documents> [System.IO.Directory]::GetCurrentDirectory()
/home/user

假设这是一种预期的行为,那么在脚本和函数中处理param(...)块以接受绝对路径和相对路径的最佳方法是什么?我曾经将路径参数类型约束为System.IO.FileInfo,但现在我可以看到这显然是错误的。

这就是我遇到的问题,但我想知道是否有更好的方法。
我相信如果使用网络路径,Split-Path -IsAbsolute也会带来问题,请纠正我如果我错了。

param(
    [ValidateScript({ 
        if(Test-Path $_ -PathType Leaf) {
            return $true
        }
        throw 'Invalid File Path'
    })]
    [string] $Path
)

if(-not (Split-Path $Path -IsAbsolute)) {
    [string] $Path = Resolve-Path $Path
}

1
你可以尝试使用 [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($pwd, '.\test.txt')) - Theo
@Theo,这很棒,在两个操作系统上都可以正常工作。你会在执行之前询问 [IO.Path]::IsRooted(..) 吗?还是不管怎样都会执行?我猜我找不到关于 FileInfo 在 Windows 和 Linux 上的行为的答案,所以你可以将其作为一个答案提出来,如果今天结束时没有人能回答另一个问题,我会接受它。谢谢 Theo。 - Santiago Squarzon
1
这个问题之前已经讨论过了。例如:https://dev59.com/w2gu5IYBdhLWcg3wj3r5 - js2010
@ js2010 谢谢! - Santiago Squarzon
3个回答

2

感觉有些重复,但既然你问了...

很抱歉我不懂Linux,但在Windows中:

你可以先加一个测试来判断路径是否是相对路径,如果是,则进行转换为绝对路径,像这样:

$Path = '.\test.txt'
if (![System.IO.Path]::IsPathRooted($Path) -or $Path -match '^\\[^\\]+') {
    $Path =  [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($pwd, $Path))
}

我添加了$Path -match '^\\[^\\]+',以便转换以反斜杠开头的相对路径,例如\ReadWays.ps1,表示路径从根目录开始。以两个反斜杠开头的UNC路径被视为绝对路径。


显然(我真的不知道为什么..)上面的方法在Linux上不起作用,因为在那里,当使用UNC路径时,![System.IO.Path]::IsPathRooted('\\server\folder')部分会产生True

然后似乎需要先检查操作系统,然后在Linux上以不同的方式进行检查。

$Path = '\\server\share'

if ($IsWindows) {  # $IsWindows exists in version 7.x. Older versions do `$env:OS -match 'Windows'`
    if (![System.IO.Path]::IsPathRooted($Path) -or $Path -match '^\\[^\\]+') {
        $Path =  [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($pwd, $Path))
    }
}
else {
    if ($Path -notlike '\\*\*') {  # exclude UNC paths as they are not relative
        if (![System.IO.Path]::IsPathRooted($Path) -or $Path -match '^\\[^\\]+') {
            $Path =  [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($pwd, $Path))
        }
    }
}

2

另一个选项:

由于您需要将结果转换为[System.IO.FileInfo],您可以改用Get-Item,这样也会返回一个[System.IO.FileInfo]对象,并解析相对路径 以预期的方式。它还包含一些错误检测(无效字符或不存在的路径等)。

示例:

PS C:\Users\User\Documents> (Get-Item -LiteralPath '.\test.txt').FullName
C:\Users\User\Documents\test.txt

1

最简单的替代方案是使用Convert-Path来:

  • 处理UNC、相对路径、绝对路径和根路径。
  • 与Windows和Linux兼容。
  • 高效。

另一个不错的选择是如果我们使用[cmdletbinding()],可以使用$PSCmdlet.GetUnresolvedProviderPathFromPSPath(..)方法

function Test-ResolvePath {
    [cmdletbinding()]
    param($path)
    $PSCmdlet.GetUnresolvedProviderPathFromPSPath($path)
}

Test-ResolvePath \\server01\test         # => \\server01\test
Test-ResolvePath C:\Users\user\Documents # => C:\Users\user\Documents
Test-ResolvePath C:Documents             # => C:\Documents
(Test-ResolvePath .) -eq $PWD.Path       # => True
(Test-ResolvePath ~) -eq $HOME           # => True

如果我们想要模拟大多数Microsoft.PowerShell.Management cmdlet的行为,具有-LiteralPath-Path的代码会更加复杂,并且调用的API也不同,对于字面路径验证使用PathIntrinsics.GetUnresolvedProviderPathFromPSPath方法,对于通配符路径验证使用PSCmdlet.GetResolvedProviderPathFromPSPath方法。这两种方法都确保目标存在。
function Test-ResolvePath {
    [cmdletbinding(DefaultParameterSetName = 'Path')]
    param(
        [Parameter(ParameterSetName = 'LiteralPath', Mandatory, Position = 0)]
        [string] $LiteralPath,

        [Parameter(ParameterSetName = 'Path', Mandatory, Position = 0)]
        [SupportsWildcards()]
        [string] $Path
    )

    $provider = $null

    try {
        if ($PSCmdlet.ParameterSetName -eq 'LiteralPath') {
            $resolvedPath = $PSCmdlet.SessionState.Path.GetUnresolvedProviderPathFromPSPath(
                $LiteralPath,
                [ref] $provider,
                [ref] $null)
        }
        else {
            $resolvedPath = $PSCmdlet.GetResolvedProviderPathFromPSPath(
                $Path,
                [ref] $provider)
        }

        foreach ($path in $resolvedPath) {
            [pscustomobject]@{
                Path             = $path
                Provider         = $provider
                IsFileSystemPath = $provider.ImplementingType -eq [Microsoft.PowerShell.Commands.FileSystemProvider]
            }
        }
    }
    catch {
        $PSCmdlet.WriteError($_)
    }
}

Test-ResolvePath $pwd\*
Test-ResolvePath HKLM:
Test-ResolvePath -LiteralPath Cert:
Test-ResolvePath doesnotexist

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接