Batch Randomize Playlist Containing Percent Symbols - batch-file

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

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.

Related

Batch program - delete all dividing symbols in numbers

I have a homework task which needed to be done using just batch script. I need to rewrite all the numbers in .txt file if they have dividing symbols . or , but those strings may contain both words and numbers. Also the result should stay in the same file.
For example:
Lorem ipsum dolor 12.3254
2556,4646 ex commodo
would become
Lorem ipsum dolor 123254
25564646 ex commodo
I started with some code that looks like this:
#echo off
setlocal EnableDelayedExpansion
SET verfile=%1
FOR /f "tokens=* delims= " %%A IN (%verfile%) DO (
SET "Var=%%A"
FOR /l %%i IN (0, 1, 9) DO (
echo !Var! | findstr "."
IF %ERRORLEVEL% EQU 0 (
)
)
And now I have no idea how to continue it.
Can you help me please?
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
rem The following settings for the source directory and filename are names
rem that I use for testing and deliberately include names which include spaces to make sure
rem that the process works using such names. These will need to be changed to suit your situation.
SET "sourcedir=u:\your files"
SET "filename1=%sourcedir%\q73553463.txt"
:: remove variables starting #
FOR /F "delims==" %%b In ('set # 2^>Nul') DO SET "%%b="
SET /a linecount=0
:: Read entire file into memory
FOR /f "usebackqdelims=" %%e IN ("%filename1%") DO (
rem next line number
SET /a linecount +=1
rem record in memory
SET "#!linecount!=%%e"
)
:: process each line removing [,.] following a digit
:: and report to original file
(
FOR /L %%e IN (1,1,%linecount%) DO (
FOR %%c IN ("." ",") DO FOR /L %%y IN (0,1,9) DO SET "#%%e=!#%%e:%%y%%~c=%%y!"
ECHO !#%%e!
)
)>"%filename1%"
TYPE "%filename1%"
GOTO :EOF
The set # command will generate a list like
#whatever=something
#whateverelse=somethingelse
for all variables that are currently set and start #.BUT it would be unusual to have any variable set that starts # so set would generate an error. The 2^>nul sends any error-report (on standard device stderr, which is device 2) to nul that is, nowhere. The caret ^ is required because cmd needs to distinguish between a part of the set command and a part of the for.
The for/f...%%b using delims of = generates "tokens" from the list generated by the set. Tokens are separated by any sequence of any of the delimiters specified between = and ", and by default, "token1" is selected, so the strings applied to %%b are
#whatever
#whateverelse
and these variables need to be set to nothing.
See for /? from the prompt for documentation on for or browse thousands of examples on SO.
Use set "var=value" for setting string values - this avoids problems caused by trailing spaces which can cause chaos. Once bitten, twice shy.
Then we read the file. Using "delims=" sets no delimiters, hence the whole line forms "token 1" which is assigned to %%e. The usebackq changes the meaning of " so that a double-quoted filename may be used. The filename I've used includes a Space but if there are no separator characters in the name, the quotes and usebackq can be omitted (again, ref for/?)
Then add 1 to the linecount and record the line in memory by assigning it to #!linecount!. The !linecount! is required because linecount is varying within the block (parenthesised sequence of lines) - and with delayedexpansion enabled, %linecount% yields the value when the block was encountered, and !linecount! the run-time or instantaneous value - as it changes within the block.
Stephan's DELAYEDEXPANSION link
So - having established #1, #2..#%linecount% with the lines from the file, we can process those variables and produce a replacement file.
Note that there is a block formed by ( for...%%e...)>filename. This allows any echoed data within the block to be redirected to the file. > redirects to a new version whereas >> would append to any existing file.
So - we iterate through all of the #linenumber lines using a for /L on %%e. For each of these, we set %%c to "." and %%y to 0 to 9 and then replace any string %%y%%c with %%y (3. gets replaced by 3 for example). Then repeat with %%c set to ",". set /? provides documentation and browsing SO examples.
But Woah, Neddie! There's a little trick here. , is a list-separator so (. ,) won't work - it will be treated as (.). Using the quotes allows cmd to read each character separately, and we need to use %%~c instead of %%c to dump the quotes.
So - take a look around. You can do a lot with batch if you're devious enough. And no doubt you'll be challenged if you present this solution. Be ready to explain it. A really good way to follow what's happening is to turn echo on and watch the magic step-by-step. Use pause liberally and perhaps remove the >"%filename1%" to prevent the report going to the file while you're observing what's happening.
#echo off
setlocal enabledelayedexpansion
set "verfile=%~1"
echo before:
type "%verfile%"
for /f "usebackq tokens=*" %%a in ("%verfile%") do (
set strline=%%a
set strline=!strline:.=!
set strline=!strline:,=!
echo !strline!>>"%verfile%.tmp"
)
echo.
echo after:
type "%verfile%.tmp"
del /f /q "%verfile%.tmp"

Storing several paths as strings inside an array in cmd

I wrote the following batch file:
setlocal EnableDelayedExpansion
set integer=0
for /f "delims=" %%a in ('where /R C:\ /F python.exe') do (
set Paths[!integer!]=%%a
set /A integer+=1
)
for %%b in (%Paths%) do (
echo %%b
)
The idea is that I want to run where /R C:\ /F python.exe and then store the several paths outputted into an array, I need this because I need to handle these paths later on. So far for testing purposes I called the second half of the script just to make sure the array is working properly.
Your second part is wrong. Since there are no real arrays in batch scripting, you cannot use a for loop similar to something like for each element in array do as used in other languages. Refer to this post for details: Arrays, linked lists and other data structures in cmd.exe (batch) script.
To loop through all (pseudo-)array elements you could do the following instead:
set /A count=integer-1
for /L %%i in (0,1,%count%) do echo Paths[%%i] = !Paths[%%i]!
As suggested by Aacini in a comment, you might change your base-0 to a base-1 (pseudo-)array by moving the line set /A integer+=1 upwards to be before line set Paths[!integer!]=%%a in your first part, so the variable integer already contains the greatest index, which would lead to a slightly simpler second part:
for /L %%i in (1,1,%integer%) do echo Paths[%%i] = !Paths[%%i]!
Alternatively, you could use a for /F loop that parses the output of set Paths[:
for /F "tokens=1* delims==" %%i in ('set Paths[ 2^> nul') do echo %%i = %%j
This might be beneficial:
you do not need to know the total number of elements;
the indexes do not have to be adjacent, they could even be non-numeric;
But there are two caveats:
the (pseudo-)array elements are not returned in ascending order with respect to the indexes, because set Paths[ returns them alphabetically sorted, where the order is 0, 1, 10, 11,..., 2, etc., so the order becomes jumbled as soon as there are indexes with more than a single digit;
in case there exist other variables beginning with that name, like Paths[invalid] or Paths[none, they become returned too;

Avoid a null value in last for loop iteration?

I have a text file with one string per line (only a couple lines total). This file needs to be searched and each string stored. The script will ultimately prompt the user to choose one of the stored strings (if more than one string/line is present in the file), but the for loop is iterating an extra time when I don't want it to.
I'm using a counter to check the number of iterations, and I also read that it's probably a NL CR regex issue, but the finstr /v /r /c:"^$" in the for file-set like in this post Batch file for loop appears to be running one extra time/iteration doesn't work for me (but I probably don't understand it correctly).
The "pref" term is because the strings are to be eventually used as a prefix of files in the same folder...
#echo off
setlocal enabledelayedexpansion
set /a x=1
for /f %%a in (sys.txt) do (
set pref!x!=%%a && echo %^%pref!X!%^% && set /a x+=1
)
echo last value of x = !x!
for /L %%a in (1,1,!x!) do (
echo !pref%%a!
)
REM The rest would be to prompt user to choose one (if multiple) and
REM then use choice as a prefix with a ren %%a %prefX%%%a
If the "sys.txt" contains three lines with strings A, B, C respectively, then the output I currently get is:
pref1
pref2
pref3
last value of x = 4
A
B
C
ECHO is off.
ECHO is off. is not desired, clearly.
You just need to change your increment structure like this. (set it before each line starting from a base of 0)
#Echo Off
Setlocal EnableDelayedExpansion
Set "i=0"
For /F "UseBackQ Delims=" %%A In ("sys.txt") Do (
Set/A "i+=1"
Set "pref!i!=%%A"
Echo(pref!i!)
Echo(last value of i = %i%
For /L %%A in (1,1,%i%) Do Echo(!pref%%A!

Not able to print the variable in windows batch file that stores an extracted value from CSV file

Below is my code and i am not able to print the variable in windows batch file that stores an extracted value from CSV file
#echo off
Set _InputFile=D:\TH_Scripts\InputParamTest.csv
for /F "usebackq tokens=* delims=" %%A in (%_InputFile%) do (
set the_line=%%A
goto process_line
)
:process_line
echo i am here
pause
for /F "usebackq tokens=1,2,3,4,5,6,7 delims=[,]" %%1 in (%the_line%) do (
set hexcode=%%1
set country=%%2
set reg=%%3
set owner=%%4
set callsign=%%5
set planetype=%%6
set model=%%7
set THISLINE=%hexcode%,%country%,%reg%,%owner%,%callsign%,%planetype%,%model%
echo %THISLINE% > %THEOUTPUTFILE%
pause
)
for /F "usebackq tokens=1,2,3,4,5,6,7 delims=[,]" %%1 in (%the_line%) do (
%%number is not supported by batch syntax. %n means "the nth parameter to the procedure".
change %%1 to %%a. The variables assigned are then %%a, %%b etc. to %%g note case is important. %%a can be %%i if you like - the values extracted are then assigned to %%i..%%o
2.
You need to search for innumerable articles on delayed expansion on SO. The value of %var% within a code block (parenthesised series of instructions) will be the value that the variable had when the code block was encountered - not the value as it is varied in the block (the "run-time" value)
To extract the run-time value, you need to first invoke delayedexpansion mode by executing a setlocal enabledelayedexpansion instruction (usually done directly after the initial #echo off) and then access the run-time value of the variable by using !var!.
That having been said, unless you are actually using the variables you are establishing in the block, you can directly output your list using %%a, etc, not %hexcode% or !hexcode!. Without further information about how you intend to use these variables elsewhere, this may or may not be useful in your case.
BTW - 1,2,3... is not incorrect syntax, but 1-7 is shorter and means the same in this context.

How to change an image tag url in multiple html files using batch script?

There are more than 10 html files with image tags. Every time we deploy our build onto test site we need to change the img source. for eg <img src=/live/Content/xyz.png />
to <img src=/test/Content/xyz.png />.
After looking around and reading for sometime, i have come up with the following batch script, however i cant figure out how do i go further from here :
for /r %%i in (*.html) do echo %%i
for %%f in (*.html) do (
FOR /F %%L IN (%%f) DO (
SET "line=%%L"
SETLOCAL ENABLEDELAYEDEXPANSION
SET "x= <--------------------WHAT DO I SET HERE?
echo %x%
ENDLOCAL )) pause
This is my first batch script, could anyone please guide me in the right direction?
#ECHO OFF
SETLOCAL enabledelayedexpansion
for /r U:\ %%i in (*.html) do (
echo found %%i
SET outfile="%%~dpni.lmth"
(
SETLOCAL disabledelayedexpansion
FOR /F "usebackq delims=" %%L IN ("%%i") DO (
SET "line=%%L"
SETLOCAL ENABLEDELAYEDEXPANSION
SET "line=!line:/live/=/test/!
echo !line!
ENDLOCAL
)
ENDLOCAL
)>!outfile!
)
pause
GOTO :EOF
How about this development?
Notes:
I've modified your FOR/R to ECHO the HTML file being processed and use %%i rather than switching to %%f. U: is my RAMDRIVE; you'd need to modify that to suit.
outfile is set to generate a filename which matches the HTML filename, but with a .lmth extension (can't update in-place) - it gets that from the ~dpn prefixing the i, which means the drive, path and name of the file %%i. It's quoted to take care of potential spaces in the filename or pathname.
The next logical statement is (for /f...[lines] )>!outfile! which sends any echoed text to a NEW file !outfile!. The enabledelayedexpansion in the second physical line of the batch makes !outfile! the RUN-TIME value - as it is changed within the FOR r outer loop.
Since the actual HTML filename in %%i may contain spaces, it needs to be quoted, hence the 'usebackq' clause in the FOR/F. The delims= clause ensures that the ENTIRE line from the file "%%i" is applied to %%L - not just the first token (well, actually, makes the entire line the first token).
The SET command substitutes the string "/test/" for any occurrence of "/live/" in the RUN-TIME value of the variable lineand assigns the result to line. The resultant value is then ECHOd - which is redirected to outfile
Note that in your original, you would be assigning x in the set x= but echo %x% would have reproduced x as it stood when the line was PARSED because batch substitutes the value of any variable for %var% as part of the parsing phase. Consequently, the line would have become simply ECHO (since x would likely be unassigned) and bizarrely would have reported the echo state (Echo is OFF)
A couple of gatchas here. First, % and some other characters are notoriously hard to process with batch, so be careful. Next, FOR/F will bypass empty lines. This can be overcome if required. Third, this will replace ANY occurrence of /live/ in any case with /test/
Good luck!
Edit to support exclamation marks: 20130711T0624Z
Added SETLOCAL enabledelayedexpansion line and ENDLOCAL just before )>!outfile! to match

Resources