Reference element by index in a list using Batch Script - windows

Trying to obtain an element in a list by its index, using batch script. Here is the code:
#Echo off
setlocal EnableDelayedExpansion
set acc[0]=default
set acc[1]=Account_2
set acc[2]=Account_3
set acc[3]=Account_4
set acc[4]=Account_5
if exist interator.txt (
set /p i=<interator.txt
echo "read: !i!"
echo "!acc[%i%]!"
REM start cmd /c setx AWS_PROFILE !acc[%i%]!
REM start cmd /k python script.py
set /A i=i+1
(echo !i!)>interator.txt
echo "write: !i!"
) else (
(echo 0)>interator.txt
)
Output Received:
"read: 0"
""
"write: 1"
As setx requires the CMD session to be closed, for affect to take place. I am trying a different approach to automate some regular stuff.
Expected Output:
"read: 0"
"default"
"write: 1"

#Echo off
setlocal EnableDelayedExpansion
set "acc[0]=default
set "acc[1]=Account_2"
set "acc[2]=Account_3"
set "acc[3]=Account_4"
set "acc[4]=Account_5"
if exist q65771965.txt (
set /p i=<q65771965.txt
echo "read: !i!"
FOR %%a IN (acc[!i!]) DO (
ECHO "!%%a!"
echo start cmd /c setx AWS_PROFILE "!%%a!"
echo start cmd /k python script.py
)
set /A i=i+1
(echo !i!)
echo "write: !i!"
) else (
(echo 0)
)
GOTO :EOF
OK - small changes to allow this to work on my test environment:
Changed name of file from interator.txt to q65771965.txt (suits my environment)
Removed updating of data file so the modifications are shown on-screen.
Replaced REM start with ECHO start to show the start commands on-screen.
Subtle syntax-oriented change : Use set "var1=data" for setting values - this avoids problems caused by trailing spaces.
Significant change : insert a for loop to transfer indirect values to a metavariable (%%a) and use these.
Possibly-required : I don't use setx much, but I've some memory of the argument's needing to be "quoted"

