Exclamation escaping after DIR command in FOR-Loop

Microsoft Windows
Post Reply
User avatar
MigrationUser
Posts: 336
Joined: 2021-Jul-12, 1:37 pm
Contact:

Exclamation escaping after DIR command in FOR-Loop

Post by MigrationUser »

05 May 2010 03:12
Mrk

Hi
My first post here.

The code below is a piece of my backup script which, in this case, drives me up the wall:

Code: Select all

setlocal enabledelayedexpansion

[...]

:some_subroutine
md %1\new_dir
for /f "tokens=*" %%i in ('dir "%1" /b') do (
    if "%%~ni" neq "new_dir" move "%1\%%i" "%1\new_dir"
)
goto :eof
Where: %1 is one of many directories passed to "some_subroutine" - I want to move all the files inside %1 into freshly created "new_dir".
The problem is that some of the filenames contain exclamation mark which is simply suppressed when processed in my loop and the routine fails to move these files.
I cannot escape exclamation using "^!" in filenames as the list of the files to be processed is unknown prior to this routine.

Anyone? Any help will be appreciated...

----------------------------

#2 05 May 2010 03:54
RG


Mrk,
I don't think you need delayed expansion here... although you may need it for the code represented by [...]. It is definitely what is eating your exclamation.
SETLOCAL and ENDLOCAL statements should be paired.
Try this:

Code: Select all

setlocal 

[...]

:some_subroutine
md %1\new_dir
for /f "tokens=*" %%i in ('dir "%1" /b') do (
    if "%%~ni" neq "new_dir" move "%1\%%i" "%1\new_dir"
)
goto :eof
endlocal

See my recent post on 'SETLOCAL ENABLEDELAYEDEXPANSION' a few posts down.
If it turns out that you need delayed expansion for [...] then do this:

setlocal enabledelayedexpansion

[...]
endlocal

:some_subroutine
setlocal
md %1\new_dir
for /f "tokens=*" %%i in ('dir "%1" /b') do (
    if "%%~ni" neq "new_dir" move "%1\%%i" "%1\new_dir"
)
endlocal
goto :eof
You should probably use "" around the argument when you call :some_subroutine in case there are spaces in the folder names. Of course then you should change %1 to %~1 to remove those quotes in :some_subroutine

Last edited by RG (05 May 2010 03:58)

Windows Shell Scripting and InstallShield

----------------------------

#3 05 May 2010 12:25
Mrk


Thanks for your reply RG

The code I've submitted here is simplified version of one of several "functions" in my script where this exclamation issue afflicts me.
This one indeed does not need enabledelayedexpansion and I'm actually using endlocal method within "some_subroutine", but...

...I already have too many places ("some_subroutines") where I have to turn off delayedexpansion; some of them are nested; in general enabledelayedexpansion is a must in my script and playing with a vast amount of endlocals disableng and enabling delayedexpansion is rather awkward;

I hoped to hear that I have missed some kind of syntax trick which will allow me to solve this problem in shorter and more neat way - but I am afraid you have just deprived me of this delusion :]

Thanks again for your help

Last edited by Mrk (05 May 2010 13:51)

----------------------------

#4 05 May 2010 19:15
bluesxman


Exclamation marks you want to keep are always a pain to handle with delayed expansion enabled. I'm afraid there are no easy answers to this one.

In your limited example, you could simply do this, as intimated by RG:

Code: Select all

:some_subroutine
setlocal disabledelayedexpansion
md %1\new_dir
for /f "tokens=*" %%i in ('dir "%1" /b') do (
    if "%%~ni" neq "new_dir" move "%1\%%i" "%1\new_dir"
)
endlocal
goto :eof
For other circumstances you're going to resort to all sorts of other hackery to save the "!" -- substituting it for another character that can't appear in a file name ("/" is a good option) as early as possible and only switching it back as late as possible.

After years using of delayed expansion liberally I finally concluded that, useful though it is, it should be used sparingly, only when absolutely necessary and with great care. You can largely (though not entirely) remove the need for delayed expansion by simply using the "call" trick.

For example:

Code: Select all

@echo off

for %%a in (*) do (
    set "file=%%a"
    call set "file=%%file:x=y%%"
    call echo "%%file%%"
)

pause
cmd | *sh | ruby | chef

----------------------------

#5 14 May 2010 04:21
NDog


I hope you don't mind me posting on to the end of this topic but I am having the same issue and cant seem to get around it.. I have pasted the whole code here, since its not that long.

Basically it syncs 2 folder ROOT's

I use it at an Internet cafe in town I go to, they are hosting a whole host of Asian tv shows on their server, I simply create the folder I want manually in my destination ROOT (must be the same name as on the server) and then the script scans the folder names and if they exist in both areas - copy across - ... It also writes to a log file so I don't re copy existing stuff..

So the problem is - if folders with !!! exist they don't get copied over... How would I feed this sub routine into my script?

Code: Select all

@echo off
setlocal enabledelayedexpansion

