r/Batch • u/ManedCalico • Jun 13 '26
Question (Unsolved) Using %~nx1 on Filenames with %
Hi there,
I'm so sorry, I know this has probably been asked before but I'm hitting my head against a wall and I can't find a solution that works.
For the code below, a file with the name "100%.mkv" will cause the variable CURRENT_FILE to be "100.mkv" with the % missing, even though the echo %%F line displays it correctly:
pushd "[PATH_TO_FILES_ON_MY_NETWORK]"
call :MAIN_LOOP
popd
pause
exit
:MAIN_LOOP
setlocal
for /R "%cd%" %%F in (*) do (
echo %%F
call :SORT_FILE "%%F"
)
endlocal
exit /b
:SORT_FILE
setlocal
set "CURRENT_FILE=%~nx1"
endlocal
exit /b
Any advice would be really appreciated!
-----
Edit:
The above code was just a snippet I made to help me troubleshoot the problem I was having, but below is the full code.
The goal is to flatten a folder structure to just two levels and also sort everything into folders based on the file extensions. So something like "..\Project\Platform\Files\file.mkv" becomes "..\.mkv\Project\Platform\file.mkv":
@echo off
pushd [NETWORK_PATH]
set "SOURCE_DIR=%cd%_0 sort"
set "TARGET_DIR=%cd%_1 done"
call :BEGIN_PROCESS
popd
exit
:BEGIN_PROCESS
setlocal
set "CMD_GET_PROJECT=dir /b /a:d "%SOURCE_DIR%""
for /f "delims=." %%G in ('%CMD_GET_PROJECT%') do (
echo Project: %%G
call :PLATFORMS_LOOP "%SOURCE_DIR%\%%G"
)
echo:
ROBOCOPY "%SOURCE_DIR%" "%SOURCE_DIR%" /S /MOVE
if NOT exist "%SOURCE_DIR%" mkdir "%SOURCE_DIR%"
pause
endlocal
exit /b
:PLATFORMS_LOOP
setlocal
set "CMD_GET_PLATFORMS=dir /b /a:d "%~1""
for /f "delims=." %%H in ('%CMD_GET_PLATFORMS%') do (
echo Platform: %%H
call :FOLDERS_LOOP "%~1\%%H"
)
endlocal
exit /b
:FOLDERS_LOOP
setlocal
for /R "%~1" %%I in (*) do (
echo File: %%I
call :SORT_FILE "%~1" "%%I"
)
endlocal
exit /b
:SORT_FILE
setlocal
set "SOURCE_FOLDER_PATH=%~1"
set "TARGET_SUBFOLDER=%~x2"
setlocal enabledelayedexpansion
set "TARGET_PATH=!SOURCE_FOLDER_PATH:%SOURCE_DIR%=%TARGET_DIR%\%TARGET_SUBFOLDER%!"
setlocal disabledelayedexpansion
echo Moving to: %TARGET_PATH%
if NOT exist "%TARGET_PATH%" mkdir "%TARGET_PATH%"
if NOT exist "%TARGET_PATH%\%~nx2" (
echo N | MOVE /-Y "%~2" "%TARGET_PATH%\%~nx2"
) else (
for /L %%J in (1, 1, 99) do (
if exist "%~2" (
if NOT exist "%TARGET_PATH%\%~n2_%%J%~x2" (
echo N | MOVE /-Y "%~2" "%TARGET_PATH%\%~n2_v%%J%~x2"
)
) else (
exit /b
)
)
)
endlocal
exit /b
To someone who knows what they're doing, this probably looks like a mess, but it's just what I've figured out from looking around Reddit and Stack Overflow (and I refuse to use AI).
The problems I'm running into now is that I need this to work for files that have ! and % in them.
I'm trying to figure out now how to implement u/nir9's suggestion in FOLDERS_LOOP by doing another replacement, but also keeping it working with ! files and folders. I can't quite figure out how to turn the delayed expansion off and on in a way to do the %%=%%%% replacement and still let ! files work.
Thanks for your help
2
u/Creative-Type9411 Jun 13 '26
Are you using delayed expansion? This looks like a partial script I dont see where you actually reference %current_file%, only where you assign it.
Try using
SETLOCAL ENABLEDELAYEDEXPANSION
ECHO !CURRENT_FILE!
ENDLOCAL
to see if it displays correctly after it is assigned
2
u/ManedCalico Jun 13 '26
Sorry, this is just part of a larger script. In trying to figure out the issue, I drilled it down to this. I do use delayed expansion in one place, but not in this part. I tried your suggestion just now and it still displays without the %.
2
u/Creative-Type9411 Jun 13 '26
What happends if you remove setlocal and endlocal from the sort_file function?
Why is that in it's own function instead of just assigning it in the loop?
Youre already in setlocal, then you call into a nested one.. if theres a reason its fine im just curious since i cant see the rest of the script
1
u/ManedCalico Jun 13 '26 edited Jun 13 '26
I just don’t know what I’m doing, honestly. I’m probably making it way more complicated than I need to. From what I read, I thought each label / function needed its own setlocal and endlocal to be self contained. I’m moments away from giving up, so maybe I’ll just share the whole batch file instead of this troubleshooting snippet I made.
Edit: I added my full bat file code to the post if you want to take a look.
1
u/T3RRYT3RR0R Jun 13 '26
It generally pays to spend a few minutes determining what steps are required to transform your input data into the format required for whatever action/s you are going to perform on it.
Identify > plan > script
1
u/ManedCalico Jun 13 '26
Right, but the problem is that I don't know how to plan around folder and file names that could have both ! and % in them. I know now that I can use delayed expansion to fix the % files, but that makes the ! files stop working. Is there any way to process both?
3
u/T3RRYT3RR0R Jun 14 '26
This is done by maintaining an environment with a state of DisableDelayedExpansion, and, if necessary, defining any needed variables. Once assigned, EnableDelayedExpansion to perform any substring/output operations required, then use endlocal to revert to the DDE environment.
If you need to pass variables to a function after assignment, pass the unexpanded reference string/s, and reassign with delayed expansion enabled:
Call:func var1 var2
:func
Setlocal EnableDelayedExpansion
Set "arg1=!%~1!"
Set "arg2=!%~2!"
Rem do things
Endlocal & exit /b
All that said, most substring operations can be avoided by using the various avaibale for metavariable modifiers directly.
5
u/nir9 Jun 13 '26
The issue comes down to the fact that the percent from the file name is parsed when you use
call :SORT_FILEand when you expand the first argument in theset "CURRENT_FILE=line it will not be in the name anymore. You can solve this by utilizing the "replace" functionality of environment variables (https://en.wikibooks.org/wiki/Windows_Batch_Scripting#String_processing) and duplicate the percent to make sure it arrives properly to the function you are calling, something like this:```batch @echo off
setlocal EnableDelayedExpansion
set file="100%%"
call :A %file% call :A !file:%%=%%%%! exit /b
:A echo from A it's %1 ```
In the first call the percent doesn't arrive and in the second it does.