# Solved: Removing Special Characters FOR Loop Batch File



## scrfix

I know that I can use the following method to remove special characters away from a folder name:
:: Request the name of the folder from the user
SET /P folder=Folder Name: 
Set folder=%folder:\= %
Set folder=%folder:/= %
Set folder=%folder:.= %
Set folder=%folder:!=%
Set folder=%folder=%
Set folder=%folder:#=%
Set folder=%folder:$=%
REM Set folder=%folder:%=% /* This does not work. Not sure how to get a % sign to work. */
REM Set folder=%folder:^=% /* This does not work. Not sure how to get a ^ sign to work. */
REM Set folder=%folder:&=% /* This does not work. Not sure how to get a & sign to work. */
REM Set folder=%folder=% /* This does not work. Not sure how to get a ( sign to work. */
REM Set folder=%folder=% /* This does not work. Not sure how to get a ) sign to work. */
REM Set folder=%folder:+=% /* This does not work. Not sure how to get a + sign to work. */
Set folder=%folder:{=%
Set folder=%folder:}=%
Set folder=%folder:[=%
Set folder=%folder:]=%
Set folder=%folder:'=%
Set folder=%folder:"=%
Set folder=%folder:;=%
Set folder=%folder::=%
REM Set folder=%folder:<=% /* This does not work. Not sure how to get a < sign to work. */
REM Set folder=%folder:>=% /* This does not work. Not sure how to get a > sign to work. */
Set folder=%folder:,=%
Set folder=%folder:?=%
REM Set folder=%folder:|=% /* This does not work. Not sure how to get a | sign to work. */
Set folder=%folder:`=%
REM Set folder=%folder:~=% /* This does not work. Not sure how to get a ~ sign to work. */
REM Set folder=%folder:^==% /* This does not work. Not sure how to get a = sign to work. */
REM Set folder=%folder:^*=% /* This does not work. Not sure how to get a * sign to work. */
Echo You said your folder name will be %folder%.
@pause

*Two Questions*
1. Is there an easier way such as utilizing a FOR Loop?
2. When the user types certain special characters it screws up the variable such as *, =, <, >, (, ) (See notes above with this does not work.) (How do I filter those out before it gets submitted to the program?)

Thanks for any help,

Wayne
*Computer Repair*


----------



## TheOutcaste

Welcome to TSG!

The *&*, *<*, *>*, and *|* are actually commands:
* &* allows two commands on the same line.
* <* and *>* are re-direction symbols
* |* is the Pipe command.
These will be seen as commands _unless the line is quoted_
The ampersand can be checked for without quoting the line, but if it contains any of the other symbols, it will fail.
And the pipe has to be checked for first.

Variables are expanded first, so any unquoted variable containing the *&*, *<*, *>*, or | will cause an error.

^, +, (, or ) should work just fine, but if the value entered contained *&*, *<*, *>*, *%*, or *|*, that could make them fail.

The caret (^) is the escape character. *Set folder=%folder:^=%* works, but each set statement will also remove carets if the value is not quoted, so it might be automatically removed. The user may enter it in quotes, so best to check for it anyways.

The *~* is used in extracting substrings. If present, the command processor expects numbers to follow. Since they are missing, it treats it as a character, but at this point the % symbols have been removed.
So *Set folder=%folder:~=%* gets interpreted as *Set folder=folder:~=*, and folder now has the value *folder:~=*
You'd think escaping it would work, but it doesn't.
Same with the equals sign.
The asterisk can't be matched. From set /?:


> Environment variable substitution has been enhanced as follows:
> 
> %PATH:str1=str2%
> 
> would expand the PATH environment variable, substituting each occurrence
> of "str1" in the expanded result with "str2". "str2" can be the empty
> string to effectively delete all occurrences of "str1" from the expanded
> output. "str1" can begin with an asterisk, in which case it will match
> everything from the beginning of the expanded output to the first
> occurrence of the remaining portion of str1.


So *set folder=%folder:*=%* fails as str1 is not specified. Using *set folder=%folder:^*=%* also fails, as that specifies str1 as null, which won't match anything in the string, so no substitution will occur.
So the following will check for everything except ~, *, and =:


Code:


SET /P folder=Folder Name:
:: Add quotes to check for special characters
Set folder="%folder:|=%"
Set folder=%folder:&=%
Set folder=%folder:>=%
Set folder=%folder:<=%
Set folder=%folder:^=%
:: Remove the quotes, not needed for the rest of the checks
Set Folder=%folder:~1,-1%
Set folder=%folder:+=%
Set folder=%folder:(=%
Set folder=%folder:)=%
Set folder=%folder:\= %
Set folder=%folder:/= %
Set folder=%folder:.= %
Set folder=%folder:!=%
Set folder=%folder:@=%
Set folder=%folder:#=%
Set folder=%folder:$=%
Set folder=%folder:{=%
Set folder=%folder:}=%
Set folder=%folder:[=%
Set folder=%folder:]=%
Set folder=%folder:'=%
Set folder=%folder:"=%
Set folder=%folder:;=%
Set folder=%folder::=%
Set folder=%folder:,=%
Set folder=%folder:?=%
Set folder=%folder:`=%
Echo You said your folder name will be %folder%.
@pause

