批处理文件中的Powershell-如何转义元字符?

Ed9*_*999 5 powershell batch-file parameter-passing quoting powershell-2.0

在Windows 7上运行时,将文件复制到外部磁盘时,在常规文件备份期间,我使用Powershell v2(从批处理文件运行)在复制文件上重新创建原始文件的所有时间戳。

以下代码在大多数情况下都能成功运行,但并非总是如此:-

SET file=%1
SET dest=E:\

COPY /V /Y  %file% "%dest%"

SetLocal EnableDelayedExpansion
FOR /F "usebackq delims==" %%A IN ('%file%') DO (
      SET fpath=%%~dpA
      SET fname=%%~nxA
)

PowerShell.exe (Get-Item \"%dest%\%fname%\").CreationTime=$(Get-Item \"%fpath%%fname%\" ^| Select-Object -ExpandProperty CreationTime ^| Get-Date -f \"MM-dd-yyyy HH:mm:ss\")
Run Code Online (Sandbox Code Playgroud)

当我将源文件拖放到批处理文件上时,以上代码将复制文件,然后将复制(目标)文件上的创建日期/时间设置为源文件的创建日期/时间。

但是在某些情况下,代码会失败。如果文件名包含“毒药”字符,例如(例如)方括号,则会出现错误“ 在此对象上找不到属性'CreationTime' ”。文件名的解析显然在“毒药”字符处失败。

代码中并没有给出具有符号错误,如

我已经尝试了使用单引号和双引号转义Powershell命令的各种变体,但是没有成功。请有人告诉我如何转义Powershell对象所针对的那些字符。

这只是更长的批处理例程的一小部分,我依靠它执行常规的系统备份。我没有选择切换到.ps1文件的选项,因此我需要一个在批处理文件而不是.ps1文件中工作的解决方案。

感谢您的所有建议。

附录:我通过采用mklement0提供的一个建议找到了解决方案。通过将以下命令替换为原始Powershell命令,克服了方括号的问题-

PowerShell.exe (Get-Item -LiteralPath \"%dest%\%fname%\").CreationTime=$(Get-Item -LiteralPath \"%fpath%%fname%\" ^| Select-Object -ExpandProperty CreationTime ^| Get-Date -f \"MM-dd-yyyy HH:mm:ss\")
Run Code Online (Sandbox Code Playgroud)

为了将来参考,请注意(在Windows 7上):

  1. 使用此修订的命令可以成功保留任何多余的空白字符。这是不是必要包括一对额外的双引号字符来实现这一目标。

    • 编者注:这是一个极端的情况,但值得指出:如果没有多余的双引号,则将多于一个空格的任何行都折叠成一个空格;例如,
      powershell.exe -command echo \"a b\"产量a b
      将整个命令包含在"..."原则上有帮助
      powershell.exe -command "echo \"a b\""--但此后由于cmd.exe无法将整个字符串识别为单引号引起来的字符串,因此元字符会破坏该命令;例如,
      powershell.exe -command "echo \"a & b\""
  2. 这是不是可能的文件路径包括任何“(双引号)字符,所以没有代码需要转义字符,双引号字符是在FAT和NTFS文件系统的非法字符,所以不能在遇到文件的路径。

  3. 从原则上讲,在Powershell命令中使用'(单引号)是不好的,因为该字符在NTFS文件系统中不是非法的,因此可以在文件的实际路径中找到它。必须首选使用双引号,因为在实际的NTFS路径中永远不会遇到非法的双引号字符。

  4. 使用ROBOCOPY,以下通配符解决方案即使对大多数有毒字符也成功-除以外的所有字符(即它可以应付=&`^)。即使有多个毒害字符(尽管并非万无一失),此命令也非常健壮:

    ROBOCOPY "%fpath% " "%dest%" "*%name%*%ext%*" /B /COPY:DAT /XJ /SL /R:0 /W:0 /V

    一种。“%fpath%”中的空格是必需的,这不是错误。

    b。在所有情况下唯一致命的毒药字符是感叹号(!)。

    C。毒药字符似乎只是FILENAME中的问题,而不是路径中的问题。

mkl*_*nt0 3

首先要做的事情是:

在 Windows 7 中,您可以使用robocopy.exe复制文件,默认情况下会保留时间戳(并且可以选择为您提供有关复制哪些属性的详细控制):

@echo off
:: Do NOT use setlocal ENABLEDELAYEDEXPANSION, because it would cause
:: misinterpretation of  "!" chars. in filenames.    
setlocal

:: Parse the file path given as %1 (the first argument) into its folder path and filename.
:: Be sure to pass the %1 argument *double-quoted* to prevent up-front interpretation 
:: by cmd.exe; e.g.:
::   someBatchFile "c:\tmp\foo.txt" or someBatchFile "%file%"
:: Note that %~dp1 always returns a path with a trailing "\".
set "fpath=%~dp1"
set "fname=%~nx1"

:: Determine the destination folder
set "dest=E:\"

:: Use robocopy to copy the file to the destination dir. with timestamps preserved.
:: Syntax is: <source-dir> <dest-dir> <filename-or-wildcard> ...
:: IMPORTANT: To avoid problems with paths that end in "\", always follow
::            a variable reference inside "..." with a *space* (a trick discovered by
::            Ed999 himself).
robocopy "%fpath% " "%dest% " "%fname%"
Run Code Online (Sandbox Code Playgroud)

笔记:

  • 虽然robocopy主要用于复制整个目录,但它确实允许您通过从第三个位置参数开始指定的通配符表达式来复制单个文件,如上所述"%fname%"
    鉴于robocopy- 与 PowerShell 不同 - 不考虑[通配符]字符,这种方法应该可行(如果你的文件名包含嵌入 *或字符,你只会遇到问题?,这是不可能的)。

  • 尾随空格技巧(例如"%fpath% ")是必要的,因为robocopy- 正如大多数命令行实用程序所做的那样 - 将\"参数末尾的 a 视为转义的 ",这会破坏命令。严格来说,正确的解决方法是将尾随加倍\(例如, ),但您可以通过附加空格"E:\\"来避免,因为路径中的任何尾随空格都会被忽略。因此,使此类调用稳健的一个简单方法是在传递文件夹路径时始终使用(结束双引号之前的尾随空格)。"%var% "

  • robocopy没有类似的开关/V导致它验证文件是否被正确复制,但是 - 至少根据这篇博客文章-verify on预先运行应该具有相同的效果。


如果您仍然需要通过 PowerShell 复制创建时间戳:

powershell -command "(Get-Item -LiteralPath '%dest%%fname%').CreationTime=(Get-Item -LiteralPath '%fpath%%fname%').CreationTime"
Run Code Online (Sandbox Code Playgroud)

警告:如果您的文件名有可能嵌入'字符。(单引号/撇号),您必须首先通过将它们加倍来转义它们(例如,返回所有实例加倍的值):%fname:'=''%%fname%'

powershell -command "(Get-Item -LiteralPath '%dest:'=''%%fname:'=''%').CreationTime=(Get-Item -LiteralPath '%fpath:'=''%%fname:'=''%').CreationTime"
Run Code Online (Sandbox Code Playgroud)
  • 请注意,整个命令被括在 中"...",以防止变量值中可能包含的cmd.exe元字符(例如&)破坏命令。[1]

  • 在命令字符串内部'...',用于确保变量值被 PowerShell 视为文字(例如,如果您使用"..."并且文件名包含,则结果将是意外的)。$

  • -LiteralPath确保将Get-Item文件路径解释为文字,而它是按位置
    -Path传递路径时隐含的参数,并且传递给的路径将被解释为通配符表达式,这可能会导致 PowerShell 通配符元字符(例如和 )出现问题。
    -Path[]

  • 无需先将.CreationTime源文件的属性值转换为日期+时间字符串;您可以简单地将其直接分配给目标文件的.CreationTime属性,该属性的类型为[System.DateTime].


[1] 引用令人头痛的内容

  • 将变量引用括在\转义双引号中(如问题 ( \"%dest%\%fname%\") 中所示)是不够的,因为这样做会使值受到空白规范化的影响,这意味着多个空格的运行将被规范化为单个空格。

  • 虽然原则上将命令作为一个整体包含在 help 中,then不会将整个字符串识别为单个双引号字符串,在这种情况下,变量值中的元字符可能会破坏命令;例如,工作正常,忠实地保留空白,但由于."..."cmd.exe&
    powershell.exe -command "echo \"a b\""
    powershell.exe -command "echo \"a & b\""&

    • 使用\""代替\"解决了元字符问题,但重新引入了空白规范化:
      powershell.exe -command "echo \""a & b\"""产生a & b
  • '...'因此,在整个字符串中使用PowerShell 字符串"..."是使命令健壮的最简单方法:您只需转义'变量值中的实例即可。