The problem is, you used echo "%acc[!i!]%" within a codeblock. You need another layer of parsing, like call echo "%%acc[!i!]%%"
As an alternative, restructure your code, so the critical part isn't in a code block:
#Echo off
setlocal EnableDelayedExpansion
set acc[0]=default
set acc[1]=Account_2
set acc[2]=Account_3
set acc[3]=Account_4
set acc[4]=Account_5
if not exist interator.txt (
(echo 0)>interator.txt
goto :eof
)
set /p i=<interator.txt
echo "read: !i!"
echo "%acc[!i!]%"
set /A i=i+1
(echo !i!)>interator.txt
echo "write: !i!"
(this code is functionally identically to yours, just structured in another way)
(btw: it should probably iterator, not interator - but that's only spelling)

Related

Removing last character from a variable in a Batch file

I'm trying to assign a value to a variable called "doNotLog" based on the last character of some other variable called "choice" through a Batch file.
The structure of the variable choice is:
1) Either an integer with 1 or more digits
2) Or an integer with 1 or more digits plus character "n" at the last
The objectives are:
1) To set the value of "doNotLog" to true if the last character of "choice" is n
2) To finally remove n from "choice"
The Batch file I'm using to achieve this is:
#echo off
echo enter choice
set/p choice=
set doNotLog=false
setlocal ENABLEDELAYEDEXPANSION
if %choice:~-1,1%==n (
set doNotLog=true
set choice=!choice:n=!
)
endlocal
echo After changes:
echo choice= %choice%
echo donotLog= %doNotLog%
#pause
It produces the following output:
enter choice
54n
After changes:
choice= 54n
donotLog= false
Press any key to continue . . .
However, I was expecting the following output:
enter choice
54n
After changes:
choice= 54
donotLog= true
Press any key to continue . . .
How do I achieve my desired output
Variable expansion is described under the help information for the Set command. Open a Command Prompt window and enter set /? to read it.
Here's some helper examples for you:
C:\Users\Mohd>Set "Variable=String"
C:\Users\Mohd>Echo(%Variable%
String
C:\Users\Mohd>Echo(%Variable:~0,-1%
Strin
C:\Users\Mohd>Echo(%Variable:~0,1%
tring
C:\Users\Mohd>Echo(%Variable:~0,-2%
Stri
C:\Users\Mohd>Echo(%Variable:~-1,1%
g
C:\Users\Mohd>Echo(%Variable:~1,-1%
trin
C:\Users\Mohd>Echo(%Variable:~1,1%
t
C:\Users\Mohd>Echo(%Variable:~1,2%
tr
C:\Users\Mohd>Echo(%Variable:~1,-2%
tri
C:\Users\Mohd>Echo(%Variable:~2,1%
r
C:\Users\Mohd>Echo(%Variable:~2,-1%
rin
C:\Users\Mohd>Echo(%Variable:~2,-2%
ri
C:\Users\Mohd>Echo(%Variable:~-2,2%
ng
C:\Users\Mohd>Echo(%Variable:~-2,1%
n
C:\Users\Mohd>Echo(%Variable:~2,-1%
rin
The issue is that you've tried to modify doNotLog and choice inside a setlocal, so you've set them locally, but the global ones remain unchanged.
Get the value of them inside setlocal to achieve the expected result:
#echo off
echo enter choice
set/p choice=
set doNotLog=false
setlocal ENABLEDELAYEDEXPANSION
if %choice:~-1,1%==n (
set doNotLog=true
set choice=!choice:n=!
)
echo After changes:
echo choice= %choice%
echo donotLog= %doNotLog%
#pause
endlocal
If you want to disable the delayed expansion anyway, you can put the rest of the code in another setlocal, that disables it:
#echo off
echo enter choice
set/p choice=
set doNotLog=false
setlocal ENABLEDELAYEDEXPANSION
if %choice:~-1,1%==n (
set doNotLog=true
set choice=!choice:n=!
)
setlocal DISABLEDELAYEDEXPANSION
echo After changes:
echo choice= %choice%
echo donotLog= %doNotLog%
#pause
:: We have to end two locals now...
endlocal
endlocal
...however, if you do this multiple times, your code will quickly become unmaintainable (oh, wait, aren't all batch files unmaintainable?) and less performant.
Alternatively, you can move variables out of local by using the magic of single-line or parenthesis-grouped commands, and the classic %variable% syntax:
endlocal & set "doNotLog=%doNotLog%" & set "choice=%choice%"
...or the equivalent, but more readable...
(
endlocal
set "doNotLog=%doNotLog%"
set "choice=%choice%"
)
The above solutions might look silly, but they do work...
Both of the above will set global variables, because they're after endlocal, but read the local ones, as they're substituted before the evaluation of the line (or the grouped structure) starts. (That's why these hacks work with the %var% syntax, but not with the !var!)
So, your code can even be changed to:
#echo off
echo enter choice
set/p choice=
set doNotLog=false
setlocal ENABLEDELAYEDEXPANSION
if %choice:~-1,1%==n (
set doNotLog=true
set choice=!choice:n=!
)
endlocal & (
set "doNotLog=%doNotLog%"
set "choice=%choice%"
)
echo After changes:
echo choice= %choice%
echo donotLog= %doNotLog%
#pause
...which may be the best solution among these.
So, what about without using SetLocal EnableDelayedExpansion
#echo off
set "doNotLog=" && set "choice="
for /f ^skip^=^4 %%a in ('echo/ prompt $h^| cmd
')do echo/ && set/p "choice=-%%a Enter choice: "
echo/%choice:~-1%|find /i "n">nul && (
call set "choice=%choice:~0,-1%" && set "doNotLog=true")
if "%doNotLog%" == "" set "doNotLog=false"
echo/ choice = %choice% && echo/ doNotLog = %doNotLog%
echo/ Press any key to continue... & timeout -1 >nulF
outputs for input 54n and 54 ::
G:\SO_en-EN\Q59738810>Q59738810.cmd
Enter choice: 54n
choice = 54
doNotLog = true
Press any key to continue...
---------------------------------------
G:\SO_en-EN\Q59738810>Q59738810.cmd
Enter choice: 54
choice = 54
doNotLog = false
Press any key to continue...

"ECHO is on" is setting in variable

I have a batch file which takes three parameters [log:path], [logok:path], [logerr:path]. The values of the respective parameter is log:c:\logs\install.log, logok:c:\logs\installok.log,
logerr:c:\logs\installerr.log.
I need to process these 3 parameters and set it on respective variable log=c:\logs\install.log, logok=c:\logs\installok.log, logerr=c:\logs\installerr.log so that I can use them in next step to create a file in those paths.
I have written below script but somehow each variable is printing "ECHO is on". It should actually print the location path. Any idea how to achieve this?
REM PARAMS ARE [LOG:PATH] [LOGOK:PATH] [LOGERR:PATH]
REM ACCESS WITH '%LOG%', '%LOGOK%' AND '%LOGERR%'
REM SETUP LOCALIZED ENVIRONMENT VARIABLES FOR THE LOG, LOGOK AND LOGERR PARAMS
SETLOCAL ENABLEDELAYEDEXPANSION
FOR %%A IN (%*) DO (
ECHO %%A >> C:\LOGS\TEST1.TXT
FOR /F "TOKENS=1,2,3 DELIMS=:" %%G IN ("%%A") DO (
SET %%G=%%H:%%I
)
)
ENDLOCAL
ECHO %LOG% >> C:\LOGS\TEST2.TXT
ECHO %LOGOK% >> C:\LOGS\TEST3.TXT
ECHO %LOGERR% >> C:\LOGS\TEST4.TXT
START /WAIT MSIEXEC /I "%~DP0SETUP.MSI" /QN
SET EXIT_CODE=%ERRORLEVEL%
REM If the instalaltion is successful it should a create installok.log file in 'c:\logs'
IF %EXIT_CODE% EQU 0 ECHO INSTALLED >> %LOGOK%
REM If it fails then it should a create installerr.log file 'c:\logs')
IF NOT EXIST %LOGOK% ECHO FAILED >> %LOGERR%
Output of TEST1.TXT:
log:c:\logs\install.log
logok:c:\logs\installok.log
logerr:c:\logs\installerr.log
Output of TEST1.TXT,TEST2.TXT,TEST3.TXT:
ECHO is on.
Try this:
REM PARAMS ARE [LOG:PATH] [LOGOK:PATH] [LOGERR:PATH]
REM ACCESS WITH '%LOG%', '%LOGOK%' AND '%LOGERR%'
REM SETUP LOCALIZED ENVIRONMENT VARIABLES FOR THE LOG, LOGOK AND LOGERR PARAMS
SETLOCAL ENABLEDELAYEDEXPANSION
FOR %%A IN (%*) DO (
ECHO %%A >> c:\Logs\TEST1.TXT
FOR /F "TOKENS=1,2,3 DELIMS=:" %%G IN ("%%A") DO (
SET %%G=%%H:%%I
)
)
ECHO !LOG! >> c:\LOGS\TEST2.TXT
ECHO !LOGOK! >> c:\LOGS\TEST3.TXT
ECHO !LOGERR! >> c:\LOGS\TEST4.TXT
START /WAIT MSIEXEC /I "%~DP0SETUP.MSI" /QN
SET EXIT_CODE=%ERRORLEVEL%
REM If the instalaltion is successful it should a create installok.log file in 'c:\logs'
IF %EXIT_CODE% EQU 0 ECHO INSTALLED >> !LOGOK!
REM If it fails then it should a create installerr.log file 'c:\logs')
IF NOT EXIST !LOGOK! ECHO FAILED >> !LOGERR!
ENDLOCAL
Basically this:
Extends the local scope so that the variables which were assigned inside the FOR loop will be available in the rest of the script
Moves variables to the !delayed! variable syntax in order to cause them to be evaluated as late as possible. See the documentation for "EnableDelayedExpansion" for more details
Note that "ECHO is on." is the output that you get when calling echo with no parameters:
C:\>type echo_test.bat
echo
echo %no_such_variable%
C:\>echo_test.bat
C:\>echo
ECHO is on.
C:\>echo
ECHO is on.

Issue with Windows Batch Script - replace

Hi have a for loop that I use to get entries out of a lsit of drives to loop through, for each drive I loop through entries in a file, this all works. But then what I am trying to do is finding the entry from the file (which is a file name and path without the drive letter and the first folder (parent folder named %_TAG%)
Ok so far so good, now what I want to do is replace the extension of the file with a r (*.w or *.p or *.cls must become *.r)
But for some reason my command does not work (set str=!str:.w=.r!) I have tried replacing the ! with %
I have tried various things and still stuck. Here is my complete batch file. It reads in a file with a list of partial path and file names. I even tried using a function to do renaming
:bof
#echo on
cls
setlocal enabledelayedexpansion
:init
set _TAG=TST
set /p _INFILE="Enter Filename to use for investigation: "
set _INFILE=%CD%\%_INFILE%
set /p _TAG="To which environment did you deploy? (TST / LIV): "
if /i "%_TAG%"=="LIV" (
set _drive=M O P R S Y X
) else (
set _drive=M T X
)
:confirm
echo We will use code in the: %_TAG% folder and file: %_INFILE% and drives: %_drive%
set /p _continue="Do you wish to continue (Y/N): "
if /i "%_continue%"=="N" goto :eof
echo We will use code in the: %_TAG% folder and file: %_INFILE% and drives: %_drive% 1>> %_INFILE%.%_TAG%.rlog 2>&1
:dowork
for %%j in (%_drive%) do (
echo ...
echo investigating: %%j:\%_TAG%\ 1>> %_INFILE%.%_TAG%.rlog 2>&1
%%j:
cd %%j:\%_TAG%\
for /f %%i in (%_INFILE%) DO (
set "string=%%i"
call:mySetPathFunc !string! "/" "\"
call:mySetPathFunc !string! ".w" ".r"
call:mySetPathFunc !string! ".p" ".r"
call:mySetPathFunc !string! ".cls" ".r"
if exist %%j:\%_TAG%\!string! (
echo I found you in correct folder 1>> %_INFILE%.%_TAG%.rlog 2>&1
) else (
echo I did not find you in correct folder 1>> %_INFILE%.%_TAG%.rlog 2>&1
)
call:mySetPathFunc !string! "cbsrc" "tty"
call:mySetPathFunc !string! "hotfix" "tty"
if exist %%j:\%_TAG%\%%string%% (
echo I found you in tty 1>> %_INFILE%.%_TAG%.rlog 2>&1
) else (
echo I did not find you in tty 1>> %_INFILE%.%_TAG%.rlog 2>&1
)
)
)
:eof
ECHO Done used - tag: %_TAG% and used file: %_INFILE% to drive %_drive%
ECHO Done used - tag: %_TAG% and used file: %_INFILE% to drive %_drive% 1>> %_INFILE%.%_TAG%.rlog 2>&1
timeout /t 8 /nobreak > NUL
c:
exit /b
::--------------------------------------------------------
::-- Function section starts below here
::--------------------------------------------------------
:mySetPathFunc - passing a variable by reference
echo before %~1 1>> %_INFILE%.%_TAG%.rlog 2>&1
set %~1=%~1:%~2=%~3
echo after %~1 1>> %_INFILE%.%_TAG%.rlog 2>&1
goto:eof
Generation 955 of the "delayedexpansion" problem.
Within a block statement (a parenthesised series of statements), the entire block is parsed and then executed. Any %var% within the block will be replaced by that variable's value at the time the block is parsed - before the block is executed - the same thing applies to a FOR ... DO (block).
Hence, IF (something) else (somethingelse) will be executed using the values of %variables% at the time the IF is encountered.
Two common ways to overcome this are 1) to use setlocal enabledelayedexpansion and use !var! in place of %var% to access the changed value of var or 2) to call a subroutine to perform further processing using the changed values.
Where flags are involved, the situation changes again. set "flag=" will ensure a flag is cleared. set "flag=somethingelse" will ensure it is set (the value is not relevant.) Using if defined flag (doiftrue) else (doiffalse) works on the run-time (current) status of flag - not the parse-time value.
Look for hundreds of items about delayedexpansion on SO for solutions.

Why this code says echo is off?

What is wrong with this code? It says ECHO is off.
#ECHO off
set /p pattern=Enter id:
findstr %pattern% .\a.txt > result
if %errorlevel%==0 (
set var2= <result
echo %var2%
set var1=%var2:~5,3%
echo %var1% > test.txt
echo %var1%
) else (
echo error
)
del result
pause
Any help is appreciated.
If your variable is empty somewhere, it will be the same as having the command "echo" on its own, which will just print the status of echo.
To avoid this, you should replace all your echo commands with something like this:
echo var2: %var2%
That way, if %var2% is empty it will just print "echo var2:" instead of "echo off".
As Laurent stated, it's not a problem of the ECHO, it's a problem of your code.
In batch files, blocks are completely parsed before they are executed.
While parsing, all percent expansion will be done, so it seems that your variables can't be changed inside a block.
But for this exists the delayed expansion, the delayed expansion will be evaluated in the moment of execution not while parsing the block.
It must be enabled, as per default the delayed expansion is disabled.
#ECHO off
setlocal EnableDelayedExpansion
set /p pattern=Enter id:
findstr %pattern% .\a.txt > result
if %errorlevel%==0 (
set var2= <result
echo(!var2!
set var1=!var2:~5,3!
echo(!var1! > test.txt
echo(!var1!
) else (
echo error
)
del result
I used here the construct echo( instead of echo as this will ensure echoing an empty line even if the variable is empty.
Not sure, if this post is still read, but nevertheless.
You should try the following:
On top of the code right after #echo off you have to put in
setlocal enabledelayedexpansion
Additionally anywhere you want to use variables changed in a block of brackets (like For-Loops or If's) you have to change the %into ! to get
!varname!
This should be helping...
Greetings
geisterfurz007
First create a file a.txt in the same directory u have this batch file ... write some text in that...Note: only Windows 2000
Windows ME
Windows XP
Windows Vista
Windows 7 supports FINDSTR
set /p pattern=Enter id:
findstr %pattern% a.txt > __query.tmp
set /p result=<__query.tmp
if %errorlevel%==0 (
set var2= %result%
echo %var2%
set var1= %var2:~5,3%
echo %var1% > test.txt
echo %var1%
) else (
echo error
)
del __query.tmp
pause
run this bath file .. you will find a substring(start=5,length=3) of the first line of string you have in a.txt in a newly created file test.txt. Finally got it working !
The solution for your problem is to put the "echo"s after the if block is completed.
Try this:
#ECHO off
set /p pattern=Enter id:
findstr %pattern% .\a.txt > result
if %errorlevel%==0 (
set var2= <result
set var1=%var2:~5,3%
goto print
) else (
echo error
goto result
)
:print
echo %var2%
echo %var1% > test.txt
echo %var1%
:result
del result
pause
This way you can see the solution as you wanted.
Cheers! ;]

windows batch file array extraction counter not being incremented by +=

I am translating a shell script to windows batch. What I need to do is take all except 1,2 and last from command line arguments. join them and send to another program as argv.
#echo off
SET subject=%1
set count=%2
set candidates=""
set /a i=0
set /a c=0
FOR %%A IN (%*) DO (
ECHO %%A
set /a i+=1
IF %i% geq 2 (
set /a c+=1;
set candidates[!c!]=%%A
)
)
SET /a count_actual=(%i%-3)
SET /a count_expected=%count%
echo %count_expected%
echo %count_actual%
echo %subject%
echo %candidates%
I want the candidates array be argv[3..n-1]
e.g. If I write batch x 2 a b p it should pass a b to that another program
The problem is loop counter is not being incremented by += operator. If I write echo %1% inside FOR I see 0 always
You should not use for %%A in (%*) as it treats %* as filename set. This may cause problems, especially if you can pass * or ? (wildcard match characters in cmd) in parameters - as they will be expanded to all files satisfying pattern. Second, batch does really know nothing about arrays - a[1] and a[2] are just a shorthand notation for humans - they are two distinct variables.
Given the problem Parse command line, take second parameter as count of parameters to concatenate into a variable, here is my take:
#echo off
setlocal
set subject=%1
shift
set exp_count=%1
if not defined exp_count (
echo Count not specified
exit /b 1
)
set /a "verify=%exp_count%"
if %verify% leq 0 (
echo Count not valid /not a positive integer/
exit /b 2
)
set real_count=0
:loop
shift
if "%~1"=="" goto end_params
set /a real_count+=1
if %real_count% leq %exp_count% set "candidates=%candidates%%~1"
goto loop
)
:end_params
if %real_count% lss %exp_count% (
echo Less parameters passed than specified!
exit /b 3
)
echo %subject%
echo %candidates%
Please note I'm not checking if there is a 'hanging' parameter (the last, not being concatenated) but it should be trivial to add that check. I left it out on purpose to make the code more flexible.
I have two answers for your question:
1- The first problem is that in IF %i% ... command the value of i variable not change (although set /a i+=1 command will correctly increment the variable) and the way to solve it is by including setlocal EnableDelayedExpansion command at beginning and enclose i in percents signs this way: IF !i! ... (as said in previous answers). However, you must note that an array variable in Batch is different than a simple variable with same name (they both can exist at same time), so array elements must always be written with subscripts and there is NO way to process an entire array in a single operation. See this topic for further details.
In your program you must transfer the elements of candidates array into a simple variable, that in the example below have the same name (just to state my point):
#echo off
setlocal EnableDelayedExpansion
SET subject=%1
set count=%2
set candidates=""
set /a i=0
set /a c=0
FOR %%A IN (%*) DO (
ECHO %%A
set /a i+=1
IF !i! geq 2 (
set /a c+=1
set candidates[!c!]=%%A
)
)
SET /a count_actual=(%i%-3)
SET /a count_expected=%count%
echo %count_expected%
echo %count_actual%
echo %subject%
REM Transfer "candidates" array elements into "candidates" simple variable:
set candidates=
FOR /L %%i IN (1,1,%c%) do (
set candidates=!candidates! !candidates[%%i]!
)
REM Show "candidates" simple variable:
echo %candidates%
Note that in Batch files you may insert commas, semicolons and equal-signs as separators instead spaces in most commands. However, SET /A command have other rules at this respect, so the semicolon must be omitted.
2- Independently of the array management explained above, this is the way I would solve your problem using a list instead of an array:
#echo off
SET subject=%1
shift
set count=%1
set candidates=
set lastArg=
set i=0
:nextArg
shift
if "%1" equ "" goto endArgv
set /a i+=1
set candidates=!candidates! !lastArg!
set lastArg=%1
goto nextArg
:endArgv
SET /a count_actual=i-3, count_expected=count
echo %count_expected%
echo %count_actual%
echo %subject%
echo %candidates%
Antonio
Yes your code will not increment i. Batch variable replacement occurs when a block is parsed, not when it is executed. The entire for block is parsed once, so %i% is replaced with zero before the for block is executed.
To disable that you need to enable delayed expansion and change your variable escape characters from %'s to !'s to have the replacement made at runtime. Then you will see i incremented in the for loop.
#echo off
Setlocal EnableDelayedExpansion
SET subject=%1
set count=%2
set candidates=""
set /a i=0
set /a c=0
FOR %%A IN (%*) DO (
ECHO %%A
set /a i+=1
IF !i! geq 2 (
set /a c+=1
set candidates[!c!]=%%A
)
)
SET /a count_actual=(%i%-3)
SET /a count_expected=%count%
echo %count_expected%
echo %count_actual%
echo %subject%
echo %candidates%
You will also need to get rid of the ; at the end of the set /a c+=1; line and I'm not sure what you are trying to do on line set candidates[!c!]=%%A as the brackets don't mean anything in batch.
While there are a bunch of answers already listed, I decided to add one more. My approach is to keep the answer as simple as possible for your specific needs. If you have any questions feel free to ask.
This will create the array as you desired [3,...,n-1] without the need for delayed expansion or fancy logic.
#echo off
:: Get the First Two Parameters
set "subject=%1"
shift
set "count=%1"
shift
:: Loop through the rest
set "index=0"
:NextParam
set "param=%1"
shift
set "next=%1"
:: Skip the last parameter
if not defined next goto EndParam
set "candidates[%index%]=%param%"
set /a "index+=1"
goto NextParam
:EndParam
set "count_actual=%index%"
set "count_expected=%count%"
:: Show the Results
echo %count_actual%
echo %count_expected%
echo %subject%
set candidates
Here is an alternate where the candidates are stored in a space delimited string instead of seperate variables. Replace the space between the %candidates% %param% to whatever delimiter you desire.
#echo off
:: Get the First Two Parameters
set "subject=%1"
shift
set "count=%1"
shift
:: Loop through the rest
set "index=0"
:NextParam
set "param=%1"
shift
set "next=%1"
:: Skip the last parameter
if not defined next goto EndParam
set "candidates=%candidates% %param%"
set /a "index+=1"
goto NextParam
:EndParam
set "count_actual=%index%"
set "count_expected=%count%"
:: Show Results
echo %count_actual%
echo %count_expected%
echo %subject%
echo %candidates%

Resources