Windows batch file - How to shuffle and unshuffle files using variable inputs

Viewed 98

EDIT: Updated to be more clear and to clarify that I only want to shuffle files 001 to 049 and leave file 050 as 050 and added an "Ideal End Goal Concept" at the end of this post

I'm looking for a way to shuffle 49 files in the current directory with my Windows batch file. All of the files are named .001, .002, etc. all the way to .049 using 9 or 10 numbers entered as variables to the batch file. I need to be able to use the same 9 or 10 numbers entered as variables to be able to "unshuffle" the files back to their original state using a seperate batch file. Any ideas and code examples to do this would be greatly appreciated.

Here is my batch file command line example:

I have 50 files in the folder named testfile.exe.001 to .050 (I only want to shuffle 001 - 049)

shuffle.bat testfile.exe 8 5 2 9 4 1 3 7 6

Making sure that I don't use the same number twice and not in the same place (i.e. don't put "1" as the first variable or it will just rename .001 to .001) so that the first 9 files are shuffled.

I then do something like this:

rem temporarily renaming .001 - .050 files to .001a - .050a to prevent collisions
ren %1.0?? %1.0??a
ren %1.001a %1.00%2
rem which renames testfile.exe.001a to testfile.exe.008
ren %1.002a %1.00%3
rem which renames testfile.exe.002a to testfile.exe.005
ren %1.003a %1.00%4
rem which renames testfile.exe.003a to testfile.exe.002
rem etc. all the way up to %1.009a

Then I figured, I could do the same with files .011 - .019, then with files .021 - 0.29, etc. and that works, but it doesn't shuffle the files across the entire span of 49 files. For example .005 could not become .045, etc.

To unshuffle, I would have to enter the same 9 or 10 number variables into the unshuffler batch file which just does everything in reverse. So without those 9 or 10 numbers (which act as a sequence or 'kind of' password), the files could not be restored to their original names.

Is there a way to improve my code or a better way to shuffle these 50 files using batch file variables (numbers) which would cause a shuffle across the entire span of 49 files and be totally reversible using the same 9 or 10 numbers as variables? Thanks for any help!

Ideal End Goal Concept:

To be able to shuffle these 49 files with all of these being true:

  • Ability to input a code on the command line (varible(s)) which uniquely affects and is the key to the shuffling order and allows unshuffling
  • Every file's original order has been changed to another order
  • No two files from the original order are one after the other in the shuffled order
  • All files can be shuffled across the entire 001-049 set (e.g. 005 can become 045, 032 can become 016, etc.)
2 Answers

Your question is not clear. However, I "half-understood" the part with 9 files, so here it is my partial solution.

This is shuffle.bat:

@echo off
setlocal

set "basefile=%1"
set "i=0"
md "renamed"

:loop
shift
if "%1" equ "" goto endLoop

set /A i+=1
move %basefile%.00%i% renamed\%basefile%.00%1 > NUL
goto loop

:endLoop
move renamed\*.* . > NUL
rd renamed

I started with this set of files that have the same number of characters of their names, so we can always identify them (even with the names changed):

09/09/2022  09:36 a. m.                 1 testfile.exe.001
09/09/2022  09:36 a. m.                 2 testfile.exe.002
09/09/2022  09:36 a. m.                 3 testfile.exe.003
09/09/2022  09:36 a. m.                 4 testfile.exe.004
09/09/2022  09:36 a. m.                 5 testfile.exe.005
09/09/2022  09:36 a. m.                 6 testfile.exe.006
09/09/2022  09:36 a. m.                 7 testfile.exe.007
09/09/2022  09:36 a. m.                 8 testfile.exe.008
09/09/2022  09:36 a. m.                 9 testfile.exe.009

After this command is executed:

shuffle.bat testfile.exe 8 5 2 9 4 1 3 7 6

... you get these files:

09/09/2022  09:36 a. m.                 6 testfile.exe.001
09/09/2022  09:36 a. m.                 3 testfile.exe.002
09/09/2022  09:36 a. m.                 7 testfile.exe.003
09/09/2022  09:36 a. m.                 5 testfile.exe.004
09/09/2022  09:36 a. m.                 2 testfile.exe.005
09/09/2022  09:36 a. m.                 9 testfile.exe.006
09/09/2022  09:36 a. m.                 8 testfile.exe.007
09/09/2022  09:36 a. m.                 1 testfile.exe.008
09/09/2022  09:36 a. m.                 4 testfile.exe.009