You can use a For loop to check for the *~*, ***, and *=*, as well as checking all the rest of the items.
This will require using Delayed Expansion, so the ! has to be checked for before that is enabled. And the *(*, *)*, and *"* will have to be escaped, and the " must be the last check. in the For loop.
Give this a whirl:


Code:


[COLOR=DarkRed]:: First make sure Delayed Expansion is Disabled
SetLocal DisableDelayedExpansion[/COLOR]
SET /P folder=Folder Name:
:: Quote the variable and remove the !
Set folder="%folder:!=%"
[COLOR=DarkRed]:: Now revert to previous settings. The Set is needed to pass the
:: Folder variable back to the previous environment
EndLocal&Set folder=%folder%[/COLOR]
Setlocal EnableDelayedExpansion
For %%I In (^| ^& ^< ^> ^^ + ^( ^) \ / . @ # $ { } [ ] ' ; : , ? ` [COLOR=Blue]^%%[/COLOR] ^") Do Set folder=!folder:%%I=!
:: Repeatedly search for ~, =, and *
:_parse
For /F "Tokens=1* Delims=~=*" %%J In ('Echo !folder!') Do (
Set folder=%%J%%K
Set _flag=%%K
)
If NOT "%_flag%"=="" Goto _parse
:: Now revert to previous settings. The Set is needed to pass the
:: Folder variable back to the previous environment
Endlocal&Set folder=%folder%
Echo You said your folder name will be %folder%.
@pause

HTH

Jerry
Edit: Added the percent symbol so it will now remove those symbols.
Also added the code in Dark Red to ensure Delayed expansion is disabled.
Not needed if you know that Delayed Expansion is disabled when the section of code is entered


----------



## scrfix

WOW!. That's awesome. That is definitely at a more advanced level then what I am even close to currently.

Thank You.

I will try that tomorrow. Going to sleep right now. Almost 1am.

Wayne


----------



## TheOutcaste

Whoops, just realized I forgot to check for the % symbol. I'll have to test tomorrow to see the best way to handle that and edit the code to include it.


----------



## scrfix

That works absolutely perfectly. WOW! I Thank you very much for your help.

I finally got enough time to read through everything. Talk about detail oriented and clear and precise. Wow. I learned a ton from that. More than those tutorials I have been reading. The explanations are only surpassed by the brilliance and knowledge shown in the code provided. There needs to be more detailed oriented people in the world like you. Do you ever feel at times that we (meaning detail oriented people in general) are alone... lol

You would have to see my post on detail oriented people to understand.

Wayne
*Computer Repair*


----------



## scrfix

I just tested the code a little more thoroughly. I was incorrect about the perfect part. It works fantastically however it does have a couple of bugs however. It appears to have some errors and will not remove some characters even though it says to remove them:

If I type in for the folder name: 
~`[email protected]#$%^&*()+={}[]|\:;"'<>,.?/Wayne
I receive the following error:
> was unexpected at this time.

If I type in Wayne;; for the folder name it does not appear to remove the ;.

Any ideas?

Wayne


----------



## TheOutcaste

More than a couple of bugs. Just shows I didn't test it with all of the characters in all possible combinations.
The problem here is the *"* you entered, which is before the *<* and *>* symbols. This matches with the first double quote, so the rest of the line is considered unquoted, so any special characters are actually processed.
So need to remove any quotes first, rather than the *!*. Since the line gets quoted at that point, we don't need to worry about the *!* being seen as the start of a variable, and can remove it once the other special characters are removed.

Found out it won't remove a *,* or *?* as well as the *;* either.
The *;* and *,* are seen as delimiters by the first For loop, so never get processed. Seems I forgot that and obviously didn't test with those.
The For loop is actually expecting a filename, so the *?* is treated as a wildcard character, so it searches the current directory for a file with a single character filename. If you don't have a file with a single character as a name in the current directory, it just gets skipped. If you do have such a file, say a file named a, it would remove the letter a from the entered folder name. So that wouldn't be good.
Same thing would happen with the ***, except it would match all files in the current directory. (This could be used to make sure the folder name entered does not contain the name of any files (including extension) in the current directory)
So just need to move those characters to the 2nd For loop.

And if you don't enter any valid characters, it won't remove *~ = * ; , ?*, so I added a check for that.

So give this a try and see if you can find any more bugs:



Code:


:_GetName
SET /P folder=Folder Name:
Set folder="%folder:"=%"
Setlocal EnableDelayedExpansion
For %%I In (^| ^& ^< ^> ^^ + ^( ^) \ / . @ # $ { } [ ] ' : ` ^%% ^") Do Set folder=!folder:%%I=!
:: Now remove any !
SetLocal DisableDelayedExpansion
Set folder="%folder:!=%"
EndLocal&Set folder=%folder:~1,-1%
:_parse
Set _Flag1=
For /F "Tokens=1* Delims=~=*;,?" %%J In ('Echo !folder!') Do (
Set folder=%%J%%K
Set _Flag1=%%J
Set _Flag2=%%K
)
If NOT "%_Flag2%"=="" Goto _parse
If Not Defined _Flag1 Echo None of the characters you entered are valid. Please try again&Goto _GetName
:: Now revert to previous settings. The Set is needed to pass the
:: Folder variable back to the previous environment
EndLocal&Set folder=%folder%
Echo You said your folder name will be %folder%.
@pause

And to remove the surrounding quotes so they aren't displayed, use this:
*Echo %~1*
See *Call /?* (or *For /?*) for an explanation of the parameter modifiers. Unfortunately, they only work for loop variables, or command line parameters.

Jerry


----------



## scrfix

Hey Jerry,

The code now seems to work perfectly. I am curios on a couple of items however.

1. Can I use this for other items as well kind of like a function where I can call this function whenever I need it and not just for the folder name? If so, can I call it from another for loop? If both are possible, how would I do that?

2. I have another problem that I am working with. I get an error on occassion (insufficient memory) using xcopy (I cannot utilize robocopy or xxcopy because they are not readily available on most machines). I already know why this is and I have devised a way around this. It is programming it that I am going to have a problem with.

*The Error*
The insufficient memory error happens when the pathway for either the source of the copy or the destination of the copy is greater than (documented 255 characters; reality around 230).

*The work-a-round*
Have a wrapper around xcopy that checks the length of each file path. If it is greater than 200 characters, it then truncates that filename. or the destination folder until the pathway is under 200 characters. If for some reason it cannot reduce the file pathway length to less than 200 characters, it creates a new directory on the root of wherever it is copying and then copies any of those files there. If It is still over 200 characters long, it skips the file and writes to the log file and says sorry I cannot copy this file with the pathway to the file that cannot be copied. I would put email capability in there so that it emails the person however I am not that far yet.

1. Checks pathway length in number of characters
2. If over 200, attempts to truncate the file that it is copying.
3. If still over 200 characters, attempts to stop xcopy, rename the root destination directory.
4. If still over 200 characters, creates a new folder in the root of the destination and attempts to copy over there.
5. If still over 200 characters then it skips that file starts xcopy up again on the next file and rights that file to the log with the pathway and somehow notifies the user.

I just ran into this scenario on an HP that has this super long stupid file name. It must have been 190 characters in itself.

Any ideas on that one?

Wayne


----------



## TheOutcaste

You can call it as a subroutine but if the input data contains a % or a ", you can't pass the input data as a parameter to the routine.
It might work if you are using a For loop and the string is contained in the Loop variable, I haven't tested that.

You would have to use the variable *folder* to get the data, then call the routine which will use the already defined value of *folder*. The routine can be a separate batch file, or contained in the same batch file.
You'd probably want to change the name of the folder variable to something more generic like *StrInput*. You can also change the *_GetName* label to something like *_CheckInput* to call the subroutine.
You would have to modify it to return an error flag if none of the characters are valid, rather than looping back to the :_GetName label.
So change this:


Code:


If Not Defined _Flag1 Echo None of the characters you entered are valid. Please try again&Goto _GetName

to something like this:


Code:


If Not Defined _Flag1 Endlocal&Set Errflag=1&Goto :EOF

Then the calling routine would have to check that flag.

Then just remove the Set /P and echo statements from the routine.

For #2 if the machines are networked, might be easier to check if robocopy or xxcopy are installed, and if not, copy one of them from a shared folder.
Or if running the batch file from a CD/USB drive, include robocopy or xxcopy on the CD/USB drive.
For example, this will check if robocopy.exe is in a folder on the path, and copies it to the PC if it's not:


Code:


Set RCInstalled=
For %%I In (robocopy.exe) Do Set RCInstalled=%~$PATH:I
If Defined RCInstalled Goto _UseRobo
Xcopy "\\server\Public\robocopy.exe" "%systemroot%\system32"
:_UseRobo

Anything else would get difficult. Dir will list the long files, but you can't copy, move, or rename them if the path is too long, not even using the short name.
Only way I can think of off hand would be to share a folder closer to the file using *Net Share*, resulting in a path short enough for Xcopy to handle, then reference the file from the shared folder, something like this:
xcopy \\%computername%\%sharename%\%filename% D:\longnames\
or map the share to a drive letter if the expanded length of \\%computername%\%sharename% would be a problem.

Several ways to determine the folder to share, depends on if the excessive length is due to nested folders, or just a long filename, and whether or not there will be multiple files under one folder.
Then have to decide if you want to preserve the long path, which would require creating a share on the destination or move those files to a special folder.

Have to do some experimenting when I get a chance.

HTH

Jerry


----------



## scrfix

Jerry,

I have question if you don't mind.

Here is example code:



Code:


@echo off
:COPYUSERS
for /f "tokens=*" %%a in ('dir C:\Users /b /ad') do call :PROCESS "%%a"
goto EOF
:PROCESS
if [%1]==["Default"] GOTO EOF
echo xcopy "C:\Users\%~1" "Z:\Users\%~1"
:EOF
 
:OTHERFILES
Echo /****************************************************/
Echo /* Why does this show up in the middle of the loop. */
Echo /****************************************************/
Echo /*************************************************************/
Echo /* This should not show up until after the loop is completed */
Echo /*************************************************************/
@pause

First,
this is not the real code I am using. It is an extremely scaled down version of the code to make things easier to read. I know that I can place the :OTHERFILES before the loop and that goes around the issue however that doesn't solve the problem. Currently what happens is the loop plays and then exits, continues with the rest of the program and then comes back and does it again. This is not good. How do I get the FOR loop to only work within its loop until the loop is done processing?

I would like the loop to run as many times as it needs to run and then continue on to the rest of the program. Is the loop programmed wrong?

If you copy and paste that into bat file you will see what I mean.

What am I doing wrong?

Wayne


----------



## TheOutcaste

The Process subroutine doesn't end until you get to the end of the file, either by executing all the rest of the statements, or by using a *Goto :EOF* statement. You haven't used a *Goto :EOF* statement, so the OTHERFILES section is actually part of the PROCESS Section.
The label named *EOF* that you've created is not the same as the special label *:EOF* that always exists, and is always the End Of File.
The If statements in the Process subroutine jump to that label if they match. And if the If statements don't match, the echo is executed and then the OTHERFILES section.


Code:


What you should use is [B]Goto [COLOR=Red]:[/COLOR]EOF[/B] for the If statements, and at the end of the Process Subroutine. The colon is the key.
When used in a called Subroutine, [B]Goto [COLOR=Red]:[/COLOR]EOF[/B] will exit the subroutine and return to the statement following the Call. If not in a subroutine, it will exit the batch file.

The EOF label you've created is called by Goto EOF (no colon). They are different.
This is what you should have:
[code]@echo off
:COPYUSERS
for /f "tokens=*" %%a in ('dir C:\Users /b /ad') do call :PROCESS "%%a"
goto [COLOR=Red]OTHERFILES[/COLOR]
[COLOR=Blue] :: Start of PROCESS Subroutine[/COLOR]
:PROCESS
if [%1]==["Default"] GOTO [COLOR=Red]:[/COLOR]EOF
echo xcopy "C:\Users\%~1" "Z:\Users\%~1"
[COLOR=Red]Goto[/COLOR] :EOF
[COLOR=Blue] :: End of Process Subroutine[/COLOR]
 
:OTHERFILES
Echo /****************************************************/
Echo /* Why does this show up in the middle of the loop. */
Echo /****************************************************/
Echo /*************************************************************/
Echo /* This should not show up until after the loop is completed */
Echo /*************************************************************/
@pause

Jerry


----------



## scrfix

Thanks,

I actually had :EOF in there at one point in time and thought that I was screwed up because it was still doing the same thing. The part that had I screwed up initally was the GOTO OTHERFILES. I had that has GOTO EOF and I created an EOF label and that was what was originally messing it up. Trying to get it to work, I just messed around with it more and more and could not get it to work. Apparently, I was making it worse.

Thanks again for the 4.0 explanations.
I have been in different forums for years and you are the first person that I have come across that provides 4.0 explanations in terms you can understand other than myself.

I don't know if you read any other posts in my forum because your explanations remind me of my own:

*Badware Explanations*

*Error Code: 0x800704DD OR 0x80240020*


----------



## scrfix

I am attempting to read a registry entry and I have the code working somewhat. The issue is that if there is something in the spot with spaces it will not go past the first space.



Code:


set var=Reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion" /v "RegisteredOwner"
for /f "tokens=3" %%a in ('%var%') DO set var=%%a
echo %var%
@pause

This works phenomenal if the data within it is a single word however if it is two words, then I have a problem. I would like to capture everything.

I have even tried:



Code:


set var=Reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion" /v "RegisteredOwner"
for /f "tokens=3" %%a in ('"%var%"') DO set var="%%a"
echo %var%
@pause

If you temporarily set that registry key to howard johnson then you will see what I mean.

What am I doing wrong?

Thank you for any help on this.


----------



## TheOutcaste

I'd use this:



Code:


For /F "Tokens=2*" %%a In ('Reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion" /v "RegisteredOwner"') Do set var=%%b
echo %var%

The *2** means assign the loop variable to the 2nd parsed item, and assign the rest of the line (if any) to the next loop variable, in this case *b*.

HTH

Jerry


----------



## scrfix

Jerry, 

That works. I do not understand why yours worked and mine did not? I didn't even know that I could do it that way. I have some other FOR Loops I am going to change now but I still don't understand why mine did not take spaces.

Wayne


----------



## Squashman

By default a FOR loop uses a space as a delimiter. Since you are not defining the For loop options to not use a delimiter and you are not defining the token to assign the remaining variable to the last token, yours doesn't work.


----------

