4

運行Windows 7,當我例行文件備份在文件複製到外部硬盤,我使用PowerShell V2(從批處理文件運行)對拷貝重新創建文件中的所有時間戳的原始文件。批處理文件中的Powershell - 如何轉義元字符?

下面的代碼工作成功地在大多數情況下,但並非總是如此: -

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\") 

上面的代碼複製該文件,然後設置創建日期/時間上的副本(目標)文件到的源文件,當我拖放源文件到我的批處理文件。

但在某些情況下,代碼失敗。如果文件名包含'毒'字符,如(例如)方括號 [...],則會給出錯誤「屬性'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\") 

以供將來參考,請注意(在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%」中的空格是ESSENTIAL,它不是錯誤。

    b。在所有情況下致命的唯一毒藥字符是EXCLAMATION MARK(!)。

    c。有毒字符只在FILENAME中出現問題,而不在路徑中。

+1

良好的漁獲後重新在'「%路徑%‘'空間:問題是,'robocopy'’在一個參數作爲_escaped_'的端'」',打破了命令對待一個'\ 。嚴格地說,正確的解決方法是_double_尾部的'\'(例如''E:\\「'),但是您可以附加一個_space_,因爲路徑中任何尾隨的空白都是_ignored_。因此,使這種調用健壯的一種簡單方法是在傳遞文件夾路徑時,_always_使用'「%var%」'(在結束雙引號之前的尾部空格)。我相應地更新了我的答案。 – mklement0

+1

只有_filenames_易受wildard元字符解釋影響:確實:'robocopy'只支持'file'(_filename_)參數中的通配符,而不支持'source'和'destination'(_folder_)參數中的通配符;文件夾路徑始終被視爲文字。 – mklement0

+0

專業術語:請打電話給「posion characters」(通配符)_metacharacters_,這是已建立的和apt-term。 '!'只有問題,因爲你正在使用'enabledelayedexpansion' - 沒有它,'!'將被視爲文字。還有哪些元字符有問題? – mklement0

回答

2

首先第一件事情:

在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%" 

注:

  • 雖然robocopy主要用於複製整個目錄,它確實允許您通過指定以第三個位置參數指定的通配符表達式複製單個文件,如上面的"%fname%"
    鑑於robocopy - 不像PowerShell的 - 不考慮[]通配符metacharacters,這種方法應該工作(你只有一個問題,如果你的文件名包含嵌入式*?字符,這是不可能的)。

  • 拖尾空間技巧(例如,,"%fpath% ")是必要的,因爲robocopy - 正如大多數命令行實用程序所做的那樣 - 在參數結尾處將\"視爲轉義",這會中斷命令。嚴格來說,適當的解決方法是\(例如,"E:\\"),但你可以逃脫追加空間,因爲在路徑的任何尾隨空白是忽略。因此,一個簡單的方法來使這種調用健壯的是總是使用"%var% "(拖尾空間在結束雙引號之前)傳遞文件夾路徑時。

  • robocopy沒有開關類似於/V,導致它驗證該文件被正確複製,但 - 至少根據this blog post - 運行verify on事先應具有相同的效果。


如果您仍然需要通過PowerShell來複制創建時間戳:

powershell -command "(Get-Item -LiteralPath '%dest%%fname%').CreationTime=(Get-Item -LiteralPath '%fpath%%fname%').CreationTime" 

買者:如果有你的文件名有嵌入'字符的機會。 (單引號/撇號),您必須逃脫他們首先,通過他們加倍(例如,%fname:'=''%返回%fname%所有'情況下身價倍增):

powershell -command "(Get-Item -LiteralPath '%dest:'=''%%fname:'=''%').CreationTime=(Get-Item -LiteralPath '%fpath:'=''%%fname:'=''%').CreationTime" 
  • 注意,命令作爲整體包含在"..."中,以防止可能包含在變量值中的元字符(如&)破壞命令。 [1]

  • 內部命令串,'...'用於確保變量值作爲文字處理由PowerShell的(如果使用"..."和文件名包含$,例如,其結果會是意想不到的)。

  • -LiteralPath確保Get-Item解釋爲字面的文件路徑,而它是隱含的
    -Path參數傳遞時,該路徑位置上,和路徑傳遞給
    -Path被解釋爲通配符表達式,這可能會導致PowerShell通配符元字符出現問題,例如[]

  • 無需將源文件的.CreationTime屬性值轉換爲日期+時間字符串第一個;您可以直接將其分配給目標文件的.CreationTime屬性,該屬性的類型爲[System.DateTime]


