use cd in windows .bat file has no effect - windows

i want to run git pull in a path for all projects, so i wrote a bat file.
#echo off
setlocal enabledelayedexpansion
set dir=%1
if "%dir%"=="" (
set dir=%~dp0
)
for /D /R "%dir%" %%i in (*) do (
if exist %%i\.git (
cd /d %%i
rem no effect
echo %cd%
rem git pull
)
)
pause
but is seems that cd in for loop does not take any effect, i don't know why.
can someone help me solve this problem?enter code here

It has effect. But, in batch files, when a block of code (the code enclosed in parenthesis) is reached (the same for a line out of a block), variable reads are replaced with the value of the variable before executing the code in the block. So, when your for command is reached, the read of the %cd% variable is replaced with the value of the %cd% variable before executing the code. This speeds and simplifies execution of the code but generate this kind of problems.
You can enable delayed expansion with setlocal enabledelayedexpansion command, and change the sintax from %cd% to !cd!. This tells cmd that that this variable read should be delayed until execution of the line.

Related

Batch, How do I execute code stored in a variable or txt file?

How can I execute code saved in a variable? I'm trying to execute an if statement that I have saved in another file (that must remain a text file). Clues on how to execute an if statement just from a variable might help, as I assume the problem is that it can't read the %%s.
Text file contains:
if %var%==0301 (echo Yay)
Batch file contains:
for /f "tokens=*" %%s in (code.file) do (
%%s
)
This normally executes the code in code.file by setting everything in code.file to the variable %%s and then executing the variable.
The result is this: 'if' is not recognized as an internal or external command, operable program or batch file.
This method works for executing echo and set, but I need it to work for if.
The IF is detected by the parser in phase2 only, but %%s will be expanded in a later phase.
You need to use a percent expansion for it, like in this sample
for /f "tokens=*" %%s in (code.file) do (
set "line=%%s"
call :executer
)
exit /b
:executer
%line%
exit /b
But for your sample: if %var%==0301 (echo Yay)
It will never say Yay, because %var% will never be expanded.
This is because %line% is already a percent expansion, it will not work recursively.
That could be solved by changing the text in code.file to
if !var! == 0301 (echo Yay)
This works, because the delayed expansion happens after the percent expansion
Or a much simpler solution:
copy code.file tmp.bat
call tmp.bat
del tmp.bat
The major problem at hand is that the command interpreter particularly handled the commands if, for and rem: These commands are recognized earlier than every other one, even before for meta-variables like %%s become expanded. Therefore, these commands are no longer detected after expansion of %%s.
Refer to: How does the Windows Command Interpreter (CMD.EXE) parse scripts?
According to this, said commands are detected during Phase 2, while expansion of for meta-variables happens in Phase 4. Other commands are found later in Phase 7.
A possible way to work around that is to use a sub-routine, which %-expansion occurs in, which happens in Phase 1, hence before recognition of the three special commands:
for /f "tokens=*" %%s in (code.file) do (
rem // Execute the read command in a sun-routine:
call :SUB %%s
)
goto :EOF
:SUB
rem // Take the whole argument string as a command line:
%*
goto :EOF

Batch: How to set variable in a nested for-loop and re-use it outside of it (enabledelayedexpansion does not work)

