When is the CD environment variable updated? - batch-file

The tipp to use the CD environment variable from batch scripts to get the current working directory is commonly posted. But CD won't get updated when calling another batch file. Then the cd command echoes the new path of the other batch file, but %CD% (or !CD!) is not updated. Example:
#echo off
cd %~dp0
echo in %0: CD=%CD%
pause
call X:\testcall.cmd
Save this as C:\testcall.cmd and X:\testcall.cmd, then run C:\testcall.cmd. You should see that the value of CD has not changed. This seems not to dependend on call; none of the following works:
start /D <NEW_DIR> <OTHER_CMD_FILE>
start cmd /c <NEW_DIR>\<OTHER_CMD_FILE>
cmd /c <NEW_DIR>\<OTHER_CMD_FILE>
<NEW_DIR>\<OTHER_CMD_FILE>
cd %~dp0
pushd %~dp0
CD will keep it's old value, while cd (the command) shows the correct directory. Therefore I set CD at the begin of a script:
set CD=%~dp0
...while assuming cmd.exe sets CD only if this variable is yet unset. True?

%CD% is the current directory, while %~dp0 is the directory of the currently-running script (with trailing '\').
Also, don't set an env. var called CD, since it will override the default %CD% pseudo-var, and will be incredibly confusing - see OldNewThing - ERRORLEVEL is not %ERRORLEVEL%.
For example, when running c:\temp\a.cmd, which is:
#echo off
echo Currently running script: %~dpnx0
cd %~dp0
echo scriptDir=%~dp0, CD=%CD%
cd %~dp0a
echo scriptDir=%~dp0, CD=%CD%
set CD=bogus value
echo scriptDir=%~dp0, CD=%CD%
output:
Currently running script: c:\temp\a.cmd
scriptDir=c:\temp\, CD=c:\temp
scriptDir=c:\temp\, CD=c:\temp\a
scriptDir=c:\temp\, CD=bogus value

Diagnosis
You have at some point set the CD variable explicitly. If you do this it will no longer automatically reflect the current working directory. To undo this, set it to empty:
set CD=
It will then begin working again.
Why is this? Well, the automatic CD variable was introduced as a feature. I assume they just didn't want to break pre-existing scripts which already used that varible name. So if you set it explicitly, CMD will assume you are doing so on purpose.
Discussion
Firstly, if the parent process has an explicitly set CD variable, it will be inherited by the child processes.
On the other hand, you shouldn't expect any of these to update the value of %CD% for the parent process:
start /D <NEW_DIR> <OTHER_CMD_FILE>
start cmd /c <NEW_DIR>\<OTHER_CMD_FILE>
cmd /c <NEW_DIR>\<OTHER_CMD_FILE>
These all create new processes, the new process then changes its own working directory. You should not expect this to affect the parent process.
The final one, does not update the working directory at all, unless OTHER_CMD_FILE executes a CD command:
<NEW_DIR>\<OTHER_CMD_FILE>
Just because you executed a script in a different directory does not mean that the script's working directory will change. The script working directory does not have to be set to the location of the script.
Advice
Relying on the working directory being set to anything in particular is generally a bad idea.
You probably want something like this:
SET SCRIPT_DIR=%~dp0
Then use (for example) "%SCRIPT_DIR%\config.txt" to refer to a file in that directory.
Alternatively if you wish to rely on the current directory, use cd /d %~dp0

I build two batch files from your comment
test1.bat - located in C:\temp
#echo off
cd %~dp0
echo File %~f CD=%CD%
call X:\test2.bat
test2.bat - located in *X:*
#echo off
cd %~dp0
echo File %~f CD=%CD%
Starting test1 it from C:\temp, the output is
File C:\temp\test1.bat CD=C:\temp
File X:\test2.bat CD=C:\temp
The result is absolutly correct!
Starting a batch file (or any other program) with an absolute or relative path doesn't change the current directory.
The CD seems to fail, as CD can't change the drive the way you used it.
You need to add a switch CD /d %~dp0

You can set the %cd% variable to whatever you want, the real current directory for the C: drive is stored in the %=c:% variable, and you can't change this with the set command:
#echo off
echo Currently running script: %~dpnx0
cd %~dp0
echo scriptDir=%~dp0, CD=%CD%
set CD=bogus value
echo scriptDir=%~dp0, CD=%CD%, =c:=%=c:%
set =c:=bogus value
echo scriptDir=%~dp0, CD=%CD%, =c:=%=c:%
Output is:
Currently running script: C:\OldDir\a.bat
scriptDir=C:\OldDir\, CD=bogus value
scriptDir=C:\OldDir\, CD=bogus value, =c:=C:\OldDir
syntax error.
scriptDir=C:\OldDir\, CD=bogus value, =c:=C:\OldDir
The =C: variable for a child process is always set from the parent process. If you avoid the setlocal command in your script OR choose endlocal, you can change persistend the current directory for the current cmd session:
C:\OldDir>type script.bat
cd c:\newdir
C:\OldDir>script
C:\OldDir>cd c:\newdir
C:\NewDir>
.
C:\OldDir>type script.bat
setlocal
cd c:\newdir
C:\OldDir>script
C:\OldDir>setlocal
C:\OldDir>cd c:\newdir
C:\OldDir>

Using the script-directory %~dp0 can be a solution, but usually isn't. This works better:
cd >tmpfile
set /P CD= <tmpfile
del tmpfile
This solution is consistent with the CD variable. CD contains the current directory, not ending in a slash character if it is not in the root directory of the current drive. The path printed by cd behaves exactly so.
I'm using this for years and had no problems setting CD explicitly. Later I began using PWD (as in Bash). This variable is not reserved in MSDOS. So my question here was actually: "Can we get rid of these lines, or is this some MSDOS idiom?"
Some have written that setting CD explicitly is bad. Why? MSODS was never properly designed, and is not further developed. Anything you can do with it is legal. There is no bad MSDOS programming - just good and bad hacks. I know no other language where this perspective is legal to such an extent.

Related

How to use batch files to change directory?

I've noticed that any time I open up the command prompt, I tend to have to navigate to my git directory. So I'm trying to set up a simple macro to take me there. I've made a folder called C:\Macros and modified my %PATH% variable to point there. And I've added a file called gotoGit.bat.
Inside that batch file, I'm simply doing this :
#ECHO off
:: Check if a path is provided...
SET pathvar=%1
IF "%pathvar%"=="" (SET pathvar=some\default\dir)
:: Navigate...
CD /D C:\git\%pathvar%
But when I call it from the command line, I'm not navigating anywhere.
C:\Users\You> gotoGit
C:\Users\You>
Toggling ECHO on I can see that it is executing and creating the correct path, but it's not navigating me to C:\git\some\default\dir. The cd is only modifying the working directory inside the script, not my command line directory.
I'd like for it to navigate me to the right place :
C:\Users\You> gotoGit
C:\git\some\default\dir>
Any help is greatly appreciated.
Add the "cmd" term at the end example:
ECHO off
:: Check if a path is provided...
SET pathvar=%1
IF "%pathvar%"=="" (SET pathvar=some/default/dir)
:: Navigate...
CD /D C:/git/%pathvar%
cmd
It will open a command prompt with the directory you specified!
(If you are already in cmd it will just change the directory)
Based on the comments on the question, this suggestion ended up working for me.
#ECHO off
:: Check if a path is provided...
SET pathvar=%1
IF "%pathvar%"=="" (SET pathvar=some\default\dir)
:: Navigate...
CMD /K CD /D C:\git\%pathvar%

CMD/FTP to create folder using to today date & connect ftp download into created folder

I'm new to this cmd/FTP command. I would like to create a new folder at my local directory using today's date and connect to FTP to download the specific file to the newly created folder. If I manually type in command one by one at cmd, it has no issue. But when I use a batch file to run, my command stopped at FTP.
setlocal enableextensions
set name=%date:~-10,2%"-"%date:~7,2%"-"%date:~-4,4"_"job%
mkdir C:\%name%
cd C:\%name%
ftp
open 192.168.31.93
*user*
*password*
binary
cd *directory*
mget -i *.*
I did try to separate my command to two batches;
1. folder creation
2. FTP download but the file downloaded didn't go into the folder I created. the downloaded file went to C:\Document & Settings.
main batch file
#echo off
call rename.bat
ftp -i -s:ftp.txt
rename.bat
setlocal enableextensions
set name=%date:~-10,2%"-"%date:~7,2%"-"%date:~-4,4%"_job"
mkdir c:\%name%
cd c:\%name%
ftp.txt
open 192.168.31.93
*user*
*password*
binary
cd *directory*
mget *.*
close
Another method I try is using '!' when in FTP environment, then create a folder then exit back to FTP environment. This method again doesn't work with the batch file. Please help
It seems that with command extensions enabled, the working directory set by a child batch file is lost, then the batch file exits.
I'm not sure how to solve it, but you actually do not need the rename.bat file to be a separate file. This "main batch file" should work:
#echo off
setlocal enableextensions
set name=%date:~-10,2%"-"%date:~7,2%"-"%date:~-4,4%"_job"
mkdir c:\%name%
cd /d c:\%name%
ftp -i "-s:%~dp0\ftp.txt"
Also note the /d added to cd. Without that your batch will not work when started from another drive. You also have to use %~dp0 to refer to the batch file folder for the ftp.txt. As at the time ftp is called, you have changed to the target directory.
You possibly do not even need the command extensions to be enabled. So simply removing the setlocal enableextensions might solve the problem too. Though you still need the %~dp0 and /d.
I've decided to post this, although similar to the answer given, there are a couple of differences.
It creates the text file, then deletes it, (this keeps everything more portable).
I have corrected your directory name, (because of a typo).
#Echo Off
Set "Name=%DATE%"
Set "Name=%Name:~-10,2%-%Name:~-7,2%-%Name:~-4%_job"
MD "C:\%Name%" 2>Nul
CD /D "C:\%Name%" || Exit /B
( Echo open 192.168.31.93
Echo *user*
Echo *password*
Echo binary
Echo cd *directory*
Echo mget *.*
Echo close
)>"ftp.txt"
FTP -i -s:ftp.txt
Del "ftp.txt" 2>Nul
Exit /B

Batch file to read the root directory it is in

Is there a batch command that can read a root directory without the entire path?
I want a batch file to tell me if its in D:\ or E:\.
I tried to use:
set mypath=%cd%
#echo %mypath%
Pause
But it just says the exact place it is in rather than just the root.
Here's a few options for you which provide the root directory of the scripts current directory:
Using PushD/Popd
PushD\&Call Set "RootDir=%%CD%%"&PopD
Echo(%RootDir%
Pause
Using a For loop
For %%A In (%CD%) Do Set "RootDir=%%~dA\"
Echo(%RootDir%
Pause
Using variable expansion
Set "RootDir=%CD:~,3%"
Echo(%RootDir%
Pause
Edit
After reading your question again, I decided to add a fourth example. This one unlike the other three provides the root directory of the batch files location.
Set "RootDir=%~d0\"
Echo(%RootDir%
Pause
the directory where the batch file is located could be different from the current directory cmd.exe operates in.
TO get the batch file root path use:
for %%a in ("%0") do echo %%~da
To ger the current directory use
echo "%cd:~0,3%"
And let us not forget the &REM trick.
#ECHO OFF
set "root=%cd:\="&rem %
echo %root%

Start *.exe from specific dir, in specific dir

First, sorry for my English, but I try my best to explain the situation. I'm no real pro about *.bat files, but I know the basics to run exe files with it.
bat-script:
setlocal
cd "%~dp0"
start "" "%~dp0\Lang\Language.exe"
I need to start "Language.exe" inside "%~dp0" (root), where the *.bat is saved. I read many different questions/answers on stackoverflow, but none worked. The "Language.exe" is saved in "%~dp0\Lang", but it need to be run in "%~dp0" or it won't work.
The *.exe will only work from root (%~dp0), nowhere else. And there can't be any real folder structures like C:\root\Lang\Language.exe, because it have to work for others as well.
*.bat location --> root
file to start at *.bat location --> root\Lang\Language.exe
The "Language.exe" converts a language to some other with a diff-patch. I mean the *.bat starts the *.exe (also with other command variations I tried), but it says, that it can't find the files to patch. Yeah, because the so called working directory is not right (need to be "root"). But all that without moving the *.exe or anything, it should only be started in "root" from "root\Lang\Language.exe", nothing else.
EDIT:
As a workaround, I now simply move the "Language.exe", start it and move it back.
setlocal
cd "%~dp0"
move "Lang\Language.exe" "%~dp0" >nul
ECHO Starting patch...
timeout /t 1 >nul
start "" /wait "Language.exe"
move "%~dp0\Language.exe" "Lang" >nul
Set working directory
It is possible that you run into the issue discussed at In Batch file ~dp0 changes on changing directory.
I can think of 3 solutions.
Solution 1:
cd /D "%~dp0"
start "Language Patch" Lang\Language.exe
First the working directory is changed and then the EXE is called with a relative path. Parameter /D is necessary if current working directory on start is on a different drive than location of the batch file.
Solution 2:
setlocal
set "BatchPath=%~dp0"
cd /D "%BatchPath%"
start "Language Patch" "%BatchPath%Lang\Language.exe"
endlocal
The path of the folder containing the batch file is first assigned to an environment variable. This path ends with a backslash. Therefore the EXE can be called without a backslash before Lang.
Solution 3:
setlocal EnableExtensions
pushd "%~dp0"
start "Language Patch" Lang\Language.exe
popd
endlocal
push and popd are used in case of folder with batch file is not on a drive with a drive letter, but on a public network share. Read help of pushd and popd output by running in a command prompt window pushd /? and popd /? for details about those 2 commands.
Application directory used
But all those variants above do not help if Language.exe does not search for the files to patch in current working directory, but in its own application directory.
For example if Language.exe is written using Qt and uses inside the static function QCoreApplication::applicationDirPath() instead of searching for the files in current working directory, i.e. use QDir::currentPath() respectively search for the files without path and without changing working directory first.
Or if Language.exe is written using .Net and the application directory is used with one of the methods explained at How can I get the application's path in a .NET console application? instead of using current working directory.
In this case the best solution is copying Language.exe into the directory with the files to patch and delete the executable after it has terminated itself after patching all the files.
setlocal EnableExtensions
pushd "%~dp0"
copy /Y Lang\Language.exe .>nul
echo Starting patch...
Language.exe
del Language.exe
popd
endlocal

How to run a batch file in an another shell seamlessly

I have this build file:
#echo off
call "%VS120COMNTOOLS%"\\vsvars32.bat
echo Deleting old exe...
del build_win32\dist\bin\OgreApp_d.exe
cd build_win32
echo Building As Debug
msbuild /detailedsummary /p:Configuration=Debug /p:Platform=x86 /t:build ALL_BUILD.vcxproj
echo Done building, Exiting.
cd dist\bin\
start OgreApp_d.exe
cd ../../../
And to run it I run from cmd build. The problem is that since I do not exit cmd between builds, the PATH grows and grows because of "%VS120COMNTOOLS%"\\vsvars32.bat until it refuses to run because path is too long. Is there a way I can start this in another shell but in the same place.
I could do
start cmd.exe /K "build.bat"
but it opens a new window and it is more complicated than just build
The advice of Bill_Stewart is very good and very easy to apply:
#echo off
setlocal
call "%VS120COMNTOOLS%"\\vsvars32.bat
echo Deleting old exe...
del build_win32\dist\bin\OgreApp_d.exe
cd build_win32
echo Building As Debug
msbuild /detailedsummary /p:Configuration=Debug /p:Platform=x86 /t:build ALL_BUILD.vcxproj
echo Done building, Exiting.
cd dist\bin\
start OgreApp_d.exe
cd ../../../
endlocal
A new local copy of the environment variables table is made with command setlocal. All changes on environment variables are done now on this local copy instead of the environment variables of always running command line interpreter process. The temporary local table of the environment variables is deleted by command endlocal. So value of PATH is not growing and growing on every execution of this batch file.
By the way: vsvars32.bat is obviously coded poorly as it does not check on current PATH if a folder path to add is not already included in PATH. My answer on Why are other folder paths also added to system PATH with SetX and not only the specified folder path? contains the few code lines to check PATH for a folder path to add before appending it to PATH.

Resources