[1]引用頭痛

  • 在問題(\"%dest%\%fname%\")圍\轉義雙引號的變量引用,如,是不是就夠了,因爲這樣做值得空白標準化,這意味着多於一個空間的運行被標準化爲單個空間。

  • 雖然另外包圍命令作爲一個整體在有助於"..."原則上,然後cmd.exe不整體串識別爲單一,雙引號字符串,在這種情況下,元字符如在&變量值可以中斷該命令;例如,
    powershell.exe -command "echo \"a b\""工作正常,忠實地保留空白,但由於&
    powershell.exe -command "echo \"a & b\""中斷。

    • 使用\""代替\"解決了元字符的問題,而是重新引入空白規範化:
      powershell.exe -command "echo \""a & b\"""產生a & b
  • 因此,使用'...' PowerShell的字符串整體"..."字符串中是最簡單的方法,使命令robust:您只需要在變量值中跳轉'實例。

0

歸根到底,這是我可以在批處理文件中提出的最佳解決方案。

它通過在Windows命令外殼中使用REN和SET命令,100%可靠地工作,它不會遭受ROBOCOPY或POWERSHELL的缺點。

@echo off 

:: ** INPUT File ** 
    SET file=%1 

:: ** Destination Directory ** 
    SET dest=C:\Users\%Username%\Desktop\test 

:: ** Copy using Command Shell ** 
    COPY /V /Y %file% "%dest%" 

:: ** Location of PowerShell ** 
    SET PowerShell=C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe -NoProfile 

:: ** Store PATH & NAME of source file (WITHOUT quotation marks) ** 
    FOR /F "usebackq delims==" %%A IN ('%file%') DO (
      SET fpath=%%~dpA 
      SET fname=%%~nxA 
    ) 

:: ** Rename the Files ** 
    REN "%fpath%%fname%" temp1 
    REN "%dest%\%fname%" temp2 

:: ** Set CREATED date of output file ** 
:: NB: Will fail if MONTHS (MM) is identical to MINUTES (mm) 
    %PowerShell% (Get-Item \""%dest%\temp2\"").CreationTime=$(Get-Item \""%fpath%temp1\"" ^| Select-Object -ExpandProperty CreationTime ^| Get-Date -f \"MM-dd-yyyy HH:mm:ss\") 

:: ** Set MODIFIED date of output file ** 
:: NB: Will fail if MONTHS (MM) is identical to MINUTES (mm) 
    %PowerShell% (Get-Item \""%dest%\temp2\"").LastWriteTime=$(Get-Item \""%fpath%temp1\"" ^| Select-Object -ExpandProperty LastWriteTime ^| Get-Date -f \"MM-dd-yyyy HH:mm:ss\") 

:: ** Wait ** 
    :: No, I have no idea why it doesn't work without this... 
    echo. & echo Wait 15 Seconds ... & echo. 
    @CHOICE /T 15 /C yn /D y > NUL 

:: ** Restore ORIGINAL filenames ** 
    REN "%fpath%temp1" "%fname%" 
    REN "%dest%\temp2" "%fname%" 
0

而不是通過篡改先前的答案渾水,這是我對這個問題的最新'採取'。

一個月的額外擺弄使我放棄我以前的解決方案之一,它是需要重命名這些文件(雖然我沒發現(這真是棒極了!),一個真正的整齊想法:你不能遇到任何有毒的元字符,如果你採用我的簡單解決方案,在運行Powershell或Robocopy之前將文件重命名爲任何無害的臨時名稱)。

但是,以下是從上述想法中挑選的解決方案,經過一個月的測試後,還沒有拋出任何失敗。它通過重命名文件採取任何「未經授權」的快捷方式!

以下批處理文件現在位於我的Windows 7發送文件夾中,因此始終可以從Windows資源管理器的右鍵菜單訪問。

Windows 7的 「發送到」 文件夾:

C:\用戶\%用戶名%\應用程序數據\漫遊\微軟\的Windows \的SendTo

@echo off 

:: *** Copy file including its CREATED date & MODIFIED date *** 

:: File : Drag-and-Drop 
    SET file=%1 

:: Destination Directory 
    SET dest=E:\ 

:: ** Safety Checks ** 
    ATTRIB -R -A -S -H %file% 

:: ** Store PATH & NAME of file (WITHOUT quotation marks) ** 
    FOR /F "usebackq delims==" %%A IN ('%file%') DO (
     SET fpath=%%~dpA 
     SET fname=%%~nxA 
     SET name=%%~nA 
     SET ext=%%~xA 
    ) 

    :: *** POWERSHELL : File only *** 

    :: ** Copy File ** 
    COPY /V /Y %file% "%dest%" 

    :: ** Location of PowerShell ** 
    SET PowerShell=C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe -NoProfile 

    :: ** Set CREATED date of copy ** 
    :: NB: Will fail if MONTHS (MM) is identical to MINUTES (mm) 
    %PowerShell% -command "(Get-Item -LiteralPath '%dest:'=''%\%fname:'=''%').CreationTime=(Get-Item -LiteralPath '%fpath:'=''%%fname:'=''%').CreationTime" 

    :: ** Set MODIFIED date of copy ** 
    :: NB: Will fail if MONTHS (MM) is identical to MINUTES (mm) 
    %PowerShell% -command "(Get-Item -LiteralPath '%dest:'=''%\%fname:'=''%').LastWriteTime=(Get-Item -LiteralPath '%fpath:'=''%%fname:'=''%').LastWriteTime" 

    :: ** Set ACCESSED date of copy ** 
    :: NB: Will fail if MONTHS (MM) is identical to MINUTES (mm) 
    %PowerShell% -command "(Get-Item -LiteralPath '%dest:'=''%\%fname:'=''%').LastAccessTime=(Get-Item -LiteralPath '%fpath:'=''%%fname:'=''%').LastAccessTime" 

    :: Open Destination Directory 
    C:\Windows\Explorer.exe "%dest%"