I have a batch inside a folder whose goal is to execute all the batch files located in its sub-folders and evaluate their errorlevel. If at least one of them is equal to 1, the main batch will return an exit code equal to 1. This does not mean that the main batch have to exit with the first errorlevel equal to 1: all the sub-batch files must be executed anyway.
EDIT: all the sub-batch files return an exit code equal to 1 if they fail or equal to 0 if they pass (they are all batch files for testing purpose I wrote myself).
Problem: the exit_code variable never changes outside the loops.
I found similar problems here on SO (and a very similar one: Counting in a FOR loop using Windows Batch script) but they didn't help (probably I didn't understand... I don't know, I'm new to batch scripting).
Thanks in advance.
CODE:
#echo off
setlocal enabledelayedexpansion
set exit_code=0
for /D %%s in (.\*) do (
echo ******************** %%~ns ********************
echo.
cd %%s
for %%f in (.\*.bat) do (
echo calling %%f
call %%f
if errorlevel 1 (
set exit_code=1
)
)
echo.
echo.
cd ..
)
echo !exit_code!
exit /B !exit_code!
Let us assume the current directory on starting the main batch file is C:\Temp\Test containing following folders and files:
Development & Test
Development & Test.bat
Hello World!
Hello World!.bat
VersionInfo
VersionInfo.bat
Main.bat
Batch file Development & Test.bat contains just the line:
#dir ..\Development & Test
Batch file Hello World!.bat contains just the line:
#echo Hello World!
Batch file VersionInfo.bat contains just the line:
#ver
Batch file main.bat contains following lines:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
cls
set "exit_code=0"
for /D %%I in ("%~dp0*") do (
echo ******************** %%~nxI ********************
echo/
for %%J in ("%%I\*.bat") do (
echo Calling %%J
echo/
pushd "%%I"
call "%%J"
if errorlevel 1 set "exit_code=1"
popd
)
echo/
echo/
)
echo Exit code is: %exit_code%
endlocal & exit /B %exit_code%
A command prompt is opened in which next the following command lines are executed manually one after the other:
C:\Temp\Test\Main.bat
echo Errorlevel is: %errorlevel%
ren "C:\Temp\Test\Development & Test\Development & Test.bat" "Development & Test.cmd"
C:\Temp\Test\Main.bat
echo Errorlevel is: %errorlevel%
The first execution of Main.bat results in exit with value 1 as it can be seen in command prompt window on the line:
Errorlevel is: 1
The reason is the wrong coded dir command with the directory name not enclosed in double quotes resulting in interpreting Test as command to execute. For that reason the dir command line results in following error output:
Volume in drive C is TEMP
Volume Serial Number is 14F0-265D
Directory of C:\Temp\Test
File Not Found
'Test' is not recognized as an internal or external command,
operable program or batch file.
The exit code of this batch file is not 0 due to the error and for that reason the condition if errorlevel 1 is true and set "exit_code=1" is executed already on first executed batch file.
The processing of the other two batch files always end with 0 as exit code.
The command ren is used to change the file extension of Development & Test.bat to have the batch file next with name Development & Test.cmd resulting in ignoring it by main.bat. The second execution of Main.bat results in exit with 0 as it can be seen on the line:
Errorlevel is: 0
Please read the following pages for the reasons on all the code changes:
Syntax error in one of two almost-identical batch scripts: ")" cannot be processed syntactically here
change directory command cd ..not working in batch file after npm install
DosTips forum topic: ECHO. FAILS to give text or blank line - Instead use ECHO/
Why is no string output with 'echo %var%' after using 'set var = text' on command line?
How does the Windows Command Interpreter (CMD.EXE) parse scripts?
What are the ERRORLEVEL values set by internal cmd.exe commands?
Which cmd.exe internal commands clear the ERRORLEVEL to 0 upon success?
Single line with multiple commands using Windows batch file
Summary of the changes:
Delayed expansion is not enabled in Main.bat as not required here to process also correct directory and file names containing an exclamation mark like C:\Temp\Test\Hello World!\Hello World!.bat.
I and J are used as loop variables instead of s and f because of the latter two letters could be misinterpreted as loop variable modifiers in some cases. Therefore it is better to avoid the letters which have a special meaning for command for on referencing the loop variables.
%~dp0 is used instead of .\ to make sure that the batch file searches for non-hidden subdirectories in the directory of the batch file independent on what is the current directory on starting the batch file. This expression references drive and path of argument 0 which is the full path of currently executed batch file Main.bat. The referenced path of the batch file always ends with a backslash and for that reason %~dp0 is concatenated with * without an additional backslash.
Directory and file name arguments are enclosed in double quotes to work also for names containing a space or one of these characters &()[]{}^=;!'+,`~. %%~nxI and %%J in the two echo command lines are not enclosed in double quotes as not necessary as long as delayed expansion is not enabled. The batch file makes sure that this is not the case for Main.bat.
The usage of "%~dp0*" instead of just .\* in first FOR loop results in getting assigned to loop variable I the directory names with full path never ending with a backslash. The usage of "%%I\*.bat" makes sure to get assigned to loop variable J the full qualified file name of a non-hidden batch file. It is in general better to use full qualified directory/file names wherever possible. This helps also quite often on errors.
The two cd commands are replaced by pushd and popd and moved inside the inner FOR loop. Then it does not matter if a called batch file works only with current directory being the directory of the called batch file or works independent on current directory like Main.bat. Further it does not longer matter if a called batch file changes the current directory as with popd the initial current directory on starting Main.bat is restored as current directory which could be the directory in which files are stored to be processed by the called batch files. The usage of pushd and popd makes this batch file also working on being stored on a network resource and Main.bat is started with its UNC path.
The most important modification is on last command line:
endlocal & exit /B %exit_code%
This command line is first parsed by Windows command processor cmd.exe with replacing %exit_code% by current value of environment variable exit_code defined inside the local environment setup with setlocal EnableExtensions DisableDelayedExpansion. So the command line becomes either
endlocal & exit /B 0
or
endlocal & exit /B 1
Then Windows command processor executes command endlocal to restore the previous environment defined outside Main.bat which results in exit_code no longer defined if not defined in initial execution environment. Then the command exit with option /B to exit just processing of batch file Main.bat is executed with returning 0 or 1 to the parent process which is cmd.exe which assigns the exit code to variable errorlevel.
Well, there is one issue left with the batch file code of Main.bat. A called batch file could modify the value of environment variable exit_code of Main.bat on using the same environment variable without definition of a local environment by using command setlocal. The solution would be to use additionally the commands setlocal before and endlocal after calling a batch file.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
cls
set "exit_code=0"
for /D %%I in ("%~dp0*") do (
echo ******************** %%~nxI ********************
echo/
for %%J in ("%%I\*.bat") do (
echo Calling %%J
echo/
pushd "%%I"
setlocal
call "%%J"
endlocal
if errorlevel 1 set "exit_code=1"
popd
)
echo/
echo/
)
echo Exit code is: %exit_code%
endlocal & exit /B %exit_code%
The command endlocal does not modify errorlevel.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
call /?
cls /?
echo /?
endlocal /?
for /?
if /?
popd /?
pushd /?
set /?
setlocal /?

