Windows 7 batchfile to iterate through subdirectories selecting first file - windows

I am running windows 7 64 bit
I have a directory, full of sub directories, each subdirectory has many files.
I want to write a batchfile which will iterate through the subdirectories in the current directory, copy the first file in each subdirectory to the parent directory, renaming the file .txt
so far I have this:
`for /D %%s in (./*) do
(
cd %%s
i=1
for /f %%a in (./*) do
(
if i==1
cp %%a >> ./../%%s.txt
i=i+1
else
)
cd ..
)`
All I am getting is that the syntax of the command is incorrect.
I have tried using echos to see how far I get and I am not getting past the first line.

Batch syntax is very specific.
for... do (
whatever
if ... (
whatever
) else (
whatever
The open-parentheses must be in the position indicated. whatever may be on the following line or the same line as the (
If you have a sequence of statements (for an if-true condition, for example) then these must be parenthesised
The close-parenthesis before an else must be on the samp physical line as the else.
cp is invalid - unless you're using cygwin, the command is copy
i=i+1 is invalid - use set /a i=i+1 (/a for arithmetic expressions, else it's assumed to be s string expression)
%var% for the value of a variable. i will never be equal to 1 except within a block statement (parenthesised series of statements) then you need to invoke delayedexpansion (an option of setlocal) and use !var! as %var% is resolved to be the value of var at the time the expression is parsed, not executed.
/ is a switch indicator, not a directory separator. \ is a directory-separator. Windows can often make the substitution, but not when there is a syntax ambuity. Best to use the correct character.
"quote" arguments that may contain separators (like spaces.) It's not strictly necessary in a cd statement, but it's harmless there and is required almost everywhere else.
setlocal enabledelayedexpansion
for /D %%s in (.\*) do (
cd "%%s"
set /a i=1
for /f %%a in (.\*) do (
if !i!==1 (
cOpY %%a >> ".\..\%%s.txt"
set /a i=i+1
) else ( )
cd ..
)
Although... I'd be tempted to use PUSHD "%%s" in place of the first cd and popd for the second.
Not sure what your copy is aimed at achieving. To append to an existing file, I'd use type. There is a copy syntax that implements append this file to that but I use it so rarely I'd have to look it up.
commandname /?
from the prompt will provide (sometimes obscure) documentation.

I think this way is simpler and easier to understand:
#echo off
setlocal EnableDelayedExpansion
for /D %%s in (*) do (
set "firstFile="
for %%a in ("%%s\*.*") do if not defined firstFile set "firstFile=%%a"
copy "!firstFile!" "%%s.txt"
)
An observation on previous code:
The .\anyName although correct, is usually never used. A dot indicate "current folder" and .\anyName indicate the anyName below current folder, that is exactly the same than just anyName.
EDIT: New method to copy the Nth file
#echo off
setlocal EnableDelayedExpansion
for /D %%s in (*) do (
set i=0
for %%a in ("%%s\*.*") do (
set /A i+=1
if !i! equ N copy "%%a" "%%s.txt"
)
)
You must write a number in place of N above.

Related

Move files batch doesn't work with double click but works from command line

I have some files
afjj.txt
agk.png
beta.tt
Ritj.rar
I use this script to move files in alphabetically order into autogenerated folders
a
|
|----> afjj.txt
|----> agk.png
b
|----> beta.tt
R
|----> Ritj.rar
To do I use this code
#echo off
setlocal
for %%i in (*) do (
set name=%%i
setlocal enabledelayedexpansion
if not "!name!" == "%0" (
set first=!name:~0,1!
md !first! 2>nul
if not "!first!" == "!name!" move "!name!" "!first!\!name!"
)
)
What is problem? If I double-click on this batch, batch doesn't work, not move.
But this batch works from command line.
Why?
NOTE: I use Windows Server 2016
P.S: from command line I use this command and works but not if I double click directly on .bat
move.bat "Z:\# 2020\Alfa\test"
The first mistake is naming the batch file move.bat. It is never a good idea to give a batch file the name of a Windows command because hat cause usually troubles. See also: SS64.com - A-Z index of Windows CMD commands.
The second mistake is using setlocal enabledelayedexpansion inside the loop without a corresponding endlocal also within same loop executed as often as setlocal. Please read this answer for details about the commands SETLOCAL and ENDLOCAL. The command SETLOCAL pushes several data on stack on every iteration of the loop and the data are never popped from stack in same loop on each loop iteration. The result is sooner or later a stack overflow depending on the number of files to process as more and more data are pushed on stack.
The third mistake is the expectation that the current directory is always the directory of the batch file. This expectation is quite often not fulfilled.
The fourth mistake is using a loop to iterate over a list of files which permanently changes on each execution of the commands in body of FOR loop. The loop in code in question works usually on storage media with NTFS as file system, but does not work on storage media using FAT32 or exFAT as file system.
The fifth mistake is the expectation that %0 expands always to name of the currently executed batch file with file extension, but without file path which is not true if the batch file is executed with full qualified file name (drive + path + name + extension), or with just file name without file extension, or using a relative path.
The sixth mistake is not enclosing the folder name on creation of the subfolder in double quotes which is problematic on file name starting unusually with an ampersand.
The seventh mistake is not taking into account to handle correct file names starting with a dot like .htaccess in which case the second character must be used as name for the subfolder, except the second character is also a dot. It is very uncommon, but also possible that file name starts with one or more spaces. In this case also the first none space character of file name should be used as Windows by default prevents the creation of a folder of which name is just a space character.
The solution is using following commented batch file with name MoveToFolders.cmd or MyMove.bat.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem Get folder path of batch file assigned to an environment
rem variable. This folder path ends always with a backslash.
set "FilesFolder=%~dp0"
rem Optionally support calling of this batch file with another folder
rem path without checking if the passed string is really a folder path.
if not "%~1" == "" set "FilesFolder=%~1"
rem Replace all / by \ as very often people use / as directory separator
rem which is wrong because the directory separator is \ on Windows.
set "FilesFolder=%FilesFolder:/=\%"
rem The folder path should end always with a backslash even on folder
rem path is passed as an argument to the batch file on calling it.
if not "%FilesFolder:~-1%" == "\" set "FilesFolder=%FilesFolder%\"
rem Get a list of files in specified folder including hidden files loaded
rem into the memory of running command process which does not change on
rem the iterations of the loop below. Then iterate over the files list and
rem move the files to a subfolder with first none dot and none space character
rem of file name as folder name with the exception of the running batch file.
for /F "eol=| delims=" %%i in ('dir "%FilesFolder%" /A-D /B 2^>nul') do if /I not "%FilesFolder%%%i" == "%~f0" (
set "FileName=%%i"
set "FirstPart="
for /F "eol=| delims=. " %%j in ("%%i") do set "FirstPart=%%j"
if defined FirstPart (
setlocal EnableDelayedExpansion
set "TargetFolderName=%FilesFolder%!FirstPart:~0,1!"
md "!TargetFolderName!" 2>nul
if exist "!TargetFolderName!\" move "%FilesFolder%!FileName!" "!TargetFolderName!\"
endlocal
)
)
rem Restore the previous execution environment.
endlocal
The batch file can be started also with a folder path as argument to process the files in this folder without checking if the passed argument string is really referencing an existing folder.
Please read very carefully the answers on How to replace a string with a substring when there are parentheses in the string if there is interest on how to verify if a passed argument string really references an existing folder.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
call /?
dir /?
echo /?
endlocal /?
for /?
if /?
md /?
move /?
rem /?
set /?
setlocal /?
Here is a slightly amended script. Notice I removed the !first! variable as it is not required. I also built in a safety measure, if it is unable to pushd to the given directory you passed to %~1 it will not continue the move. Else it might move files in a path you do not want it to move files. i.e the working dir you started the script in.
#echo off
setlocal enabledelayedexpansion
pushd "%~1" && echo( || goto :end
for %%i in (*.test) do (
set "name=%%~ni"
if not "%%~nxi" == "%~nx0" (md !name:~0,1!)2>nul
if not "!name:~0,1!" == "%%~i" move "%%~i" "!name:~0,1!\%%~i"
)
popd
goto :eof
:end
echo The directory you entered does not exist. Exited script..
pause
Note, with the above you can also drag a directory to the batch file, which will process that directory.
Or if you plan on double clicking, without parameters from cmd.
#echo off
setlocal enabledelayedexpansion
for %%i in (*.test) do (
set "name=%%~ni"
if not "%%~nxi" == "%~nx0" (md !name:~0,1!)2>nul
if not "!name:~0,1!" == "%%~i" move "%%~i" "!name:~0,1!\%%~i"
)
pause
A slightly different take.
This script will work on the current directory if double clicked, or run at the command line without a path specified.
But it will also allow you to provide a path to it as well.
#( SETLOCAL ENABLEDELAYEDEXPANSION
ECHO OFF
SET "_PATH=%~1"
IF NOT DEFINED _PATH SET "_PATH=%CD%"
)
CALL :Main
( Endlocal
Exit /B )
:Main
For /F "Tokens=*" %%_ in ('
DIR /B/A-D-S-H-L "%path%"
') DO (
IF /I "%%_" NEQ "%~nx0" (
SET "_TmpName=%%_"
IF NOT EXIST "%_Path%\!_TmpName:~0:1!\" MD "%_Path%\!_TmpName:~0:1!\"
MOVE /Y "%_Path%\!_TmpName!" "%_Path%\!_TmpName:~0:1!\!!_TmpName!"
)
)
GOTO :EOF
Based upon only filenames beginning with alphabetic characters, here's a somewhat simpler methodology:
#Echo Off
SetLocal EnableExtensions
PushD "%~1" 2> NUL
If /I "%~dp0" == "%CD%\" (Set "Exclusion=%~nx0") Else Set "Exclusion="
For %%G In (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) Do (
%SystemRoot%\System32\Robocopy.exe . %%G "%%G*" /Mov /XF "%Exclusion%" 1> NUL 2>&1
RD %%G 2> NUL
)
PopD

Can someone please tell me what's wrong with this FOR loop?

Apologies if duplicate, but no other answers so far have helped.
All I'm trying to do is loop through the files in a folder, and rename the last part of the file/extension.
Simply put - there could be 1-90 files, [filename]_01 - [filename]_90, and each day (via windows event scheduler) the number has to increment by one.
Nothing I do seems to achieve this.
The files are also meant to behave slightly differently when they hit certain milestones (30-60-90) but this I believe should already work if the variables update properly.
I have tried so many possible combinations of variable addressing (!variable!/%variable%/etc.) and while I can enter the loop, it does not repeat, nor update the variable number for the end of the files.
SetLocal EnableDelayedExpansion
set cnt=0
for %%A in (*) do set /a cnt+=1
set /A fileNumber = %cnt%-1
set /A newFileNumber = %cnt%
echo %fileNumber%
echo %newFileNumber%
for /l %%F in (%fileNumber%,1,1) do (
if %newFileNumber%==90 (
ren "*_%fileNumber%.don" "*_%newFileNumber%.csv"
)
if %newFileNumber%==60 (
ren "*_%fileNumber%.don" "*_%newFileNumber%.csv"
)
if %newFileNumber%==60 (
"ren *_%fileNumber%.don" "*_%newFileNumber%.csv"
)
ren "*_%fileNumber%.don" "*_%newFileNumber%.don"
set fileNumber=%fileNumber%-1
set newFileNumber=%newFileNumber%-1
)
This should simply update all the files in the directory to increment by 1 in the file name. If anyone can point out where I'm going wrong I would really appreciate it.
#ECHO OFF
SetLocal EnableDelayedExpansion
:: target directory name in variable for convenience.
SET "targetdir=U:\sourcedir"
:: switch to target directory
pushD "%targetdir%"
:: Start count at 100 so that it is 3 digits long
set cnt=100
dir
for %%A in (*) do set /a cnt+=1
set /A fileNumber = %cnt%
set /A newFileNumber = %cnt%+1
:: echoing last 2 characters (will be digits) of variables
echo %fileNumber:~-2% (%filenumber%)
echo %newFileNumber:~-2% (%newfilenumber%)
:: Assign %%F to values 100+actual descending by 1 to 101
for /l %%F in (%fileNumber%,-1,100) do (
rem note need REM remarks within the loop
REM use !varname! for current value of variable VARNAME
if !newFileNumber:~-2!==90 (
ECHO ren "*_!fileNumber:~-2!.don" "*_!newFileNumber:~-2!.csv"
)
if !newFileNumber:~-2!==60 (
ECHO ren "*_!fileNumber:~-2!.don" "*_!newFileNumber:~-2!.csv"
)
if !newFileNumber:~-2!==30 (
ECHO ren "*_!fileNumber:~-2!.don" "*_!newFileNumber:~-2!.csv"
)
rem Since you've just renamed (eg) _59.don to _60.csv, _59.don is missing
IF EXIST "*_!fileNumber:~-2!.don" ECHO ren "*_!fileNumber:~-2!.don" "*_!newFileNumber:~-2!.don"
set /A fileNumber=fileNumber-1
set /A newFileNumber=newFileNumber-1
)
:: return to original directory
popd
GOTO :EOF
The required REN commands are merely ECHOed for testing purposes. After you've verified that the commands are correct, change ECHO REN to REN to actually rename the files.
Unfortunately, you've shown us HOW you've NOT been able to do some operation that's a little vague. We thus need to nut out what you intend to do, which is more work and prone to chasing wild geese.
You don't say what to do with files *_90, so we can't help there.
You appear to want to change *_59.don to *_60.csv, but your rename wants to change *_60.DON the next invocation, so unless the name has been changed from _*60.CSV back to *_60.DON, this isn't going to work.
Note that the basis of this routine is to work with the last two characters of variables. This is to accommodate the leading 0 you say is in your numbering scheme.
It's standard practice to assume that you will exercise any routine against a test directory for verification.
Note that every REN is ECHOed, so it is not EXECUTED, merely reported. Change the ECHO REN to REN to actually execute the command.
Note also that batch is largely case-insensitive. This means you don't have to wear out your SHIFT key unless you want to.
To do math you have to use /A with SET
SET x=1
SET /A x=%x%+1
ECHO %x%
The /A switch specifies that the string to the right of the equal sign
is a numerical expression that is evaluated. The expression evaluator
is pretty simple and supports the following operations, in decreasing
order of precedence:
For nested variables you need to use ! instead of %
SetLocal EnableDelayedExpansion
Setlocal EnableDelayedExpansion
for /f %%G in ("abc") do (
set _demo=%%G & echo !_demo!
)

How to copy a file depending on file size comparison?

There are the subdirectories S and F containing files with the same names but different file sizes (< 2 MB).
I want to copy a file from S to F, exactly if the file from S is smaller than the file from F.
FOR /R %%F IN ( >>THE_FILES_IN_S<< ) DO (
set fileS="S/%%~nF"
FOR /F "usebackq" %%A IN ('%fileS%') DO set sizeS=%%~zA
set fileF="F/%%~nF"
FOR /F "usebackq" %%A IN ('%fileF%') DO set sizeF=%%~zA
if %sizeS% LSS %sizeF% (copy /V /Y %fileS% %fileF%)
)
The code above
does not work because >>THE_FILES_IN_S<< is pseudo-code.
What is the right expression?
Are there other mistakes (and what is the correct form)?
The 32bit signed integer size limitation of ~2GB does not apply
when comparing numbers in strings left padded with zeroes to equal length.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "BaseFolder=C:\Temp"
for %%S in ("%BaseFolder%\S\*") do (
if exist "%BaseFolder%\F\%%~nxS" (
for %%F in ("%BaseFolder%\F\%%~nxS"
) do Call :Check %%~zS %%~zF || copy /Y "%%S" "%%F" >nul
)
)
endlocal
Goto :Eof
:Check
Set "S=00000000000000000000%1"
Set "F=00000000000000000000%2"
If %S:~-20% LSS %F:~-20% exit /B 1
The above batch avoids delayed expansion by passing the sizes to a subroutine and comparing strings with 20 decimal places returning an errorlevel on a less result to copy on this fail condition.
The usage of environment variables defined/modified within a command block and referenced in same command block would require the usage of delayed expansion. The help of command SET output on running in a command prompt window set /? explains usage of delayed expansion on an IF and a FOR example.
Best is avoiding usage of delayed expansion by using the loop variables directly instead of assigning their values to environment variables and next reference the values of the environment variables in a command block starting with ( and ending with matching ).
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "BaseFolder=C:\Temp"
for %%S in ("%BaseFolder%\S\*") do (
if exist "%BaseFolder%\F\%%~nxS" (
for %%F in ("%BaseFolder%\F\%%~nxS") do (
if %%~zS LSS %%~zF copy /Y "%%S" "%%F" >nul
)
)
)
endlocal
Replace C:\Temp in third line by real path to base folder. If the batch file is stored in base folder, the third line could be replaced by:
rem Base folder is the directory containing the batch file.
set "BaseFolder=%~dp0"
rem Remove the backslash at end from batch file path.
set "BaseFolder=%BaseFolder:~0,-1%"
The outer FOR searches for any non hidden file in subdirectory S of base folder matching the wildcard pattern * (any name) and assigns the full qualified file name to loop variable S.
If a file with same file name and file extension exists also in subdirectory F of base folder, one more FOR loop is executed which just assigns the already known full qualified file name of current file in subdirectory F to loop variable F and then runs one more IF comparison.
The inner IF compares with a 32-bit signed integer comparison the file sizes of the two files and copies a smaller file in subdirectory S to subdirectory F with suppressing the success message output by COPY to handle STDOUT by redirecting it to device NUL.
Please note that this batch file works only for files with less than 2 GiB because of 32-bit signed integer limitation of Windows command processor on processing integer values.
The entire batch code above could be written also as a single command line:
#for %%S in ("C:\Temp\S\*") do #if exist "C:\Temp\F\%%~nxS" for %%F in ("C:\Temp\F\%%~nxS") do #if %%~zS LSS %%~zF copy /Y "%%S" "%%F" >nul
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
call /? ... explains %~dp0 (drive and path of argument 0 – the batch file)
copy /?
echo /?
endlocal /? ... is not really needed here and could be removed.
for /?
if /?
rem /?
set /?
setlocal /? ... is not really needed here and could be removed.

Subtract 1 from FOR loop index in Windows batch

In Windows batch, I have a for loop like so:
for /l %%a in (0,1,337) do (
for /F "tokens=*" %%b IN ("tile%%a.jpg") DO set size=%%~zb
if !size! GTR 0 (
echo Size is greater than 0
) ELSE (
)
)
I know this code doesn't make much sense right now, but I'm going to develop it further. I just want to know how to subtract 1 from %%a in the ELSE statement. Basically I want to be able to "redo" a loop number when the IF isn't true, if that makes sense. Thanks.
You can't modify the value of a loop variable. You can only modify the value of an environment variable.
But why using for /L %%a in (0,1,337) do at all?
Better would be for example:
#echo off
for %%A in (tile*.jpg) do (
if %%~zA == 0 (
echo File size of %%A is 0 bytes.
) else (
echo File size of %%A is greater than 0.
)
)
This loop processes simply all tile*.jpg in current directory.
But this loop can't be used if files with 0 bytes are deleted in current directory. Processing the list of tile*.jpg files in current directory and change the files list in the same loop is no good idea because simply not working. The solution is using command DIR to get first the list of all files matching the file name pattern and next process the output of DIR line by line using FOR.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
for /F "delims=" %%A in ('dir /A-D /B /OS tile*.jpg 2^>nul') do (
if %%~zA == 0 (
echo File size of %%A is 0 bytes.
) else (
echo First file with more than 0 bytes is: %%A
goto ExitLoop
)
)
:ExitLoop
endlocal
The command DIR is executed to output the list of files matching the pattern tile*.jpg with ignoring directories which by chance would be matched also by this wildcard pattern because of option /A-D in bare format (only file name) because option /B in order sorted by file size because of option /OS from smallest to largest file.
2^>nul redirects the error message output by command DIR to handle STDERR on not finding any file matching the wildcard pattern to device NUL to suppress this error message. The redirection operator > must be escaped here with caret character ^ to be interpreted as literal character on parsing the FOR command line and interpreted as redirection operator on execution of DIR command line by FOR.
The loop is immediately exited once a file with more than 0 bytes is found as all further files have surely also more than 0 bytes.
One more loop can be used after label ExitLoop which should be renamed to something more suitable in this case for example to renumber the remaining files using command REN when first loop deletes files with 0 bytes.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
dir /?
echo /?
endlocal /?
for /?
goto /?
if /?
setlocal /?
See also the Microsoft article Using command redirection operators for an explanation of 2>nul.
You cannot modify the loop variable %%a. Only the loop itself can modify it.
If you want to calculate a new value you can do:
set /A NEW_VALUE=%%a-1
echo %NEW_VALUE% (prove that NewValue is now 1 smaller than %%a)
You cannot modify a for variable reference like %%a, but you can store its value into a standard environment variable (like index) and modify this. For this to work you need to enable and use delayed expansion, because the variable is modified and read within the same block of code, namely the loop body, so read it like !index!; using normal expansion like %index% returned the value present before the loop has even started:
#echo off
setlocal EnableDelayedExpansion
for /L %%a in (0,1,337) do (
set /A "index=%%a-1"
echo %%a - 1 = !index!
)
endlocal
A nice alternative that avoids need of delayed expansion is to use an embedded for /F loop that gets the output of the subtraction and iterates once only per iteration of the surrounding for /L loop, like this:
#echo off
for /L %%a in (0,1,337) do (
for /F %%b in ('set /A "%%a-1"') do (
echo %%a - 1 = %%b
)
)
This works because the for /F loop executes the set /A command in cmd context, in which it returns the resulting value -- in contrast to the aforementioned approach, where set /A is executed in batch-file context, in which it does not output anything.

Find files and sort by size in a Windows batch file

I have as command-line parameters to my batch script a list of filenames and a folder. For each filename, I need to print all subfolders of the folder where the file is found (the path of that file). The subfolder names should be sorted in descending order of the file sizes (the file can have various sizes in different subfolders).
I have done this so far, but it doesn't work:
::verify if the first parameter is the directory
#echo off
REM check the numbers of parameters
if "%2"=="" goto err1
REM check: is first parameter a directory?
if NOT EXIST %1\NUL goto err2
set d=%1
shift
REM iterate the rest of the parameters
for %%i in %dir do (
find %dir /name %i > temp
if EXIST du /b temp | cut /f 1 goto err3
myvar=TYPE temp
echo "file " %i "is in: "
for %%j in %myvar do
echo %j
echo after sort
du /b %myvar | sort /nr
)
:err1
echo Two parameters are necessary
goto end
:err2
echo First parameter must be a directory.
goto end
:err3
echo file does not exist.
goto end
:end
I don't feel guilty answering this homework question now that the semester is long past. Print folders and files recursively using Windows Batch is a closed duplicate question that discusses the assignment.
My initial solution is fairly straight forward. There are a few tricks to make sure it properly handles paths with special characters in them, but nothing too fancy. The only other trick is left padding the file size with spaces so that SORT works properly.
Just as in the original question, the 1st parameter should be a folder path (.\ works just fine), and subsequent arguments represent file names (wildcards are OK).
#echo off
setlocal disableDelayedExpansion
set tempfile="%temp%\_mysort%random%.txt"
set "root="
for %%F in (%*) do (
if not defined root (
pushd %%F || exit /b
set root=1
) else (
echo(
echo %%~nxF
echo --------------------------------------------
(
#echo off
for /f "eol=: delims=" %%A in ('dir /s /b "%%~nxF"') do (
set "mypath=%%~dpA"
set "size= %%~zA"
setlocal enableDelayedExpansion
set "size=!size:~-12!"
echo !size! !mypath!
endlocal
)
) >%tempfile%
sort /r %tempfile%
)
)
if exist %tempfile% del %tempfile%
if defined root popd
I had hoped to avoid creation of a temporary file by replacing the redirect and subsequent sort with a pipe directly to sort. But this does not work. (see my related question: Why does delayed expansion fail when inside a piped block of code?)
My first solution works well, except there is the potential for duplicate output depending on what input is provided. I decided I would write a version that weeds out duplicate file reports.
The basic premise was simple - save all output to one temp file with the file name added to the front of the sorted strings. Then I need to loop through the results and only print information when the file and/or the path changes.
The last loop is the tricky part, because file names can contain special characters like ! ^ & and % that can cause problems depending on what type of expansion is used. I need to set and compare variables within a loop, which usually requires delayed expansion. But delayed expansion causes problems with FOR variable expansion when ! is found. I can avoid delayed expansion by calling outside the loop, but then the FOR variables become unavailable. I can pass the variables as arguments to a CALLed routine without delayed expansion, but then I run into problems with % ^ and &. I can play games with SETLOCAL/ENDLOCAL, but then I need to worry about passing values across the ENDLOCAL barrier, which requires a fairly complex escape process. The problem becomes a big vicious circle.
One other self imposed constraint is I don't want to enclose the file and path output in quotes, so that means I must use delayed expansion, FOR variables, or escaped values.
I found an interesting solution that exploits an odd feature of FOR variables.
Normally the scope of FOR variables is strictly within the loop. If you CALL outside the loop, then the FOR variable values are no longer available. But if you then issue a FOR statement in the called procedure - the caller FOR variables become visible again! Problem solved!
#echo off
setlocal disableDelayedExpansion
set tempfile="%temp%\_mysort%random%.txt"
if exist %tempfile% del %tempfile%
set "root="
(
for %%F in (%*) do (
if not defined root (
pushd %%F || exit /b
set root=1
) else (
set "file=%%~nxF"
for /f "eol=: delims=" %%A in ('dir /s /b "%%~nxF"') do (
set "mypath=%%~dpA"
set "size= %%~zA"
setlocal enableDelayedExpansion
set "size=!size:~-12!"
echo(!file!/!size!/!mypath!
endlocal
)
)
)
)>%tempfile%
set "file="
set "mypath="
for /f "tokens=1-3 eol=/ delims=/" %%A in ('sort /r %tempfile%') do call :proc
if exist %tempfile% del %tempfile%
if defined root popd
exit /b
:proc
for %%Z in (1) do (
if "%file%" neq "%%A" (
set "file=%%A"
set "mypath="
echo(
echo %%A
echo --------------------------------------------
)
)
for %%Z in (1) do (
if "%mypath%" neq "%%C" (
set "mypath=%%C"
echo %%B %%C
)
)
exit /b

Resources