Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Batch - EnableDelayedExpansion, exclamation mark and file/folder names

Tags:

batch-file

I'm struggling with exclamation marks (!) on file and folder names. The batch I created reads the content of a .txt file and sort the listed files from a folder into a subfolder.
For example:
source.txt has inside: file_1.zip and file_2.zip
The folder called system has file_1.zip, file_2.zip, file_3.zip, etc. and the batch reads source.txt and copy both files into system/source.

I tried to avoid enabling enabling DelayedExpansion, but after adding the folder browser I couldn't figure out a better way than using it. I needed it enabled for the way I show :listSources and :listFolders My problem right now is not so much handling (!) on folder names (although it would be wonderful to allow it), but to allow the copy of the files having a (!) on its name.

I thought about escaping ^^! the character or toggling between setlocal and endlocal, but I'm still learning how to properly use it, thus no idea how to handle it. Maybe there is a better way? (I'm not a programmer!)

@echo off
setlocal EnableDelayedExpansion

rem Copy sorted files into destination folder? [Y/N]
set "copyMode=y"
if /i "%copyMode%"=="y" (set appendTitle=[Copy Mode]) else set appendTitle=[Log Mode]

rem Set prefix for output file (used only if copyMode=n)
set prefixMiss=missing_in_

rem Set defaults to launch directory
set rootSource=%~dp0
set rootFolder=%~dp0

:listSources
title Source file selection %appendTitle%
Show folder names in current directory
echo Available sources for sorting:
for %%a in (*.txt) do (
    set /a count+=1
    set mapArray[!count!]=%%a
    echo !count!: %%a
)

:checkSources
set type=source
if not exist !mapArray[1]! cls & echo No source file (.txt) found on current directory^^! & goto SUB_folderBrowser
if !count! gtr 1 set /a browser=!count!+1 & echo.!browser!: Open Folder Browser? & goto whichSource
if !count! equ 1 echo. & set /p "oneSource=Use !mapArray[1]! as source? [Y/N]: "
if /i "%oneSource%"=="y" (set "sourceFile=1" & echo. & goto listFolders) else goto SUB_folderBrowser

:whichSource
echo.
rem Select source file (.txt)
echo Which one is the source file you want to use? Choose !browser! to change directory.
set /p "sourceFile=#: "
if %sourceFile% equ !browser! goto SUB_folderBrowser
if exist !mapArray[%sourceFile%]! echo. & goto listFolders
echo Incorrect input^^! & goto whichSource

:listFolders
title System folder selection %appendTitle%
rem Show folder names in current directory
cd %~dp0
set count=0
echo Available folders for sorting:
for /d %%a in (*) do (
    set /a count+=1
    set mapArray2[!count!]=%%a
    echo !count!: %%a
)

:checkFolders
set type=root
if not exist !mapArray2[1]! cls & echo No folders found on current directory^^! & goto SUB_folderBrowser
if !count! gtr 1 set /a browser=!count!+1 & echo.!browser!: Open Folder Browser? & goto whichSystem
if !count! equ 1 echo. & set /p "oneFolder=Use !mapArray2[1]! as target? [Y/N]: "
if /i "%oneFolder%"=="y" (set "whichSystem=1" & goto whichFolder) else goto SUB_folderBrowser

:whichSystem
echo.
rem Select system folder
echo Which system do you want to sort? Choose !browser! to change directory.
set /p "whichSystem=#: "
if %whichSystem% equ !browser! goto SUB_folderBrowser
if exist !mapArray2[%whichSystem%]! echo. & goto createFolder
echo Incorrect input^^! & goto whichSystem

:createFolder
rem Create destination folder
if /i not "%copyMode%"=="y" goto sortFiles
set destFolder=!mapArray[%sourceFile%]:~0,-4!
if not exist ".\!mapArray2[%whichSystem%]!\%destFolder%" md ".\!mapArray2[%whichSystem%]!\%destFolder%"

:sortFiles
rem Look inside the source file and copy the files to the destination folder
title Sorting files in progress... %appendTitle%
for /f "delims=" %%a in ('type "%rootSource%\!mapArray[%sourceFile%]!"') do (
if exist ".\!mapArray2[%whichSystem%]!\%%a" (
    if /i "%copyMode%"=="y" copy ".\!mapArray2[%whichSystem%]!\%%a" ".\!mapArray2[%whichSystem%]!\%destFolder%\%%~nxa" >nul
    echo %%a
) else (
    echo.
    echo %%a missing & echo.
    if /i not "%copyMode%"=="y" echo %%a >> "%~dp0%prefixMiss%!mapArray2[%whichSystem%]!.txt"
    )
)

title Sorting files finished^^! %appendTitle%

popd & pause >nul & exit

:SUB_folderBrowser
if !count! lss 1 set /p "openBrowser=Open Folder Browser? [Y/N]: "
if !count! lss 1 (
    if not "%openBrowser%"=="y" exit
)
set count=0 & echo Opening Folder Browser... & echo.
if "%type%"=="root" echo Select the root system folder, not the system itself^^!

rem PowerShell-Subroutine to open a Folder Browser
set "psCommand="(new-object -COM 'Shell.Application')^
.BrowseForFolder(0,'Please choose a %type% folder.',0,0).self.path""
for /f "usebackq delims=" %%i in (`powershell %psCommand%`) do set "newRoot=%%i"

if "%type%"=="source" set rootSource=%newRoot%
if "%type%"=="root" set rootFolder=%newRoot%

rem Change working directory
if "%type%"=="source" cls & pushd %rootSource% & goto listSources
if "%type%"=="root" cls & pushd %rootFolder% & echo Selected source file: !mapArray[%sourceFile%]! & echo. & goto listFolders

Any help would be greatly appreciated!

like image 262
paradadf Avatar asked Oct 18 '22 19:10

paradadf


1 Answers

Delayed expansion only causes problems within FOR loops when the FOR variable contains !.

You can typically avoid delayed expansion within a FOR loop if you CALL out to a :subroutine from within the loop. The subroutine will be reparsed for each iteration, so you can use normal %var% variable expansion. But CALL slows things down considerably, so I like to avoid it.

There is a simple solution that avoids delayed expansion and avoids call - use DIR /B with FINDSTR /N to inject line numbers into the directory output, which FOR /F can easily parse.

For example, here is a solution for the ListSources loop:

for /f "delims=: tokens=1*" %%A in ('dir /b /a-d *.txt^|findstr /n "^"') do (
  set "mapArray[%%A]=%%B"
  set "count=%%A"
  echo %%A: %%B
)

You can easily apply the same technique to your second loop.

You still need delayed expansion in other non-loop sections of code to expand array values. So start with delayed expansion disabled, then enable it in :whichSource, disable it again before the 2nd loop, and then enable it one more time after the last loop. Note that you cannot use ENDLOCAL, else you will loose your previously defined array values. So you will create a small stack of SETLOCAL as you toggle between disabled and enabled delayed expansion.

like image 142
dbenham Avatar answered Oct 21 '22 01:10

dbenham