I have an array in a batch file which contains several file directories. I would like to remove the first three elements of the array and have everything shift over (i.e. the fourth element takes the place of the first, the fifth element takes the place of the second, sixth takes the place of the third, etc). Is this something that can be done in a Batch script?
Example:
[directoryA, directoryB, directoryC, directoryD, directoryE, directoryF]
is changed to:
[directoryD, directoryE, directoryF]
Here is the code I have for the "array":
set paramCount=0
for %%x in (%*) do (
set /A paramCount+=1
set "dirs[!paramCount!]=%%x"
)
The best was I can think of to do this is to have two "arrays", one for the data, another for whether the data is currently valid. The sample code below creates one dirs "array" holding any command line parameters and another valid "array" and then prints only "valid" data from dirs.
valid[i] will be equal to 1 if and only if the data in dirs[i] is valid. Any other value in valid[i] will indicate that the data in dirs[i] is to be ignored.
#echo off
setlocal enabledelayedexpansion
set paramCount=0
for %%x in (%*) do (
echo %%x
set /A paramCount+=1
REM Add data to dirs array
set "dirs[!paramCount!]=%%x"
REM Add valid flag to valid array
set "valid[!paramCount!]=1"
)
echo Printing...
REM Delete element at index 2
set valid[2]=0
REM Print the modified array
for /L %%a in (1 1 %paramCount%) do (
IF !valid[%%a]!==1 (echo !dirs[%%a]!)
)
Example Output:
$>deletetest.bat 1 2 3 4
1
2
3
4
Printing...
1
3
4
Excuse me. I would like to state some aclarations about this topic.
You have not indicated where your "array" come from. You may store it in a variable, for example:
set dirs=directoryA directoryB directoryC directoryD directoryE directoryF
However, this is not an array, but a list. You may create this list from the parameters of a subroutine this way:
set dirs=%*
If you want to remove the first three elements from this list, you may do that this way:
for /F "tokens=3*" %%a in ("%dirs%") do set dirs=%%b
Another possibility is that the directories were passed to a subroutine as a parameters list:
call :subroutine directoryA directoryB directoryC directoryD directoryE directoryF
I think this is the case based on your example. In this case it is very easy to "remove" the first three parameters via three shift commands:
:subroutine
rem Remove first three parameters:
shift
shift
shift
rem Process the rest of parameters:
:nextParam
if "%1" equ "" goto endParams
echo Next param is: %1
shift
goto nextParam
:endParams
However, if you have a "real" array (with numeric subscripts that start at 1) that may also be created from the parameters list for a subroutine this way (like in your example):
set paramCount=0
for %%x in (%*) do (
set /A paramCount+=1
set "dirs[!paramCount!]=%%x"
)
Then you may remove the first three elements this way:
for /L %%i in (4,1,%paramCount%) do (
set /A j=%%i-3
set dirs[!j!]=!dirs[%%i]!
)
set /A paramCount-=3
Related
I need to create an integer array that stops at a limit number and then goes on after another fixed value. Something like [1 2 3 4 5 10 11 12 13 14 15 16], where 5 is the limit number and 10 where it restarts.
Can you do something like set arr = [1:%lim%, 10:%max%] ?
#ECHO OFF
SETLOCAL
CALL :buildlist mylist 1 5 10 16
ECHO list is %mylist%
PAUSE
GOTO :eof
:: Produce a list of integers
:: %1 is listname
:: %2 is start value
:: %3 is end-value for range
:: %4 is restart-value
:: %5 is end-value
:buildlist
SET "%1="
FOR /L %%v IN (%2,1,%5) DO (
IF %%v leq %3 CALL SET "%1=%%%1%% %%v"
IF %%v geq %4 CALL SET "%1=%%%1%% %%v"
)
CALL SET "%1=%%%1:~1%%"
GOTO :eof
Batch really doesn't have arrays, but can simulate one with a little imagination.
The above routine produces a list of values to the specification, ready for processing by a for statement.
Although it's possible to use delayedexpansion, it's possible to avoid that facility. By CALL ing a set command, the command is parsed before execution so to decode the hieroglyphics, where %1 is the variable name "var"
SET "%1=%%%1%% %%v"
is processed as:
SET "var=%var% %v"
as %% is an escaped-%, and %v will be replaced by the value of the metavariable (loop-control variable) %%v
Similarly,
SET "%1=%%%1:~1%%"
is executed as
SET "var=%var:~1%"
which deletes the first character, which will be a space.
This problem is about several for /L commands really, although it could be described in terms of nested if commands or in other different ways...
There are several different methods to give the values of the for commands, and also to implement they. I think this is the simplest one:
#echo off
setlocal EnableDelayedExpansion
set ranges="1:5" "10:16"
set "list="
for %%a in (%ranges::= 1 %) do for /L %%i in (%%~a) do set "list=!list! %%i"
echo list=%list%
Note that this method also works with several (more than two) ranges of values.
PS - This result is a list, not an array
Batch doesn't have OR operators, so you'll have to make do with two separate if statements - one for the first set of numbers and one for the second set of numbers. You can, however, put both of those in the same for loop:
#echo off
setlocal enabledelayedexpansion
set "lim=5"
set "max=10"
REM Setting counter to -1 because we're going to increment the counter and then
REM use it, and arrays famously start at zero.
set "counter=-1"
for /L %%A in (1,1,16) do (
if %%A LEQ %lim% (
set /a counter+=1
set "array[!counter!]=%%A"
)
if %%A GEQ %max% (
set /a counter+=1
set "array[!counter!]=%%A"
)
)
REM Display the contents of the array just to prove it worked.
REM Alphabetic order is used, so array[10] and array[11] are going to print before array[1]
set array[
I've mentioned constant index in the title because all question in StackOverflow related to array indexing in a batch file were focused on accessing array using variable index inside a loop.
I'm new to batch scripting. I want to print array value with a constant index if the array is initialized as a list(in one line) rather than each element being initialized individually. I've written a snippet in which I can print the value of arr but not list.
#echo off
set arr[0]=1
set arr[1]=2
set arr[2]=3
set list=1 2 3 4
REM Result is 2
echo %arr[1]%
REM Won't print
echo %list[1]%
A list in a string isn't an array, this answer 2 days ago shows how to turn a string list into an array.
To have the array index zero based use this changed version
:: SO_51225079.cmd
#echo off & Setlocal EnableDelayedExpansion
set arr[0]=1
set arr[1]=2
set arr[2]=3
set i=-1&set "list= 1 2 3 4"
Set "list=%list: ="&Set /a i+=1&Set "list[!i!]=%"
set list
REM Result is 2
echo %arr[1]%
REM Won't print
echo %list[1]%
Sample output:
> SO_51225079.cmd
list[0]=1
list[1]=2
list[2]=3
list[3]=4
2
2
You might be confusing Batch and PowerShell. In PowerShell, yes, you can initialize an array on one line:
$list = 1, 2, 3, 4
$list[1]
# output here would be 2
In the Batch scripting language, there are no array objects. You can simulate arrays by having similarly or sequentially named scalar variables, but the Batch language doesn't provide methods such as split() or splice() or push() or similar.
Often in Batch, splitting a string on spaces (or commas or semicolons) is accomplished by tokenizing using a for loop.
#echo off & setlocal
rem // Quoting "varname=val" is the safest way to set a scalar variable
set "list=1 2 3 4"
rem // When performing arithmetic using "set /a", spacing is more flexible.
set /a ubound = -1
rem // Split %list% by tokenizing using a for loop
for %%I in (%list%) do (
set /a ubound += 1
rem // Use "call set... %%ubound%%" to avoid evaluating %ubound% prematurely.
rem // Otherwise, %ubound% is expanded when the for loop is reached and keeps the
rem // same value on every loop iteration.
call set "arr[%%ubound%%]=%%~I"
)
rem // output results
set arr[
setlocal enabledelayedexpansion
rem // You can also loop from 0..%ubound% using for /L
for /L %%I in (0, 1, %ubound%) do echo Element %%I: !arr[%%I]!
endlocal
As a side note, in that code block I demonstrated two methods of delaying expansion of variables in Batch -- using call set and setlocal enabledelayedexpansion using exclamation marks where delayed retrieval is desired. There are times when it's useful to know both. The enabledelayedexpansion method is usually more readable / more easily maintained, but in some circumstances can clobber values where exclamation marks possibly exist (such as file names). For that reason, I try to avoid enabling delayed expansion for the entire script.
LotPings' answer is clever, but limited in application. The way it works is, using substring substitution it sets and evaluates the value of list to a string of commands (separated by &). Unfortunately, it destroys the value of %list% in the process, and it cannot handle values containing spaces or exclamation marks.
#echo off & setlocal
set "list="The quick brown" "fox jumps over" "the lazy dog!!!""
set /a ubound = -1
for %%I in (%list%) do (
set /a ubound += 1
call set "arr[%%ubound%%]=%%~I"
)
rem // output results
set arr[
The for method of splitting will correctly maintain quoted spaces.
Batch has only one type of variable: string.
An array and a list are very different things (and it is discussed, if those even exist in batch, but they can be emulated). In batch, a list isn't different elements, but just a single string, and an array isn't a single structure, but different independend variables.
Nevertheless, you can split a string (that looks like a list) by delimiters (space is a default delimter) with a for loop:
set list=a b c d
set element=2
for /f "tokens=%element%" %%a in ("%list%") do echo %%a
(Note: this is batch syntax. For use directly on command line, replace each %%a with %a)
I am using a for loop to create textfiles with predefined headers, but I want to append a unique string (alice, bob, etc.) on the 2nd line for each file from a predetermined list.
Set a=alice
Set b=bob
Set c=chris
Rem ...etc. (I have about 30 files with a unique name to go in row 2 of each file)
For /l %%x in (1, 1 , 30) do (echo headerRow1 > file%%x.txt & echo %a% > file%%x.txt)
I dont understand how to automatically insert "bob" for the second iteration (ie. In "file2.txt") and "chris" for the third iteration, and continue for 30 files.
Do I somehow loop the %a% part to increment by one each time. What should I name the string variables to do this? Are these even the right questions?
Thanks for looking.
It seems that the problem you're trying to solve is to be able to iterate between two lists in one for loop, when the for loop can only iterate through one set at a time. The problem is harder in that arrays and lists don't really exist in Batch as first-class citizens.
There are multiple ways of solving this, but one simpler way is to loop through the names, and increment a counter each time through the loop. (This is probably easier than a for /l loop and incrementing the list of names.)
Here is an example.
#echo off & setlocal enabledelayedexpansion
set names=alice bob chris
set x=1
for %%a in (%names%) do (
>file!x!.txt (
echo headerRow
echo %%a
)
set /a x+=1
)
Notes:
Set all your names on a single line, and a for loop can iterate through them.
Set your counter (x) at the start, then increment it at the end of the loop.
I'm using delayed expansion (setlocal enabledelayedexpansion) and the syntax of !x! instead of %x% so that Batch resolves the variable x each time through the loop, instead of once (and only once) the first time it goes through, so that it gets the right incremented value each time.
I hope that helps.
#ECHO Off
SETLOCAL
SET "sourcedir=U:\sourcedir"
SET "targetdir=U:\destdir"
SET "filename1=%sourcedir%\q40171884.txt"
SET "headerrow=This is your header"
SET "line2=This is line two for "
FOR /f "tokens=1*delims=[]" %%a IN ('find /n /v "" "%filename1%"') DO IF "%%b" neq "" (
>"%targetdir%\file%%a.txt" (
ECHO(%headerrow%
ECHO(%line2%%%b
TYPE "%sourcedir%\thefilecontainingtheremainder.txt"
)
)
GOTO :EOF
You would need to change the settings of sourcedir and targetdir to suit your circumstances.
I used a file named q40171884.txt containing your names for my testing.
Uses the file "%sourcedir%\thefilecontainingtheremainder.txt" to contain the body of the data to be assigned to the output file. Naturally, this could be anywhere.
Essentially, take the filename containing your names, and use find /n /v "" to prefix each line with [sequencenumber]. Using delims of [], assign the prefix to %%a and the remainder of the line (the name) to %%b.
The first line output by find will report the filename, but won't contain [] (unless you get silly and engineer it that way), so %%b in that case will be empty. We skip that line, and for the others, %%a contains the number and %%b the name.
Then create new output files "filenumber.txt" and the two special lines - the second with %%b (the name) appended. Then regurgitate the remainder of the required text by simply typeing it.
I am trying to create some sort of handler for arguments passed into my script. The idea is to handle "pairs" of arguments together. So, that is to say 1 and 2, 3 and 4, 5 and 6, and so on... (NOT 2 and 3, 4 and 5, 6 and 7, and so on...)
I think batch is very cool, but I am very new to batch. I am lost as how to do it. So far I have managed to successfully put the arguments into an array with the following script:
SETLOCAL EnableDelayedExpansion
set /a count+=1
set "params[%count%]=%~1"
shift
if defined params[%count%] (
goto :repeat
) else (
set /a count-=1
)
Now I just need to handle each of the elements in pairs. I don't know how to do this at all. I know how to handle the elements individually with for /F "tokens=2 delims==" %%s in ('set params[') do, but I don't know how to do pairs
on the side can someone explain what the delayed expansion thing is supposed to do?
just for clarity, by "handle" I mean to manipulate it and pass it on as an argument onto another script. this must be done in pairs.
#ECHO Off
SETLOCAL ENABLEDELAYEDEXPANSION
:: remove variables starting $ or #
For %%b IN ($ #) DO FOR /F "delims==" %%a In ('set %%b 2^>Nul') DO SET "%%a="
SET /a count=0
SET "select=this is select's original value"
ECHO parameters received = %*
:loop
REM SET $... and #... to the parameter-pair.
SET $%count%=%1
SET #%count%=%2
IF NOT DEFINED $%count% GOTO process
IF NOT DEFINED #%count% ECHO "Incomplete pair"&GOTO :EOF
SET /a count +=1
:: shift twice to move 2 parameters
shift&shift&GOTO loop
:process
ECHO %count% pairs found
SET $
SET #
FOR /L %%a IN (1,1,%count%) DO (
REM note that this changes "select"
SET /a select=%%a - 1
CALL SET /a item1=%%$!select!%%
CALL SET /a item2=%%#!select!%%
ECHO pair %%a is !item1! and !item2! from $!select! and #!select! NOT $#%select%
)
ECHO ----------------- select = %select% ----------
SET /a count-=1
FOR /L %%a IN (0,1,%count%) DO (
REM note that this changes "select"
SET /a select=%%a + 1
ECHO pair !select! is !$%%a! and !#%%a! from $%%a and #%%a NOT $#%select%
GOTO :EOF
First, set up some initial values. The first for ensures that any variables starting $ or # are cleared.
Set $0 and #0 to the first and second parameters. If $0 is not set, then we have no further data, so process what we have. If $0 is set but #0 is not, we don't have a pair so complain and terminate.
If both #0 ans $0 are set, increment count, shift the parameters twice and run around the loop. The next pair will be placed in $/#1 and so on until the parameters are exhausted.
Now show the count of parameters and the $ and # values that have been set.
Next loop show unnecessary manipulation of the variables.
First set select to 1 less than the loop-counter.
then use a parsing trick to get the value of $[current value of select]. The line translates to "SET /a item1=%$n%" where n is the run-time value of select - ie. the value as it changes through the loop.
then show the results. item1 is changed within the block (parenthesised series of statements) so we need !item1 to access that changed value - %item1% would give us the value as it was when the loop was parsed. You can see that from the report for select on the same line.
Last loop is the same idea - but varying %%a from 0 to (count-1). Observe that select in the dashed echo line has the last value it was assigned in the previous loop.
So, once again we change select within the block, so we see a difference between !select! and %select%. We can also access $0 directly using !$%%a! - that is the current value of the variable [$ strung with the current value of the loop-control %%a]
#echo off
setlocal EnableDelayedExpansion
rem Store arguments one by one
set "count=0"
:repeat
set /A count+=1
set "param[%count%]=%~1"
shift
if "%~1" neq "" goto repeat
rem Show arguments in pairs
set /A countMIN1=count-1
for /L %%i in (1,2,%countMIN1%) do (
set /A j=%%i+1
for %%j in (!j!) do echo !param[%%i]! !param[%%j]!
)
For further details, see: Arrays, linked lists and other data structures in cmd.exe (batch) script
Hi Can anyone help me out in this problem.
I need to create multiple file?? i give with any example. In some folder, say Folder Records.
"Record" folder contain 1 file by name "example2tought1023.au" . i need to generate same file contains, multiple time just by increasing the numbers.
i should get result like this example2tought1023.au example3tought1024.au example4tought1025.au example5tought1026.au
This is what I currently have:
SET count=9
SET filename_1=example
SET filename_2=thought
SET extension=.au
SET start_1=2
SET start_2=1023
SET source=%filename_1%%start_1%%filename_2%%start_2%%extension%
FOR /L %%i IN (1, 1, %count%) DO (
REM These two lines do not work!
SET /a n=%start_1%+%%i
SET /a number_2=%start_2% + %%i
SET destination=%filename_1%%number_1%%filename_2%%number_2%%extension%
ECHO %destination%
REM COPY %source% %destination%
)
PAUSE
but the lines in the FOR /L loop do not work
You have mis-identified which lines are not working :-)
The problem you are having relates to when variables are expanded. Normal expansion using percents occurs when the line is parsed, and your entire FOR statement, including the parenthesised DO clause, is parsed in one go. So the following line
SET destination=%filename_1%%number_1%%filename_2%%number_2%%extension%
is seeing the values of %number_1% and %number_2% that existed before the loop was executed. Obviously not what you want. The solution is simple - you need to use delayed expansion (the value at run time instead of parse time). You do that by 1st enabling delayed expansion using setlocal enableDelayedExpansion, and then use !number_1! instead of %number_1%.
You are not consistent with your variable names (n vs number_1).
I think you want to count from 0 to count-1 instead of from 1 to count.
You do not have to explictly expand the variable when using a variable in a SET /A expression. You can simply use the variable name without percents or exclamations. But this only works with the SET /A command.
You can also perform multiple computations and assignments with a single SET /A command by using a comma between each assignment.
#echo off
setlocal enableDelayedExpansion
REM counts from 0 to count, so 8 = 9 copies
set count=8
set filename_1=example
set filename_2=thought
set extension=.au
set start_1=2
set start_2=1023
set source=%filename_1%%start_1%%filename_2%%start_2%%extension%
for /L %%i in (0, 1, %count%) do (
set /a "number_1=start_1+%%i, number_2=start_2+%%i"
set destination=%filename_1%!number_1!%filename_2%!number_2!%extension%
echo !destination!
REM copy %source% !destination!
)
pause