That is, the first file go to position 8, the second file to position 5, etc. until the ninth file in position 6. Good!

Now, this is unshuffle.bat:

@echo off
setlocal

set "basefile=%1"
set "i=0"
md "renamed"

:loop
shift
if "%1" equ "" goto endLoop

set /A i+=1
move %basefile%.00%1 renamed\%basefile%.00%i% > NUL
goto loop

:endLoop
move renamed\*.* . > NUL
rd renamed

So after this command is executed:

unshuffle.bat testfile.exe 8 5 2 9 4 1 3 7 6

... the files recover their original positions! :)

Whats next?


New method added

I read again the question, particularly the "I could do the same with files .011 - .019, then with files .021 - 0.29, etc." part and I think I have a complete solution now.

I didn't created two .bat files for shuffle/unshuffle operations. Instead, I added the /U switch as the first parameter of the only batch file to select the "unshuffle" task.

I used two different shuffle methods for each group of ten files. Files in 001..009, 021..029 and 041..049 (even) groups use the original method, and files in 011..019 and 031..039 (odd) groups use the "opposite" method. The files 010, 020, 030, 040 and 050 are not renamed.

This is the new shuffle.bat:

@echo off
setlocal

set "unshuffle="
if /I "%1" equ "/U" set "unshuffle=1" & shift

set "basefile=%1"
set "i=0"
md renamed

:loop
shift
if "%1" equ "" goto endLoop

set /A i+=1
set "flipFlop="
(for /L %%j in (0,1,4) do (
   if not defined flipFlop (
      if not defined unshuffle (
         move %basefile%.0%%j%i% renamed\%basefile%.0%%j%1
      ) else (
         move %basefile%.0%%j%1 renamed\%basefile%.0%%j%i%
      )
      set "flipFlop=1"
   ) else (
      if not defined unshuffle (
         move %basefile%.0%%j%1 renamed\%basefile%.0%%j%i%
      ) else (
         move %basefile%.0%%j%i% renamed\%basefile%.0%%j%1
      )
      set "flipFlop="
   )
)) > NUL
goto loop

:endLoop
echo These files were not renamed:
echo/
dir %basefile%.*
move renamed\*.* . > NUL
rd renamed

After this command:

shuffle.bat testfile.exe 8 5 2 9 4 1 3 7 6

... the resulting files are these:

09/09/2022  11:46 a. m.                 6 testfile.exe.001
09/09/2022  11:46 a. m.                 3 testfile.exe.002
09/09/2022  11:46 a. m.                 7 testfile.exe.003
09/09/2022  11:46 a. m.                 5 testfile.exe.004
09/09/2022  11:46 a. m.                 2 testfile.exe.005
09/09/2022  11:46 a. m.                 9 testfile.exe.006
09/09/2022  11:46 a. m.                 8 testfile.exe.007
09/09/2022  11:46 a. m.                 1 testfile.exe.008
09/09/2022  11:46 a. m.                 4 testfile.exe.009
09/09/2022  11:46 a. m.                10 testfile.exe.010
09/09/2022  11:46 a. m.                18 testfile.exe.011
09/09/2022  11:46 a. m.                15 testfile.exe.012
09/09/2022  11:46 a. m.                12 testfile.exe.013
09/09/2022  11:46 a. m.                19 testfile.exe.014
09/09/2022  11:46 a. m.                14 testfile.exe.015
09/09/2022  11:46 a. m.                11 testfile.exe.016
09/09/2022  11:46 a. m.                13 testfile.exe.017
09/09/2022  11:46 a. m.                17 testfile.exe.018
09/09/2022  11:46 a. m.                16 testfile.exe.019
09/09/2022  11:46 a. m.                20 testfile.exe.020
09/09/2022  11:46 a. m.                26 testfile.exe.021

. . .

09/09/2022  11:46 a. m.                44 testfile.exe.049
09/09/2022  11:46 a. m.                50 testfile.exe.050

After this command:

