這個問題是延伸 使用bat批次檔做檔案歸類 這個主題
由於覺得不適合寫在同個標題內,故又開一個新標題
這是該主題的延伸
是實際上在整理檔案的狀況
條件為
環境:
一共4層資料夾
批次檔放在第1層資料夾,第1層資料夾下有無數的子資料夾(第2層資料夾)
需要處理的檔案放在第2層資料夾內,第2層資料夾下亦有無數的子資料夾(第3層資料夾)
第3層資料夾內亦有無數子資料夾(第4層資料夾)
依據「條件」把檔案放到指定的第4層資料夾內
條件:
同樣喂Bing AI,結果檔案只放到第3層目錄(個人雖然有做小小變動)
Bing AI提供的程式碼如下
@echo off
setlocal enabledelayedexpansion
rem 設置第1層資料夾路徑
set "folder1=D:\Test"
rem 遍歷第2層資料夾
for /d %%a in ("%folder1%\*") do (
rem 遍歷第2層資料夾中的文件
for %%b in ("%%a\*") do (
rem 擷取文件名稱的前4個字元
set "filename=%%~nxb"
set "first4=!filename:~0,4!"
rem 遍歷第3層資料夾
for /d %%c in ("%%a\*") do (
rem 比對第3層資料夾名稱的前4個字元
set "foldername=%%~nxc"
if "!foldername:~0,4!"=="!first4!" (
rem 顯示第1次匹配成功
echo Matched: !filename! with folder !foldername!
rem 將文件移動到第3層資料夾中
move "%%b" "%%c"
rem 顯示第1次移動成功
echo Moved: !filename! to folder !foldername!
)
)
)
)
rem 遍歷第3層資料夾
for /d %%a in ("%folder1%\*\*") do (
rem 遍歷第3層資料夾中的文件
for %%c in ("%%a\*") do (
rem 擷取文件名稱的第5和第6字元
set "filename=%%~nxc"
set "char56=!filename:~4,2!"
rem 遍歷第4層資料夾
for /d %%d in ("%%a\*") do (
rem 比對第4層資料夾名稱的前2個字元
set "foldername=%%~nxd"
if "!foldername:~0,2!"=="!char56!" (
rem 顯示第2次匹配成功
echo Matched: !filename! with folder !foldername!
rem 將文件移動到第4層資料夾中
move "%%c" "%%d"
rem 顯示移動成功
echo Moved: !filename! to folder !foldername!
)
)
)
)
endlocal
pause
這張圖抱歉少畫一層目錄(用小畫家畫這張圖快要20分鐘…)
簡單來講就是把第二層目錄的檔案,依據條件判斷後把檔案放到第三層目錄夾,然後放到第三層目錄夾後再度比對後,再細分到第四層目錄夾
若有不足處還請不吝指教,感謝各位前輩
因為你有兩層要做類似動作, 我寧願用 Function 方式實作; 說明丟註解內:
@echo off
@rem chcp 950
setlocal EnableDelayedExpansion
@rem ##
set "folder1=D:\Test"
@rem ##
@rem ## myCreepyMove: %1:Path; %2:FilePart; %3:FolderPart;
for /d %%G in ("%folder1%\*") do (
@rem 將第2層文件移動到第3層中
call :myCreepyMove "%%~G" "0,4" "0,4"
for /d %%H in ("%%~G\*") do (
@rem 將第3層文件移動到第4層中
call :myCreepyMove "%%~H" "4,2" "0,2"
)
)
echo Done;
goto :eof
@rem ##
:myCreepyMove
@rem ## 將目標目錄下的檔案, 依照名稱擷取索引敘述與子目錄比對,
@rem ## 比對符合時搬移至子目錄;
@rem ## %1: 目標目錄路徑;
@rem ## %2: 檔案名擷取索引敘述;
@rem ## %3: 目錄名擷取索引敘述;
setlocal
for %%A in ("%~1\*") do (
@rem ## 針對每個檔案, 呼叫 myCreepyMoveP01 處理;
call :myCreepyMoveP01 "%~1" "%~2" "%~3" "%%~nxA"
)
endlocal
goto :eof
@rem ##
:myCreepyMoveP01
@rem ## 為節省資源 (or 忽略錯誤), 在檔案搬移後, 就不繼續比對;
@rem ## 所以分出這段, 以 `goto :eof` 作為 `break`。
setlocal
set "myFN=%~4"
for /d %%B in ("%~1\*") do (
set "myDN=%%~nxB"
if "!myFN:~%~2!"=="!myDN:~%~3!" (
echo Matched: [!myFN!] with folder [!myDN!] in [%~1];
move "%~1\!myFN!" "%~1\!myDN!"
goto :eof
)
)
endlocal
goto :eof
@rem ##
:EOF
另, 我是覺得, 對檔案名稱剖析出該放置的位置後, 直接從第二層搬到第四層比較快:
( 或許, 你有需要用到遺留在第三層的檔案? )
@echo off
@rem chcp 950
setlocal EnableDelayedExpansion
@rem ##
set "folder1=D:\Test"
@rem ##
for /d %%A in ("%folder1%\*") do (
for %%B in ("%%~A\*") do (
set "filename=%%~nxB"
set "DestPath=%%~A\!filename:~0,4!\!filename:~4,2!"
if exist "!DestPath!" (
echo Matched: ["%%~nxB"] with folder [!DestPath!];
rem move "%%~B" "!DestPath!"
call :tryMove "%%~B" "!DestPath!"
) else (
echo Can not find folder for [%%~nxB]; 1>&2
)
)
)
echo Done;
goto :eof
@rem ##
:tryMove
@rem ## 就只是加個目標路徑的目錄屬性檢查;
setlocal
set myAttr=%~a2
if "%myAttr:~0,1%"=="d" (
move "%~1" "%~2"
) else (
echo [%~1] is not a folder; 1>&2
)
endlocal
goto :eof
@rem ##
:EOF
因為有人提到 PowerShell, 所以給個雙模式的 .ps1
給你參考:
( 因為 隨便寫 + 亂塞東西, 看起來有點大~ 其實依需求選用內容會很小喔~ 應該~~ )
## This is a '.ps1' file;
## 宣告主要函式;
function run-myMain {
$folder1 = 'D:\Test'
##
## myCreepyMove 使用版本選擇: 在下方第三行的 "[0]" 內選用 "0" or "1";
Set-Alias 'myCreepyMove' @(
'myCreepyMoveRev1', 'myCreepyMoveRev2'
)[0] -Scope Script
Write-Host (
"Alias[myCreepyMove]: " `
+ "$($(Get-Alias myCreepyMove).ResolvedCommand.Name);"
)
## 取得資料夾內子物件的集合; 就是第二層的那些資料夾;
$myCol01 = Get-ChildItem $folder1 -Directory
$myCol01 |%{ ## "%": alias of ForEach-Object;
## 將第二層的那些資料夾, 每一個都丟給 myCreepyMove 處理;
myCreepyMove $_ (0,4) (0,4)
Get-ChildItem $_ -Directory |%{
## 給 myCreepyMove 處理第三層的資料夾;
myCreepyMove $_ (4,2) (0,2)
}
}
##
return
}
## 宣告 myCreepyMoveRev1 函式;
function myCreepyMoveRev1 {
<##
此函式用途: 將目標目錄下的檔案, 依照名稱擷取索引敘述與子目錄比對,
比對符合時搬移至子目錄;
#>
param(
## $target: 目標目錄路徑;
$target = $(throw "Target parameter is required."),
## $FNSA: 檔案名擷取(Substring())用;
$FNSA = $(throw "`"File name Substring() parameter`" is required."),
## $DNSA: 目錄名擷取用;
$DNSA = $(throw "`"Directory name Substring()`" parameter is required.")
)
## 取得資料夾內子物件的集合;
$myCol01 = Get-ChildItem $target
## 取得資料夾內子物件的集合;
$myCol01 |?{ ## "?": alias of Where-Object
## 過濾出檔案;
-not $_.PSIsContainer
}|%{
$myFile = $_
$myCol01 |?{
## 過濾出目錄;
$_.PSIsContainer
}|?{
## 過濾出名稱片段比對符合的目錄;
$myFile.Name.Substring($FNSA[0],$FNSA[1]) `
-eq $_.Name.Substring($DNSA[0],$DNSA[1])
}|%{
Write-Host ( `
"Matched: [$($myFile.Name)] with folder [$($_.Name)] " `
+ "in [$($myFile.Directory)];" `
)
if(Test-Path $myFile){
## `Test-Path $myFile` 檢查是為了略過檔案早已被搬走的錯誤;
## 畢竟 break 不會做用於 ForEach-Object 上;
Move-Item $myFile.FullName $_.FullName
}
}
}
}
## 宣告 myCreepyMoveRev2 函式;
function myCreepyMoveRev2 {
<## 用途和 myCreepyMove 一樣~ #>
param(
$target = $(throw "Target parameter is required."),
$FNSA = $(throw "`"File name Substring() parameter`" is required."),
$DNSA = $(throw "`"Directory name Substring()`" parameter is required.")
)
## 分類至集合;
$myFiles01 = @()
$myFolders01 = @()
Get-ChildItem $target |%{
if($_.PSIsContainer){
$myFolders01 += $_
} else {
$myFiles01 += $_
}
}
## 比對與搬移;
foreach($file in $myFiles01){
foreach($folder in $myFolders01){
if(
$file.Name.Substring($FNSA[0],$FNSA[1]) `
-eq $folder.Name.Substring($DNSA[0],$DNSA[1])
){
Write-Host ( `
"Matched: [$($file.Name)] with folder [$($folder.Name)] " `
+ "in [$($file.Directory)];" `
)
Move-Item $file.FullName $folder.FullName
break ## 搬走檔案後不用比對了~
}
}
}
}
## 執行主要函式; 於結束時提示;
run-myMain; Write-Host "`aDone;";
##
在 Windows 上, PowerShell 與 Win-Cmd 的選用, 我個人感想是看需求或編程能力。
例如就你的案例而言,我個人是偏好用 Win-Cmd, 因為 Win-Cmd 輕巧快捷, 而且我有 Batch-Script 相當的基礎與經驗。
只有需要 PowerShell 限定功能, 我才會用 PowerShell; 因為 PowerShell 跟 Win-Cmd 比起來, PowerShell 太肥了~ (認真貌~)
( 複雜的要命的作業我就偏好 Python, 那語法糖太好吃了~ 若還加上效能需求就得去寫程式來編譯使用了~ )
若是新手, 我是推薦 C 語言, 在 Windows shell script 上, 我是推薦 PowerShell , 畢竟跟 Windows 系統整合度很高, 只是要注意版本差異、 程式呼叫處理等各種可能遇上的神奇狀況。
( PowerShell 除了版本號的差異外, 現在還有平台上的差異 (or here) , 真是多采多姿~~ )
關於 Win-Cmd 的語法參考, 小推 SS64 這裡, 查資料很方便~
( SS64 也有提供其他語言的參考文件, 不過我在這裡幾乎只是查閱 Win-Cmd 的語法~ )
(線上)畫圖的話, 小推一下 Diagrams.net / draw.io ; 雖說我也不常用, 但應付臨時而隨便的需求時還不錯用~