Page 1 of 1

Escaping special characters in a variable in for loop

Posted: 07 Jan 2023 17:07
by dosthebos
Hi, I'm trying to create a batch file where I loop through a directory of audio files to create a variable holding all filenames wrapped in double quotes. I then pass this variable to foobar2000 to do some tagging.

The problem is my audio files can contain ampersands and exclamation marks.

For example, here's what that directory could look like:

Code: Select all

01. Some track.mp3
02. Some track & stuff.mp3
03. Some other track!.mp3
I would loop through this directory to create a variable containing the following:

Code: Select all

"01. Some track.mp3" "02. Some track & stuff.mp3" "03. Some other track!.mp3"
Here's what I have so far for my batch file:

Code: Select all

setlocal EnableDelayedExpansion
set tracks=
for /r %%i in (*.mp3) do (
    set tracks=!tracks! "%%i"
)

:: Tag with foobar2000!
c:\apps\foobar2000\foobar2000.exe /tag:GUESS:"%%TRACKNUMBER%%. %%TITLE%%":%%_FILENAME%% %tracks%

endlocal
If I echo %tracks% after the for loop, files with exclamation marks will have the exclamation mark stripped. For example, "03. Some other track!.mp3" will become "03. Some other track.mp3" which is wrong. I know the issue is probably due to how I'm using delayed expansion with double quotes. Is there anyway I can get this batch file to work with both ampersands and exclamation marks?

Thanks so much for any help!

Re: Escaping special characters in a variable in for loop

Posted: 09 Jan 2023 05:53
by T3RRY
Special handling of the environment state is required to preserve Exclamation characters.
Variable assignment must occur before DE is enabled, DE then gets toggled on and of during the loop, with an additional for loop required to tunnel the concatenation variable across the endlocal.

After the loop has finished, to safely use the string it should be expanded with DE enabled.
See the example below.

Code: Select all

@Echo off
	Setlocal EnableExtensions DisableDelayedExpansion

	If "!!"=="" (
		Echo( Delayed expansion must not be enabled at this time
		Endlocal
		Exit /B 1
	)
	Set "Tracks="
	For /f "delims=" %%G in ('Dir /B *.mp3') Do (
		Set "Track="%%G""
		Setlocal EnableDelayedExpansion
		If Defined Tracks For /F "delims=" %%H in ("!Tracks! !Track!")Do Endlocal & Set "Tracks=%%H"
		If Not Defined Tracks For /F "delims=" %%H in ("!Track!")Do Endlocal & Set "Tracks=%%H"
	)
	
	Setlocal EnableDelayedExpansion

	Echo(!Tracks!
	Endlocal & Endlocal
	Pause
	Exit /B 0
Edit: It's worth noting there is a length limit to batch variables of 8191 characters.
If there are too many files in the directory the above solution may not behave as expected.

Re: Escaping special characters in a variable in for loop

Posted: 10 Jan 2023 16:38
by dosthebos
T3RRY wrote:
09 Jan 2023 05:53
Special handling of the environment state is required to preserve Exclamation characters.
Variable assignment must occur before DE is enabled, DE then gets toggled on and of during the loop, with an additional for loop required to tunnel the concatenation variable across the endlocal.
Thanks so much T3RRY for the explanation and the code example.

The second loop is what I was missing. Can you break down the following bit of the code?

Code: Select all

		If Defined Tracks For /F "delims=" %%H in ("!Tracks! !Track!")Do Endlocal & Set "Tracks=%%H"
		If Not Defined Tracks For /F "delims=" %%H in ("!Track!")Do Endlocal & Set "Tracks=%%H"
So the first line checks to see if the main Tracks variable is not empty. If it isn't empty, this is where I get a little confused. Does the `in ("!Tracks! !Track!")Do` check to see if the current Track in the loop is in the main Tracks variable and if so, it adds Track to the main Tracks variable?

Also on the second line, does this check if the main Tracks variable is empty. And if Tracks is empty, we add Track to the main Tracks variable?

Just want to understand this a bit more. Thanks again for all your help, T3RRY!

Re: Escaping special characters in a variable in for loop

Posted: 11 Jan 2023 08:39
by T3RRY
dosthebos wrote:
10 Jan 2023 16:38
T3RRY wrote:
09 Jan 2023 05:53
Special handling of the environment state is required to preserve Exclamation characters.
Variable assignment must occur before DE is enabled, DE then gets toggled on and of during the loop, with an additional for loop required to tunnel the concatenation variable across the endlocal.
Thanks so much T3RRY for the explanation and the code example.

The second loop is what I was missing. Can you break down the following bit of the code?

Code: Select all

		If Defined Tracks For /F "delims=" %%H in ("!Tracks! !Track!")Do Endlocal & Set "Tracks=%%H"
		If Not Defined Tracks For /F "delims=" %%H in ("!Track!")Do Endlocal & Set "Tracks=%%H"
So the first line checks to see if the main Tracks variable is not empty. If it isn't empty, this is where I get a little confused. Does the `in ("!Tracks! !Track!")Do` check to see if the current Track in the loop is in the main Tracks variable and if so, it adds Track to the main Tracks variable?

Also on the second line, does this check if the main Tracks variable is empty. And if Tracks is empty, we add Track to the main Tracks variable?

Just want to understand this a bit more. Thanks again for all your help, T3RRY!
The reason the If Defined / If not defined tests are perform is to prevent the unintended assignment of leading whitespace to tracks in the event it is not yet defined.

So this:

Code: Select all

If Defined Tracks For /F "delims=" %%H in ("!Tracks! !Track!")Do Endlocal & Set "Tracks=%%H"
Appends track to tracks only if tracks is already defined

And this:

Code: Select all

If Not Defined Tracks For /F "delims=" %%H in ("!Track!")Do Endlocal & Set "Tracks=%%H"
Defines tracks with the content of track in the event tracks is not defined.
The order of these two tests is significant. If the order was reversed, the first track would be assigned to tracks twice.

The for loops themselves are used to expand the Delayed variable/s to be passed across the endlocal barrier each time we toggle Delayed expansion off. You can read more about that here: viewtopic.php?p=27972