shuffle.bat /U testfile.exe 8 5 2 9 4 1 3 7 6

files recover their original positions.

setlocal enabledelayedexpansion
set "sourcedir=c:\testso"

set "filename=%1"
:: clear any variables starting #
for /f "tokens=1delims==" %%e in ('set # 2^>nul') do set "%%e="

:: number of files
set /a #files=8
set /a #files1k=1000+#files

set "parm= %* "
set /a #count=0
set /a #randomadd=0
call :readparms %parm%

:: if 0 or 1 parameter, shuffle randomly
if %#count% lss 2 goto shuffle

:: unshuffling

echo unshuffling
call :shuffleren
goto :eof


:shuffle
set /a #remain=#files1k
for /L %%e in (1001,1,%#remain%) do set "#%%e="
:another
set /a #choice=((%random%+#randomadd) %% #files) + 1001

if defined #%#choice% goto another
if %#remain%%#choice%==10011001 goto shuffle
if %#choice% equ %#remain% goto another
set /a #%#choice%=%#remain%
if %#remain%==1001 goto doneshuffle
set /a #remain-=1
goto another

:doneshuffle
set "#report="
set #lastnum=0
for /L %%e in (1001,1,%#files1k%) do (
 set /a #num=!#%%e!-1000
 set /a #lastnum+=1
 if !#num!==!#lastnum! goto shuffle
 set /a #lastnum=#num
 set "#report=!#report! !#num!
)
call :shuffleren
echo sequence=%#report:~1%
goto :eof

:shuffleren
for /L %%e in (1001,1,%#files1k%) do (
 set "#oldname=%%e"
 set "#newname=!#%%e!"
 ren "%sourcedir%\%filename%*.!#oldname:~-3!" "%filename%*.!#oldname:~-3!.!#newname:~-3!.t"
)
for %%b in ("%sourcedir%\%filename%*.t") do ren "%%b" "%%~nb"
goto :eof

:readparms
shift
set "parm=%1"
if not defined parm goto :eof
set "parm=9%1"
for /L %%e in (0,1,9) do set "parm=!parm:%%e=!"
if defined parm echo ignored "%1" &goto readparms
set /a #randomadd=%1
set /a parm=1000+%1
set /a #count+=1
set /a #count1k=1000+#count
set /a #%parm%=#count1k

goto readparms

We start with setting the directory to examine and read the filename from the parameter list; then clear any variables starting #.

Establish the number of files to manipulate - I've seen the revised spec. that this should be 49, not 50. I used 8 for testing.

Calculate 1000+#files to save repeatedly calculating. Initialise the count of numeric parameters found and read the parameters from the command line using :readparms. I had some control options to facilitate testing, so added a space either end of the passed parameters list %*.

Having read and stored the parameters,examine #count - 0 or 1 means to shuffle, more means unshuffle.

So, shuffling :

First, set all variables #1001 to #1050 (it's self-adjusting to #files specified) to nothing.

Next, generate a random number 1001..1050 and if it's not already assigned, assign the remaining number from the range 1001..1050 to #the randomnumber and decrement the number remaining until it reaches 1001 (last one assigned). If both #count and #remain are 1001 then we're attempting to rename .001 to .001 so we start again. If #remain and #choice are the same otherwise, choose another number

When we're done shuffling, we need to build the unshuffle string. We grab each number in turn from #1000 to #1050, convert it to a natural number (no leading zeroes) and compare it against the last number processed - if the difference is 1, then we're attempting to resequence 2 sequential filenumbers, and that's not allowed - so restart the shuffling.

Finally, we have a set of variables such as

#1001=1004
#1002=1003
#1003=1002
#1004=1001

which means that file.001 will become .004 ,etc.

So, execute :shuffleren to rename the files.

I chose to rename the files by appending an extra 2 extensions (the new name and a t) then remove the t. This shows a trace of the old and new names (filename.002 becomes filename.002.003 in the above). Changing "%filename%*.!#oldname:~-3!.!#newname:~-3!.t" to "%filename%*.!#newname:~-3!.t"would change filename.002 to filename.003.

Finally, the unshuffle mechanism relies on a string of space-separated numeric arguments that matches the list printed by the shuffle mechanism. It's reaaly just more of the same, but unchecked so providing duplicate or insufficient numbers will no doubt cause a mess. Build a table like the above from the parameters and call the :shuffleren routine to change the numbers back.

Of course, you should test the routine in a dummy area - don't apply it directly to live files.

Since the random function is seeded from the time, I've attempted to apply some variability by adding into the number-choosing mechanism a number that may be specified on the command line, hence 0 or 1 numeric arguments - if one numeric argument is provided, that argument is added to the random number so you can have a little bit of variation if you happen to run the routine at the same time on two days.

---- revision

@echo off
setlocal enabledelayedexpansion
set "sourcedir=c:\testso"

set "filename=%1"
:: clear any variables starting #
for /f "tokens=1delims==" %%e in ('set # 2^>nul') do set "%%e="

:: number of files
set /a #files=50
set /a #files1k=1000+#files
:: length of code to generate
set /a #lencode=10

set "parm= %* "
set /a #count=0
set /a #randomadd=0
set /a #assigned=0
call :readparms %parm%

:: if 0 or 1 parameter, shuffle randomly
if %#count% gtr 2 goto extendkey

:shuffle
set "#shuffle=y"
for /L %%e in (1,1,%#files%) do set "#%%e="
set "#report="
set /a #assigned=0
: nextrandom
call :assignrandom
if %#assigned% lss %#lencode% goto nextrandom
set "#key=%#report%"
:: Extend the key currently in #Report
:extendkey
set /a #accum=1
:nextmore
call :addmore %#report%
if %#assigned% equ %#files% goto donerandom
if %#accum% neq 10 goto nextmore
:: attempt to assign all unassigned
for /L %%e in (%#files,-1,1) do (
 set /a #choice=%%e-1
 call :addreport
)
if %#assigned% neq %#files% if defined #shuffle goto shuffle
if %#assigned% neq %#files% echo Fail&goto :eof
:donerandom
if defined #shuffle echo key is %#key%
: now build rename-list
for /L %%e in (1000,1,%#files1k%) do set "#%%e="
set /a #position=1000
:brlloop
set /a #position+=1
set /a #value=%#report% 2>nul
set /a #value+=1000
if defined #shuffle (set /a #%#position%=#value
 ) else set /a #%#value%=#position
set "#report=%#report:* =%"
if defined #report goto brlloop
:shuffleren
for /L %%e in (1001,1,%#files1k%) do (
 set "#oldname=%%e"
 set "#newname=!#%%e!"
 ren "%sourcedir%\%filename%*.!#oldname:~-3!" "%filename%*.!#oldname:~-3!.!#newname:~-3!.t"
)
for %%b in ("%sourcedir%\%filename%*.t") do ren "%%b" "%%~nb"
goto :eof

:readparms
shift
set "#choice=%1"
if not defined #choice goto :eof
set "#choice=9%1"
for /L %%e in (0,1,9) do set "#choice=!#choice:%%e=!"
if defined #choice echo ignored "%1" &goto readparms
set /a #randomadd=%1
set /a #count+=1
set /a #choice=%1-1
call :addreport

goto readparms

:: Assign a new random number to #report

:assignrandom
set /a #choice=%random%+#randomadd
:: Caution - flow-through

:: Add and count an entry into #report
:: don't add if #choice is already in #report or #assigned

:addreport
set /a #choice=(#choice %% #files) + 1
set /a #assigned+=1
for %%y in (%#assigned% %#report%) do if %#choice%==%%y set /a #assigned-=1&goto :eof
set "#report=%#report%%#choice% "
goto :eof

:: Add some more numbers derived from the existing list

:addmore
set /a #accum+=1
:movel
if "%1"=="" goto :eof
set /a #choice=0
for /L %%y in (1,1,%#accum%) do  call set /a #choice=#choice+%%%%y 2>nul
call :addreport
shift
goto movel

This code limits the "key" to #lencode.

First step is to process the parameters provided, but this time build each parameter into #report (the name is a hangover from earlier processing)

If there are fewer than 2 parameters provided, then this is a suffle operation and #shuffle is set. In this case, #lencode random numbers are added to a new #report. If #shuffle is not set, then the key has been provided in the command line and has been assigned to #report.

Using #report as a basis, a specific function is applied, adding new numbers. This means that the result in #report will be identical for the two cases (shuffling or unshuffling) All that is needed then is to add 1000 to the numbers for substringing purposes and build the #1xxx array; either for shuffling or unshuffling; then run the renaming code as before.

If an incorrect code is supplied, then the unshuffling will cause chaos. The file renaming trace-for-verification system survives from the earlier code.

-- another revision

@echo off
setlocal enabledelayedexpansion
set "sourcedir=c:\testso"

set "filename=%1"
:: clear any variables starting #
for /f "tokens=1delims==" %%e in ('set # 2^>nul') do set "%%e="

:: number of files
set /a #files=50
set /a #files1k=1000+#files
:: length of code to generate
set /a #lencode=10
set "parm= %* "

:: Set flag #shuffle

for %%e in (shuffle) do (
 if "!parm!" neq "!parm:/%%e=!" set "#%%e=Y"&set "parm=!parm:/%%e=!"
)

set /a #count=0
set /a #randomadd=0
set /a #assigned=0
call :readparms %parm%

:: if 0 or 1 parameter, shuffle randomly
if %#count% gtr 2 goto extendkey

:shuffle
set "#shuffle=y"
for /L %%e in (1,1,%#files%) do set "#%%e="
set "#report="
set /a #assigned=0
: nextrandom
call :assignrandom
if %#assigned% lss %#lencode% goto nextrandom
set "#key=%#report%"
:: Extend the key currently in #Report
:extendkey
set /a #accum=1
:nextmore
call :addmore %#report%
if %#assigned% equ %#files% goto donerandom
if %#accum% neq 10 goto nextmore
:: attempt to assign all unassigned
for /L %%e in (%#files,-1,1) do (
 set /a #choice=%%e-1
 call :addreport
)
if %#assigned% neq %#files% if defined #shuffle goto shuffle
if %#assigned% neq %#files% echo Fail&goto :eof
:donerandom
if defined #shuffle if defined #key echo key is %#key%
: now build rename-list
for /L %%e in (1000,1,%#files1k%) do set "#%%e="
set /a #position=1000
:brlloop
set /a #position+=1
set /a #value=%#report% 2>nul
set /a #value+=1000
if defined #shuffle (set /a #%#position%=#value
 ) else set /a #%#value%=#position
set "#report=%#report:* =%"
if defined #report goto brlloop
:shuffleren
for /L %%e in (1001,1,%#files1k%) do (
 set "#oldname=%%e"
 set "#newname=!#%%e!"
 ren "%sourcedir%\%filename%*.!#oldname:~-3!" "%filename%*.!#oldname:~-3!.!#newname:~-3!.t"
)
for %%b in ("%sourcedir%\%filename%*.t") do ren "%%b" "%%~nb"
goto :eof

:readparms
shift
set "#choice=%1"
if not defined #choice goto :eof
set "#choice=9%1"
for /L %%e in (0,1,9) do set "#choice=!#choice:%%e=!"
if defined #choice echo ignored "%1" &goto readparms
set /a #randomadd=%1
set /a #count+=1
set /a #choice=%1-1
call :addreport

goto readparms

:: Assign a new random number to #report

:assignrandom
set /a #choice=%random%+#randomadd
:: Caution - flow-through

:: Add and count an entry into #report
:: don't add if #choice is already in #report or #assigned

:addreport
set /a #choice=(#choice %% #files) + 1
set /a #assigned+=1
for %%y in (%#assigned% %#report%) do if %#choice%==%%y set /a #assigned-=1&goto :eof
set "#report=%#report%%#choice% "
goto :eof

:: Add some more numbers derived from the existing list

:addmore
set /a #accum+=1
:movel
if "%1"=="" goto :eof
set /a #choice=0
for /L %%y in (1,1,%#accum%) do  call set /a #choice=#choice+%%%%y 2>nul
call :addreport
shift
goto movel

This time, you need to add a switch /shuffle if you are providing a key and wish to shuffle. Without the /shuffle switch, specifying fewer than 2 numeric arguments will shuffle randomly, more than 2, unshuffle.

Note that #key will only be established for a random shuffle, so that is the only time it's reported (otherwise, the key was provided or it's unshuffling).

Related