Modify for loop to not use delayedexpansion in batch script

In my efforts to understand the for..do loops syntax and their use of %% variables. I have gone through 2 specific examples/implementations where the one for loop does not use DELAYEDEXPANSION and another where it does use DELAYEDEXPANSION with the ! notation. The 1st for loop appears to be compatible with older OSs like the Windows XP whereas the 2nd for loop example does not.
Specifically, the 1st for loop example is taken from this answer (which is related to this) and the 2nd for loop example is taken from this answer.
Modified code for both examples copied below:
1st for loop
for /f "tokens=2 delims==" %%a in ('wmic OS Get localdatetime /value') do set "dt=%%a"
set "YY=%dt:~2,2%"
set "YYYY=%dt:~0,4%"
set "MM=%dt:~4,2%"
set "DD=%dt:~6,2%"
set "HH=%dt:~8,2%"
set "Min=%dt:~10,2%"
set "Sec=%dt:~12,2%"
set "datestamp=%YYYY%%MM%%DD%"
set "timestamp=%HH%%Min%%Sec%"
echo datestamp: "%datestamp%"
echo timestamp: "%timestamp%"
2nd for loop
SETLOCAL ENABLEDELAYEDEXPANSION
set "path_of_folder=C:\folderA\folderB"
for /f "skip=5 tokens=1,2,4 delims= " %%a in (
'dir /ad /tc "%path_of_folder%\."') do IF "%%c"=="." (
set "dt=%%a"
set vara=%%a
set varb=%%b
echo !vara!, !varb!
set day=!vara:~0,2!
echo !day!
)
Since I have been reading and seeing issues where delayed expansion (or the ! notation) is not compatible with older OSs (e.g. Windows XP), I would like to see how to write the 2nd loop like the 1st loop; i.e. without the use of DELAYEDEXPANSION.
I explain in detail what aschipfl wrote already absolutely right in his comment.
Both batch files work also on Windows 2000 and Windows XP using also cmd.exe as command processor. The batch files do not work on MS-DOS, Windows 95 and Windows 98 using very limited command.com as command interpreter.
A command can be executed with parameter /? in a command prompt window to get output the help for this command. When in help is written with enabled command extensions it means supported only by cmd.exe on Windows NT based Windows versions and not supported by MS-DOS or Windows 9x using command.com. That means, for example, for /F or if /I or call :Subroutine are not available on Windows 9x, or on Windows NT based Windows with command extensions explicitly disabled. On Windows 9x it is not even possible to use "%~1" or "%~nx1".
The first batch file executes in FOR loop only one command exactly once:
set "dt=%%a"
That command line requires already enabled command extensions. All other commands below are executed after the FOR loop finished. In other words the FOR loop in first batch file does not use a command block to run multiple commands within the FOR loop.
Whenever the Windows command processor detects the beginning of a command block on a command line, it processes the entire command block before executing the command on this command line the first time.
This means for second batch file all variable references using %Variable% are expanded already before the command FOR is executed and then the commands in the command block are executed with the values of the variables as defined above FOR command line. This can be seen by removing #echo off from first line of batch file or change it to #echo ON and run the batch file from within a command prompt window because now it can be seen which command lines respectively entire command blocks defined with ( ... ) are really executed after preprocessing them by the Windows command processor.
So whenever an environment variable is defined or modified within a command block and its value is referenced in same command block it is necessary to use delayed expansion or use workarounds.
One workaround is demonstrated below:
setlocal EnableExtensions DisableDelayedExpansion
set "FolderPath=%SystemRoot%\System32"
for /F "skip=5 tokens=1,2,4 delims= " %%a in ('dir /AD /TC "%FolderPath%\."') do if "%%c"=="." (
set "VarA=%%a"
set "VarB=%%b"
call echo %%VarA%%, %%VarB%%
call set "Day=%%VarA:~0,2%%
call echo %%Day%%
)
endlocal
pause
As there is no #echo off at top of this batch code it can be seen on executing the batch file what happens here. Each %% is modified on processing the command block to just %. So executed are the command lines.
call echo %VarA%, %VarB%
call set "Day=%VarA:~0,2%
call echo %Day%
The command CALL is used to process the rest of the line a second time to run the ECHO and the SET commands with environment variable references replaced by their corresponding values without or with string substitution.
The disadvantage of this solution is that CALL is designed primary for calling a batch file from within a batch file. For that reason the command lines above result in searching first in current directory and next in all directories of environment variable PATH for a file with name echo respectively set with a file extension of environment variable PATHEXT. That file searching behavior causes lots of file system accesses, especially on running those command lines in a FOR loop. If there is really found an executable or script file with file name echo or set, the executable respectively the script interpreter of the script file would be executed instead of the internal command of cmd.exe as usually done on using such a command line. So this solution is inefficient and not fail-safe on execution of the batch file.
Another workaround to avoid delayed expansion is using a subroutine:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "FolderPath=%SystemRoot%\System32"
for /F "skip=5 tokens=1,2,4 delims= " %%a in ('dir /AD /TC "%FolderPath%\."') do if "%%c"=="." call :ProcessCreationDate "%%a" "%%b"
endlocal
pause
exit /B
:ProcessCreationDate
echo %~1, %~2
set "Day=%~1"
set "Day=%Day:~0,2%
echo %Day%
goto :EOF
A subroutine is like another batch file embedded in current batch file.
The command line with exit /B avoids a fall through to the code of the subroutine.
The command line with goto :EOF would not be necessary if the line above is the last line of the batch file. But it is recommended to use it nevertheless in case of more command lines are ever added later below like a second subroutine.
The second batch file is for getting the day on which the specified folder was created. It would be possible to code this batch file without usage of delayed expansion and any workarounds.
#echo off
setlocal EnableExtensions DisableDelayedExpansion
set "FolderPath=%SystemRoot%\System32"
for /F "skip=5 tokens=1,2,4 delims= " %%a in ('dir /ad /tc "%FolderPath%\." 2^>nul') do if "%%c"=="." set "CreationDate=%%a, %%b" & goto OutputDateAndDay
echo Failed to get creation date of "%FolderPath%"
endlocal
pause
exit /B
:OutputDateAndDay
echo %CreationDate%
set "Day=%CreationDate:~0,2%
echo %Day%
endlocal
pause
Once the line of interest with the creation date of specified folder is found, the creation date/time is assigned to an environment variable and the FOR loop is exited with using command GOTO to continue execution on a label below. For the meaning of operator & see single line with multiple commands using Windows batch file.
This solution is better than all other methods because the FOR loop executes the single command line with the three commands IF, SET and GOTO only once which makes this solution the fastest. And it outputs an error message when it was not possible to determine the creation date of the directory because of the directory does not exist at all.
Of course it would be possible to add a GOTO command also on the other solutions to exit FOR loop once the creation date of the directory was determined and output. The last solution is nevertheless the fastest and in my point of view best one for this task.
BTW: All posted batch file examples were tested on Windows XP and produced the expected output.

