iT邦幫忙

0

使用bat批次檔做檔案歸類 --- 延伸進階

  • 分享至 

  • xImage

這個問題是延伸 使用bat批次檔做檔案歸類 這個主題
由於覺得不適合寫在同個標題內,故又開一個新標題

這是該主題的延伸
是實際上在整理檔案的狀況

條件為
環境:
一共4層資料夾
批次檔放在第1層資料夾,第1層資料夾下有無數的子資料夾(第2層資料夾)
需要處理的檔案放在第2層資料夾內,第2層資料夾下亦有無數的子資料夾(第3層資料夾)
第3層資料夾內亦有無數子資料夾(第4層資料夾)
依據「條件」把檔案放到指定的第4層資料夾內

條件:

  1. 掃描第2層資料夾內的檔案名稱
  2. 擷取檔案名稱的前4個字元
  3. 掃描第3層資料夾的前4個字元名稱
  4. 檔案和第3層資料夾名稱匹配比對成功後
  5. 顯示第1次匹配成功
  6. 把相同結果的檔案移動到第3層資料夾內
  7. 顯示第1次移動成功
  8. 掃描第3層資料夾內的檔案名稱
  9. 擷取該檔案名稱的第5和第6個字元
  10. 掃描第4層資料夾的前2個字元名稱
  11. 檔案和第4層資料夾的前2個字元名稱匹配比對
  12. 顯示第2次匹配成功
  13. 匹配比對後將相同結果的檔案放到第4層資料夾內
  14. 顯示移動成功

同樣喂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

https://ithelp.ithome.com.tw/upload/images/20230510/20105484cHWIddRGoY.png
這張圖抱歉少畫一層目錄(用小畫家畫這張圖快要20分鐘…)
簡單來講就是把第二層目錄的檔案,依據條件判斷後把檔案放到第三層目錄夾,然後放到第三層目錄夾後再度比對後,再細分到第四層目錄夾

若有不足處還請不吝指教,感謝各位前輩

看更多先前的討論...收起先前的討論...
alien663 iT邦研究生 4 級 ‧ 2023-05-10 16:13:13 檢舉
我以前用python有完成過類似的需求,是將一個"00000000.txt"的檔案放成"00/00/00/00.txt"的架構,不過我是用python寫的。
檔案網址: https://github.com/Alien663/Lib-Python/blob/main/I3SFile.py
如果是跟我的需求差不多的,那你應乾不需要這麼多層for loop下去,判斷如下
1. 檔案切完需要怎樣的路徑,分成資料夾路徑和檔案名稱
2. 資料夾路徑是否存在,若無,則建立路徑
3. 將檔案移動至路徑底下
其實現在的Windows基本上都有內建PowerShell,可以不用加裝任何程式就能以批次檔的執行模式運作(純文字編輯器就能寫程式,我建議用ISE),為何要PowerShell ? 因為BATCH老舊殘破,一句powershell以管道符串接的指定,用BAT要十幾幾十行,看了就煩

況且,BAT,頂多就是用來做檔案的事,其他的,真的不足,但PowerShell只有你想不到,沒有........如果你覺得取得設備資料、執行SQL抓資料不香的話!
rucifa iT邦新手 5 級 ‧ 2023-05-10 16:40:30 檢舉
謝謝前輩們的指教,我再看看怎麼處理
>> for /d %%a in ("%folder1%\*\*") do (
這行能跑嗎? 當然做不下去丫.
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 個回答

0
re.Zero
iT邦研究生 5 級 ‧ 2023-05-11 20:19:48
最佳解答

因為你有兩層要做類似動作, 我寧願用 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 ; 雖說我也不常用, 但應付臨時而隨便的需求時還不錯用~

https://ithelp.ithome.com.tw/upload/images/20230511/20155649rf1crw1JVV.png

rucifa iT邦新手 5 級 ‧ 2023-05-12 14:54:33 檢舉

感謝前輩指教

為什麼不直接把檔案直接放到第四層算是有原因
第一層除了放批次檔外,剩下的就是區域的資料夾(第二層資料夾名)
第二層放的資料夾是年份(第三層資料夾名)
第三層放的資料夾是月份(第四層資料夾名)

當然如果可以一開始就判別好的話那自然就可以直接放到第四層
只是我腦袋比較死,想說就一層一層這樣會比較保險就是(也怕會有漏網之魚,剛好檔案放在第三層)

關於前輩提到的 Diagrams.net / draw.io
這個我會好好應用的,謝謝!

我要發表回答

立即登入回答