1

I have the following code (source identified) which is great, but it fails when a title contains a percent symbol. I like the fact that it doesn't use temp files and allows me to overwrite the original file. Any thoughts on how to fix this for percent symbols?

  REM Randomize the Onesies playlist
  REM Source: stackoverflow.com/questions/19393155
  REM DOES NOT PROPERLY CREATE PLAYLIST ENTRY FOR ALBUMS CONTAINING PERCENT SYMBOLS (but it's fast)

  for /f "delims=" %%a in (G:\Playlists\Onesies.m3u8) do call set "$$%%random%%=%%a"
  (for /f "tokens=1,* delims==" %%a in ('set $$') do echo(%%b)>G:\Playlists\Onesies.m3u8
  echo Playlist Onesies.m3u8 has been created in the folder G:\Playlists\
  echo.
  pause
RKO
  • 161
  • 10
  • Possible duplicate of [How to randomly rearrange lines in a text file using a batch file](http://stackoverflow.com/q/19393155) – aschipfl Aug 18 '16 at 01:44

1 Answers1

3

The problematic part is call set "$$%%random%%=%%a". To explain why, let us start from the beginning.

Simply using set "$$%random%=%%a" would not work, because %random% would be expanded (read) when the entire for /F loop, or more generally spoken, the whole block of code, is parsed, so the same value would be returned for every loop iteration. The call trick, toghether with doubled percent signs around variables, introduces another parsing phase, so when the entire for /F loop is parsed, call set "$$%%random%%=%%a" is interpreted as call set "$$%random%=%a", because each %% in a batch file is interpreted as one literal %. Hence the random variable is not yet expanded.

For every loop iteration, the for /F variable becomes replaced by the current iteration value. call initiates another parsing phase as already mentioned, so %random% becomes replaced by a individual random number per loop iteration. Assuming that %a is abritrary string, the line is interpreted as call set "$$%random%=arbitrary string" first, and after the second parsing phase it becomes set "$$16384=arbitrary string", supposing %random% returns the number 16384.

Now let us take another example value for %a which contains a percent sign, like string with % sign: after the first parsing phase, we have call set "$$%random%=string with % sign"; after the second one, we get something like set "$$32767=string with sign". The percent sign disappears here, because the command line interpreter encounters a single % sign that cannot be paired with another one (where a variable name is expected in between them, like %random%), so it simply dismisses that character. In case %a contained two % signs, for instance, more % signs % here, the result would be more here (most likely), because SPACEsignsSPACE, being in between a pair of % signs, would be treated as the name of a (undefined, hence empty) variable.

To solve that issue, we need to avoid %a to be passed through a second parsing phase. This can be achieved by avoiding %%a to appear in the call command line.


So to overcome the loss of % signs in your script, you need to store %%a into an interim variable and to do the same double-% expansion within the call command line like you already do for random:

@echo off
setlocal EnableExtensions DisableDelayedExpansion

set /A "index=0"
for /f "usebackq delims=" %%a in ("G:\Playlists\Onesies.m3u8") do (
    set "item=%%a"
    call set "$$%%random%%.%%index%%=%%item%%"
    set /A "index+=1"
)
> "G:\Playlists\Onesies.m3u8" (
    for /f "tokens=1,* delims==" %%a in ('set $$') do echo(%%b
)
echo Playlist "Onesies.m3u8" has been created in the folder "G:\Playlists\".
echo/

endlocal
pause

An alternative method is to apply delayed expansion:

@echo off
setlocal EnableExtensions DisableDelayedExpansion

rem // Toggling delayed expansion in the loop is necessary to not lose exclamation marks.
set /A "index=0"
for /f "usebackq delims=" %%a in ("G:\Playlists\Onesies.m3u8") do (
    setlocal EnableDelayedExpansion
    rem // This loop passes `random` over the `endlocal` barrier:
    for /f %%b in ("!random!.!index!") do (
        endlocal
        set "$$%%b=%%a"
    )
    set /A "index+=1"
)
> "G:\Playlists\Onesies.m3u8" (
    for /f "tokens=1,* delims==" %%a in ('set $$') do echo(%%b
)
echo Playlist "Onesies.m3u8" has been created in the folder "G:\Playlists\".
echo/

endlocal
pause

The original code from the question relies on the built-in random number variable random which may return duplicate values. This leads to the problem that some lines of the read file get lost.

To resolve this issue, both of the above approaches herein feature a counter called index which is of course unique for every single line that is read by the for /F loop. This counter value is appended to the random value so that the total concatenated string is unique. Therefore, none of the lines of the read file are lost any more, opposed to the original script.

aschipfl
  • 33,626
  • 12
  • 54
  • 99
  • Thanks but your code doesn't work. It gives a single line of output (the last line of the text file). I tried several similar approaches and it seems as though the call statement was crucial for getting a full set of output. – RKO Aug 17 '16 at 23:27
  • Sorry, I was answering too quick! The code is fixed now. Note that `random` might return duplicate values (also for the original code you posted in your question), leading to lost lines from the original input file; as soon as I find a solution, I will fix that as well. – aschipfl Aug 18 '16 at 00:24
  • Yes you're right. Good catch. I didn't notice the original was losing lines due to duplicate values. And yes your code now works for percent symbols. I look forward to your even better fix! – RKO Aug 18 '16 at 13:50
  • I just [fixed the scripts](http://stackoverflow.com/revisions/39006130/5) so that duplicate `random` value do no longer cause lines to be lost. Take also a look at [this answer](http://stackoverflow.com/a/39008715) to the question I linked [above](http://stackoverflow.com/questions/39005490/batch-randomize-playlist-containing-percent-symbols/39006130#comment65369147_39005490)... – aschipfl Aug 18 '16 at 14:06
  • Works great. Thanks. Quite a clever answer. – RKO Aug 18 '16 at 15:08
  • I had a chance to look at this closer. It works great but I'm curious as to why set "item=%%a" call set "$$%%random%%.%%index%%=%%item%%" preserves the percent symbols? call set "$$%%random%%.%%index%%=%%a" doesn't work. – RKO Aug 18 '16 at 18:20
  • [This great post](http://stackoverflow.com/a/4095133) perfectly explains why your method failed. I will update my answer and include an explanation... – aschipfl Aug 18 '16 at 22:27
  • I get it. Thanks. Clears up a long standing mystery for me. Great post. – RKO Aug 19 '16 at 15:21