CMD for-loop, while executing via default program, and piping

I have a directory filled with .nc files. These files have to be executed using a program called 'ncdump', which is set as the default program to open the file. It can then be executed as (output to file):
file.nc > output.txt
The file is somehow executed here. Now I want to do this for all the files in the directory using a for-loop. I tried the below:
for /r %i in (*) do cmd i% > scriptout.txt
And of course a way of numbering the outfile (with a counter no doubt) would be nice. This is probably very basic stuff but I'm rather unfamiliar with CMD (and Windows as a whole). Any help is much appreciated.
echo off
setlocal enabledelayedexpansion
set count=0
for /r %%i in (*.nc) do (
set count+=1
%%i >scriptout!count!.txt
)
echo %count% files processed.
some common traps:
in batchfiles use %%i (on command line only %i)
to use a variable inside a block (between ( and )) you have to use delayed expansion (setlocal enabledelayedexpansion to enable it and writing the variables !var! instead of %var%)
the opening parantheses (() has to be on the same line than do .

Concatenate file paths to an environment variable in batch script

I have made a bat script that should copy the list of folders to a variable but I don't get anything in the variable. In other words, when I echo the variable after my for loop I get the expected output, but in the shell outside after executing the script, I don't see anything set in my variable. How can I get all the variables to copy correctly?
I am using Windows 7.
Batch FIle (script.bat):
#echo off
setlocal enabledelayedexpansion enableextensions
for /r /D %%x in (*) do (
SET PATH_VALUE=%%x;!PATH_VALUE!
)
echo %PATH_VALUE%
Output of windows cmd utility
C:\test> script.bat
C:\test\1;C:\test\2
C:\test> echo %PATH_VALUE%
%PATH_VALUE%
How do I get the %PATH_VALUE% as an environment variable? I found a similar question here but it doesn't quite answer my case.
That is because of your SETLOCAL command that you use to enable delayed expansion. Yes it provides the delayed expansion you need, but it also localizes environment changes. As soon as your batch script ends, there is an implicit ENDLOCAL, and the old environment is restored.
You can pass the value across the ENDLOCAL barrier by adding the following to the end of your script:
endlocal&set "PATH_VALUE=%PATH_VALUE%"
or you could write it like:
(
endlocal
set "PATH_VALUE=%PATH_VALUE%"
)
Both of the above work because the blocks of code are expanded and parsed prior to the ENDLOCAL executing, but the SET statement with the expanded value is executed after the ENDLOCAL.

Resources