rem    set _srceROOT=\\Server\movies (f)\TV - Korean 1
rem    set _destROOT=%~dp0

    set _srceROOT=E:\vids
    set _destROOT=F:\Video\Drama\Korea

    REM Scan for Existing folders in the dest (excluding hidden)
    for /f "tokens=*" %%g in ('dir/b/ad-h "%_destROOT%"') do (
        set _dest=%%g
        REM Scan for Existing folders in the source (excluding hidden)
        for /f "tokens=*" %%h in ('dir/b/ad-h "%_srceROOT%"') do (
            set _srce=%%h
            if "!_dest!" == "!_srce!" (
                REM Start the transfer
                echo ==================
                echo !_srce!
                echo ==================
                if not exist "%_destROOT%\!_dest!\copied.txt" echo.>>"%_destROOT%\!_dest!\copied.txt"
                
                for /f "tokens=*" %%g in ('dir /b "%_srceROOT%\!_srce!" 2^>nul') do (
                    for /f "usebackq tokens=1* delims==" %%h in ("%_destROOT%\!_dest!\copied.txt") do (
                        if "%%g" == "%%h" set _nocopy=1
                        )
                    if not defined _nocopy xcopy "%_srceROOT%\!_srce!\%%g" "%_destROOT%\!_dest!\" /f/v/y/z && echo %%g>>"%_destROOT%\!_dest!\copied.txt"
                    set _nocopy=
                    )
            )
        )
    )

echo ==================
echo Finished...
echo ==================
echo  & start /min sndrec32 /play /close %windir%\media\ding.wav
pause>nul
exit


REM Unused copy switches
rem fsync "%_srceROOT%\!_srce!" "%_destROOT%\!_dest!" /k /f /c
rem if not defined _nocopy echo %%g & copy /v/y/z "%_srceROOT%\!_srce!\%%g" "%_destROOT%\!_dest!\" && echo %%g>>"%_destROOT%\!_dest!\copied.txt"
rem if not defined _nocopy xcopy "%_srceROOT%\!_srce!\%%g" "%_destROOT%\!_dest!\" /f/v/y/z && echo %%g>>"%_destROOT%\!_dest!\copied.txt"
cmd, vbs, ps, bash
autoit, python, swift

----------------------------

#6 14 May 2010 10:07
bluesxman


Delayed expansion is screwing you, but perhaps you already knew that. To be frank, I see no need to be using it in that script. I propose the following simplification to your FOR commands:

Code: Select all

REM Scan for Existing folders in the dest (excluding hidden) -- token %%D
for /f "tokens=*" %%D in ('dir/b/ad-h "%_destROOT%"') do (
    REM Scan for Existing folders in the source (excluding hidden) -- token %%S
    for /f "tokens=*" %%S in ('dir/b/ad-h "%_srceROOT%"') do (
        if "%%D" == "%%S" (
            REM Start the transfer
            echo ==================
            echo %%S
            echo ==================
            if not exist "%_destROOT%\%%D\copied.txt" echo.>>"%_destROOT%\%%D\copied.txt"
            
            for /f "tokens=*" %%D in ('dir /b "%_srceROOT%\%%S" 2^>nul') do (
                for /f "usebackq tokens=1* delims==" %%S in ("%_destROOT%\%%D\copied.txt") do (
                    if "%%D" == "%%S" set _nocopy=1
                    )
                if not defined _nocopy xcopy "%_srceROOT%\%%S\%%D" "%_destROOT%\%%D\" /f/v/y/z && echo %%D>>"%_destROOT%\%%D\copied.txt"
                set _nocopy=
            )
        )
    )
)
I've replaced the %%h and %%g tokens with %%S and %%D, respectively, throughout (so it's easy to see when you're referring to Source and Destination). I've also replaced all references to the _srce and _dest variables (which weren't doing you any favours) with %%S and %%D as well.

Obviously this is untested smile

cmd | *sh | ruby | chef

----------------------------

#7 16 May 2010 05:03
NDog


Thanks bluexman you saved the day again .. I didnt realise that the %% variables would be active since it was one big /for loop. Now I am confused to why we ever need !type variables, but I saw RG has written a article viewtopic.php?f=2&t=27. Im gonna read it now

Last edited by NDog (16 May 2010 05:08)

cmd, vbs, ps, bash
autoit, python, swift

----------------------------

#8 14 Jul 2010 22:31
Crash&Burn


I rarely ever see the need for ENABLEDELAYEDEXPANSION. When I was first cutting my teeth on cmd batch I used it liberally since SET's within ( ) code blocks aren't visible until you get outside the code block. Yet that usage is easily handled by using a CALL:_Some_Function "%%A" -- within a for loop. As well many FOR loops can be easily replaced with parsing the individual %1 args + SHIFT & GOTO.

I also find it better practice to do:
SET someVar="%someValue%"
vs.
SET "someVar=%someValue%"

The former generally handles all special chars that might be within a given string without needing exceptions to replace them at the start and reinsert them at the end.
Post Reply