Resolve absolute path from relative path and/or file name - windows

Is there a way in a Windows batch script to return an absolute path from a value containing a filename and/or relative path?
Given:
"..\"
"..\somefile.txt"
I need the absolute path relative to the batch file.
Example:
"somefile.txt" is located in "C:\Foo\"
"test.bat" is located in "C:\Foo\Bar".
User opens a command window in "C:\Foo" and calls Bar\test.bat ..\somefile.txt
In the batch file "C:\Foo\somefile.txt" would be derived from %1

In batch files, as in standard C programs, argument 0 contains the path to the currently executing script. You can use %~dp0 to get only the path portion of the 0th argument (which is the current script) - this path is always a fully qualified path.
You can also get the fully qualified path of your first argument by using %~f1, but this gives a path according to the current working directory, which is obviously not what you want.
Personally, I often use the %~dp0%~1 idiom in my batch file, which interpret the first argument relative to the path of the executing batch. It does have a shortcoming though: it miserably fails if the first argument is fully-qualified.
If you need to support both relative and absolute paths, you can make use of Frédéric Ménez's solution: temporarily change the current working directory.
Here's an example that'll demonstrate each of these techniques:
#echo off
echo %%~dp0 is "%~dp0"
echo %%0 is "%0"
echo %%~dpnx0 is "%~dpnx0"
echo %%~f1 is "%~f1"
echo %%~dp0%%~1 is "%~dp0%~1"
rem Temporarily change the current working directory, to retrieve a full path
rem to the first parameter
pushd .
cd %~dp0
echo batch-relative %%~f1 is "%~f1"
popd
If you save this as c:\temp\example.bat and the run it from c:\Users\Public as
c:\Users\Public>\temp\example.bat ..\windows
...you'll observe the following output:
%~dp0 is "C:\temp\"
%0 is "\temp\example.bat"
%~dpnx0 is "C:\temp\example.bat"
%~f1 is "C:\Users\windows"
%~dp0%~1 is "C:\temp\..\windows"
batch-relative %~f1 is "C:\Windows"
the documentation for the set of modifiers allowed on a batch argument can be found here:
https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/call

I came across a similar need this morning: how to convert a relative path into an absolute path inside a Windows command script.
The following did the trick:
#echo off
set REL_PATH=..\..\
set ABS_PATH=
rem // Save current directory and change to target directory
pushd %REL_PATH%
rem // Save value of CD variable (current directory)
set ABS_PATH=%CD%
rem // Restore original directory
popd
echo Relative path: %REL_PATH%
echo Maps to path: %ABS_PATH%

Most of these answers seem crazy over complicated and super buggy, here's mine -- it works on any environment variable, no %CD% or PUSHD/POPD, or for /f nonsense -- just plain old batch functions. -- The directory & file don't even have to exist.
CALL :NORMALIZEPATH "..\..\..\foo\bar.txt"
SET BLAH=%RETVAL%
ECHO "%BLAH%"
:: ========== FUNCTIONS ==========
EXIT /B
:NORMALIZEPATH
SET RETVAL=%~f1
EXIT /B

Without having to have another batch file to pass arguments to (and use the argument operators), you can use FOR /F:
FOR /F %%i IN ("..\relativePath") DO echo absolute path: %%~fi
where the i in %%~fi is the variable defined at /F %%i. eg. if you changed that to /F %%a then the last part would be %%~fa.
To do the same thing right at the command prompt (and not in a batch file) replace %% with %...

This is to help fill in the gaps in Adrien Plisson's answer (which should be upvoted as soon as he edits it ;-):
you can also get the fully qualified path of your first argument by using %~f1, but this gives a path according to the current path, which is obviously not what you want.
unfortunately, i don't know how to mix the 2 together...
One can handle %0 and %1 likewise:
%~dpnx0 for fully qualified drive+path+name+extension of the batchfile itself,
%~f0 also suffices;
%~dpnx1 for fully qualified drive+path+name+extension of its first argument [if that's a filename at all],
%~f1 also suffices;
%~f1 will work independent of how you did specify your first argument: with relative paths or with absolute paths (if you don't specify the file's extension when naming %1, it will not be added, even if you use %~dpnx1 -- however.
But how on earth would you name a file on a different drive anyway if you wouldn't give that full path info on the commandline in the first place?
However, %~p0, %~n0, %~nx0 and %~x0 may come in handy, should you be interested in path (without driveletter), filename (without extension), full filename with extension or filename's extension only. But note, while %~p1 and %~n1 will work to find out the path or name of the first argument, %~nx1 and %~x1 will not add+show the extension, unless you used it on the commandline already.

You can also use batch functions for this:
#echo off
setlocal
goto MAIN
::-----------------------------------------------
:: "%~f2" get abs path of %~2.
::"%~fs2" get abs path with short names of %~2.
:setAbsPath
setlocal
set __absPath=%~f2
endlocal && set %1=%__absPath%
goto :eof
::-----------------------------------------------
:MAIN
call :setAbsPath ABS_PATH ..\
echo %ABS_PATH%
endlocal

Small improvement to BrainSlugs83's excellent solution. Generalized to allow naming the output environment variable in the call.
#echo off
setlocal EnableDelayedExpansion
rem Example input value.
set RelativePath=doc\build
rem Resolve path.
call :ResolvePath AbsolutePath %RelativePath%
rem Output result.
echo %AbsolutePath%
rem End.
exit /b
rem === Functions ===
rem Resolve path to absolute.
rem Param 1: Name of output variable.
rem Param 2: Path to resolve.
rem Return: Resolved absolute path.
:ResolvePath
set %1=%~dpfn2
exit /b
If run from C:\project output is:
C:\project\doc\build

I have not seen many solutions to this problem. Some solutions make use of directory traversal using CD and others make use of batch functions. My personal preference has been for batch functions and in particular, the MakeAbsolute function as provided by DosTips.
The function has some real benefits, primarily that it does not change your current working directory and secondly that the paths being evaluated don't even have to exist. You can find some helpful tips on how to use the function here too.
Here is an example script and its outputs:
#echo off
set scriptpath=%~dp0
set siblingfile=sibling.bat
set siblingfolder=sibling\
set fnwsfolder=folder name with spaces\
set descendantfolder=sibling\descendant\
set ancestorfolder=..\..\
set cousinfolder=..\uncle\cousin
call:MakeAbsolute siblingfile "%scriptpath%"
call:MakeAbsolute siblingfolder "%scriptpath%"
call:MakeAbsolute fnwsfolder "%scriptpath%"
call:MakeAbsolute descendantfolder "%scriptpath%"
call:MakeAbsolute ancestorfolder "%scriptpath%"
call:MakeAbsolute cousinfolder "%scriptpath%"
echo scriptpath: %scriptpath%
echo siblingfile: %siblingfile%
echo siblingfolder: %siblingfolder%
echo fnwsfolder: %fnwsfolder%
echo descendantfolder: %descendantfolder%
echo ancestorfolder: %ancestorfolder%
echo cousinfolder: %cousinfolder%
GOTO:EOF
::----------------------------------------------------------------------------------
:: Function declarations
:: Handy to read http://www.dostips.com/DtTutoFunctions.php for how dos functions
:: work.
::----------------------------------------------------------------------------------
:MakeAbsolute file base -- makes a file name absolute considering a base path
:: -- file [in,out] - variable with file name to be converted, or file name itself for result in stdout
:: -- base [in,opt] - base path, leave blank for current directory
:$created 20060101 :$changed 20080219 :$categories Path
:$source http://www.dostips.com
SETLOCAL ENABLEDELAYEDEXPANSION
set "src=%~1"
if defined %1 set "src=!%~1!"
set "bas=%~2"
if not defined bas set "bas=%cd%"
for /f "tokens=*" %%a in ("%bas%.\%src%") do set "src=%%~fa"
( ENDLOCAL & REM RETURN VALUES
IF defined %1 (SET %~1=%src%) ELSE ECHO.%src%
)
EXIT /b
And the output:
C:\Users\dayneo\Documents>myscript
scriptpath: C:\Users\dayneo\Documents\
siblingfile: C:\Users\dayneo\Documents\sibling.bat
siblingfolder: C:\Users\dayneo\Documents\sibling\
fnwsfolder: C:\Users\dayneo\Documents\folder name with spaces\
descendantfolder: C:\Users\dayneo\Documents\sibling\descendant\
ancestorfolder: C:\Users\
cousinfolder: C:\Users\dayneo\uncle\cousin
I hope this helps... It sure helped me :)
P.S. Thanks again to DosTips! You rock!

You can just concatenate them.
SET ABS_PATH=%~dp0
SET REL_PATH=..\SomeFile.txt
SET COMBINED_PATH=%ABS_PATH%%REL_PATH%
it looks odd with \..\ in the middle of your path but it works. No need to do anything crazy :)

In your example, from Bar\test.bat, DIR /B /S ..\somefile.txt would return the full path.

PowerShell is pretty common these days so I use it often as a quick way to invoke C# since that has functions for pretty much everything:
#echo off
set pathToResolve=%~dp0\..\SomeFile.txt
for /f "delims=" %%a in ('powershell -Command "[System.IO.Path]::GetFullPath( '%projectDirMc%' )"') do #set resolvedPath=%%a
echo Resolved path: %resolvedPath%
It's a bit slow, but the functionality gained is hard to beat unless without resorting to an actual scripting language.

stijn's solution works with subfolders under C:\Program Files (86)\,
#echo off
set projectDirMc=test.txt
for /f "delims=" %%a in ('powershell -Command "[System.IO.Path]::GetFullPath( '%projectDirMc%' )"') do #set resolvedPath=%%a
echo full path: %resolvedPath%

Files See all other answers
Directories
With .. being your relative path, and assuming you are currently in D:\Projects\EditorProject:
cd .. & cd & cd EditorProject (the relative path)
returns absolute path e.g.
D:\Projects

SET CD=%~DP0
SET REL_PATH=%CD%..\..\build\
call :ABSOLUTE_PATH ABS_PATH %REL_PATH%
ECHO %REL_PATH%
ECHO %ABS_PATH%
pause
exit /b
:ABSOLUTE_PATH
SET %1=%~f2
exit /b

Related

Does the current directory exist in %PATH%?

I tried so many possibilities to achieve this but I keep on getting errors.
I dont know why I can't pass %CD% and %PATH% to FINDSTR.
#echo off
findstr %cd% %path%
echo %errorlevel%
pause
The result is findstr can't take value from %path% because it is not a file so I tried to echo it to file.
#echo off
echo %path% > path.txt
findstr %cd% path.txt
echo %errorlevel%
pause
For now findstr could open path.txt but couldn't get the string to compare. The %cd% didn't appear to work so I tried to put it manually like this:
#echo off
echo %path% > path.txt
findstr c:\foo path.txt
echo %errorlevel%
pause
It works!
So how can I get the current directory value and pass the value to findstr? or more plainly, how do I detect if the current directory exists within %PATH% variable.
for %%a in (echo "%path:;=" "%") do if /i "%cd%"=="%%a" echo "found it"
should do this for most situations but there are exceptions.
path may contain relative paths (. or ..) which will not be detected.
path may contain "some;directory" which will not play nicely
and there is no requirement that the drivename appears in path.
So - use with caution.
Run the following small example script which uses FIND and FINDSTR with conditionals and also an IF/ELSE:
#Echo Off
Echo(%PATH%|Find/I "%CD%;">Nul&&(Echo(Found)||Echo(Not Found
Timeout 2
Echo(%PATH%|FindStr/I "%CD%;">Nul&&(Echo(Found)||Echo(Not Found
Timeout 2
SetLocal EnableDelayedExpansion
If /I "!PATH:%CD%;=!"=="!PATH!" (Echo(Not Found) Else Echo(Found
EndLocal
Timeout -1
So that is three different attempts at the same task, how do they work for you?
There is not any universal, simple, direct way to check if the current folder is included in the path variable using findstr because for each referenced folder inside path: it can be an absolute or relative reference, it can include or not an ending backslash, it can be or not quoted, it can include or not special characters, it can include (if quoted) semicolons, ...
In top of that, in order to use findstr to do the check you will need to handle problems with the backslash characters as they are used as escape characters in regular expressions but also in literals when preceding a non alphanumeric character. Try
echo x:\_uno\ | findstr /L /c:"x:\_uno" && echo Yes || echo No
So, you will need to process each value inside the path variable dealing with quoted semicolons, special characters, backslashes, ...
Fortunately this was solved by Jeb and dbenham in the 'Pretty print' windows %PATH% variable - how to split on ';' in CMD shell. Using their code to enumerate the elements it the path variable, and the approach in the Magoo's answer in this question, we can write somenthing like
#echo off
setlocal enableextensions disabledelayedexpansion
rem Flag variable. Assume current folder is not present in path variable
set "present="
rem This code uses:
rem Q: Pretty print %path% https://stackoverflow.com/q/5471556
rem A: Jeb answer https://stackoverflow.com/a/5472168
rem A: dbenham enhancement https://stackoverflow.com/a/7940444
set "var=%path:"=""%"
set "var=%var:^=^^%"
set "var=%var:&=^&%"
set "var=%var:|=^|%"
set "var=%var:<=^<%"
set "var=%var:>=^>%"
set "var=%var:;=^;^;%"
set var=%var:""="%
set "var=%var:"=""Q%"
set "var=%var:;;="S"S%"
set "var=%var:^;^;=;%"
set "var=%var:""="%"
setlocal EnableDelayedExpansion
set "var=!var:"Q=!"
rem Get a reference to current folder (%%c) and check against each
rem delimited value inside the processed variable
for %%c in (.) do for %%a in ("!var:"S"S=";"!") do (
if "!!"=="" endlocal
if %%a neq "" for %%b in ("%%~fa.") do (
if /i "%%~fb"=="%%~fc" set "present=1"
)
)
if defined present (
echo Current folder is INCLUDED in path variable
) else (
echo Current folder is NOT included in path variable
)
For each element in the path, resolve it to the full qualified path and check against the full qualified path of the current folder.

Remove unwanted path name from %path% variable via batch

Scope: Windows XP or newer
Tools: Batch script
I need to be able to remove an unneeded path name from the system %PATH% variable. I know how to add a new path name to the system %PATH% variable, using a tool such as SETX.EXE, which also makes it immediately available within the existing CMD environment. It's probably a matter of using FIND and/or a FOR loop of some kind, but I'm not quite sure how to accomplish this. Here's a sample path statement...
%PATH% = C:\;C:\Program Files\Common Files\Java;C:\oracle\product\10.2.0\bin;C:\WINDOWS;C:\WINDOWS\system32;
From this, I need to be able to remove the full path name related to "oracle." So, in the above example, I need to be able to remove the "C:\oracle\product\10.2.0\bin" from the above path statement. Unfortunately, not only could the oracle path name be different than shown above, there could be multiple oracle path names and all need to be removed. I tried implementing the solution here...
How can I extract a full path from the PATH environment variable?
However, it just isn't working. The script wouldn't find the path name. Any help would be appreciated. Thank you.
This removes the substring C:\Program Files (x86)\Git\bin; from the PATH string and re-assigns:
set PATH=%PATH:C:\Program Files (x86)\Git\bin;=%
You might use this to see the change:
echo %PATH:C:\Program Files (x86)\Git\bin;=% | tr ; \n
Note: be exact on the substring. It's case-sensitive and slash-sensitive.
If you need to make it a persistent change use setx instead of set and open another console for changes to take effect.
setx /M PATH "%PATH:C:\Program Files (x86)\Git\bin;=%"
You can try something like this :
#echo off&cls
setlocal EnableDelayedExpansion
set $line=%path%
set $line=%$line: =#%
set $line=%$line:;= %
for %%a in (%$line%) do echo %%a | find /i "oracle" || set $newpath=!$newpath!;%%a
set $newpath=!$newpath:#= !
echo set path=!$newpath:~1!
I putted an echo to the last line. Check the result and If it's OK for you, remove it.
After trying SachaDee's answers I got errors with paths like
C:\Program Files (x86)
with brackets:
Program Files (x86)\Directory
gave me
Directorywas unexpected at this time. (no matter what time I tried it)
I added
set $line=%$line:)=^^)%
before the for-loop and
set $newpath=!$newpath:^^=!
after the loop (not sure if it is necessary)
#echo off
setlocal EnableDelayedExpansion
set path
set $line=%path%
set $line=%$line: =#%
set $line=%$line:;= %
set $line=%$line:)=^^)%
for %%a in (%$line%) do echo %%a | find /i "oracle" || set $newpath=!$newpath!;%%a
set $newpath=!$newpath:#= !
set $newpath=!$newpath:^^=!
set path=!$newpath:~1!
And it is now working.
I found the other solutions to this problem a bit awkward, I don't really want to rely on exact paths, complex 'delayed expansion' syntax, removing spaces for the 'for /f' loop and then adding them back in...
I think this is more elegant, and I commented the hell out of it so even someone new to the horrors of Batch can follow along.
::Turn off command display and allows environmental variables to be overridden for the current session
#echo off & setlocal
::Creates a unique file to use for the 'for loop'
set "TMPFILE="%temp%\tmp%RANDOM%%RANDOM%.txt""
::Duplicate PATH into OLDPATH
set "OLDPATH=%PATH%"
::Declare label for the 'goto' command
:Loop
::Extract the first text token with the default delimiter of semicolon
for /f "tokens=1 delims=;" %%G in ("%OLDPATH%") do (
REM Copy text token to TMPFILE unless what we want to remove is found
<NUL set /p="%%G" | find /i "StRiNgThAtMaTcHeSwHaTtOrEmOvE" >NUL 2>&1 || <NUL set /p="%%G;" >>%TMPFILE%
REM Remove text token from OLDPATH
set "OLDPATH=%OLDPATH:*;=%"
)
::Repeat loop until OLDPATH no longer has any delimiters, and then add any remaining value to TMPFILE
echo %OLDPATH% | findstr /C:";" >NUL && (goto :Loop) || <NUL set /p="%OLDPATH%" >>%TMPFILE%
::Set the path to TMPFILE
for /f "usebackq delims=" %%G in (%TMPFILE%) do (set "PATH=%%G")
::Clean-up
del %TMPFILE% >NUL 2>&1
::An echo and pause just for debug purposes
echo %PATH%
pause
I use this in CYGWIN to filter out CYGWIN paths before starting some Windows commands:
export PATH=`perl -e '#a=grep {$_ =~ /^\/cygdrive\//} split(":", $ENV{PATH});print join(":",#a)'`
I'm quite sure it's easy to adapt to Windows-native perl and bat files. Advantage: the flexible power of regular expressions.
I wanted to remove %LocalAppData%\Microsoft\WindowsApps; from PATH. But this was not possible due to using a another variable in the environment variable for Windows. The CALL hack is worked in SS64. (Also, thanks to Jens A. Koch for the base command.)
CALL set PATH=%PATH:%LocalAppData%\Microsoft\WindowsApps;=%
Of course, the PATH changing by SET will not be permanent. For fixed change, it is necessary to use the SETX command or directly change the entries in the Registry.
Actually, this solution was not needed to delete %LocalAppData%\Microsoft\WindowsApps; from PATH.
The %LocalAppData%\Microsoft\WindowsApps; is stored in the PATH entry of the Registry's HKCU\Environment key. Although it is more practical to delete this entry with the REG DELETE command, if there are another directories in the PATH entry, they will also be deleted, so new solution is needed.
I failed to remove the %USERPROFILE% variable syntax from SET (The %% symbol dilemma). Fortunately, PShell came to the rescue:
SET userprofile=
Powershell -c "$UserEnvironmentPath = [System.Environment]::GetEnvironmentVariable('Path', 'User'); $UserEnvironmentPath = $UserEnvironmentPath.Replace('%USERPROFILE%\AppData\Local\Microsoft\WindowsApps;',''); [Microsoft.Win32.Registry]::SetValue('HKEY_CURRENT_USER\Environment', 'Path', $UserEnvironmentPath, [Microsoft.Win32.RegistryValueKind]::ExpandString)"
Special thanks to vonpryz for the last command. Because PowerShell's [System.Environment]::SetEnvironmentVariable command saves variables to Registry as REG_SZ even if their original value type is REG_EXPAND_SZ, which it's the known issue.
I wrote this code to simply remove any python executeable path from the path variable,
and insert my own specefic python version in the path so i can run python with
the versoin i wanted.
setlocal enableDelayedExpansion
set path`enter code here`
set $line=%path%
set $line=%$line: =#%
set $line=%$line:;= %
set $line=%$line:)=^^)%
set newpath=
for %%a in (%$line%) do (
echo %%a | find /i "python" ||set newpath=!newpath!;%%a
)
set path=!newpath!
set PATH=D:\python2.7\;%PATH%
#REM Rest of your script
python --version
#REM to exit the batch but not the window
exit /b
also, the first line is important! don't remove it or it wont work.
Notice: this code must run from a batch ".bat" file , if u want to copy paste this code in cmd window, you must replace all "%%a" to "%a" in this code.
If you know a file that exists within the directory you want to remove (e.g. want to remove all paths that might include java.exe), the following will work very straightforwardly by simply doing string replacement, no need to parse the path, etc:
#REM Executable to look for
set CMD=java.exe
:search
#REM Find the executable anywhere in the path
for %%a in (%CMD%) do set FOUND=%%~$PATH:a
if "%FOUND%"=="" goto done
#REM Strip \cmd.ext so we just have the directory
set FOUND=!FOUND:\%CMD%=!
#echo Found %CMD% in %FOUND%
#echo Removing %FOUND% from path...
set "PATH=!PATH:%FOUND%=!"
#REM Clean up any lone leftover \ in the path (in case the path was C:\foo\ instead of C:\foo)
set PATH=%PATH:;\;=;%
goto search
:done

batch File -How to check if path to a bin folder is set

I my batch file i want to check if path to the following location is set
C:\Program Files(x86)\Windows Installer XML v3.5\bin
in any of the variable either in system variable or user defined variable
my need is to use content of the bin folder ; what if the user has not installed the way quoted above but has done something like
C:\WindowsinstallerXML\bin
or
D:\WindowsInstaller\bin
If you want to check every defined variable for the string, this would be the way. If you want to specifically check the path variable replacing set with echo %path% would work too.
set | find "C:\Program Files(x86)\Windows Installer XML v3.5\bin" > NUL 2>&1 || goto badinstall
If you want to check the install directory, I suggest checking to see if there is a registry value for it, if there is you can pull it like this.
for /f "tokens=3 delims= " %%a in ('reg query \HKCU\path\to\subkey\here /v "keyhere" ^| findstr /C:"keyhere"') do set wixmlpath=%%a
You can use:
PATH="C:\Program Files(x86)\Windows Installer XML v3.5\bin";%PATH%
Basically, instead of checking if it is set, why not to set explicitly in your script ?
#echo off
set input=%1
IF DEFINED input (ECHO you entered %input%) ELSE (ECHO usage: script bin_file_path)
If you know the name of one of the binary files, then you can use a FOR variable ~PATH: modifier to check if the path to that binary is currently in your PATH. And it will give you the actual path :)
for %F in (someFile.exe) do #if "%~$path:F" neq "" echo %~$path:F
If you want to use the code in a batch file, then each % must be doubled as %%.

How to check if a directory exists in %PATH%

How does one check if a directory is already present in the PATH environment variable? Here's a start. All I've managed to do with the code below, though, is echo the first directory in %PATH%. Since this is a FOR loop you'd think it would enumerate all the directories in %PATH%, but it only gets the first one.
Is there a better way of doing this? Something like FIND or FINDSTR operating on the %PATH% variable? I'd just like to check if a directory exists in the list of directories in %PATH%, to avoid adding something that might already be there.
FOR /F "delims=;" %%P IN ("%PATH%") DO (
#ECHO %%~P
)
First I will point out a number of issues that make this problem difficult to solve perfectly. Then I will present the most bullet-proof solution I have been able to come up with.
For this discussion I will use lower case path to represent a single folder path in the file system, and upper case PATH to represent the PATH environment variable.
From a practical standpoint, most people want to know if PATH contains the logical equivalent of a given path, not whether PATH contains an exact string match of a given path. This can be problematic because:
The trailing \ is optional in a path
Most paths work equally well both with and without the trailing \. The path logically points to the same location either way. The PATH frequently has a mixture of paths both with and without the trailing \. This is probably the most common practical issue when searching a PATH for a match.
There is one exception: The relative path C: (meaning the current working directory of drive C) is very different than C:\ (meaning the root directory of drive C)
Some paths have alternate short names
Any path that does not meet the old 8.3 standard has an alternate short form that does meet the standard. This is another PATH issue that I have seen with some frequency, particularly in business settings.
Windows accepts both / and \ as folder separators within a path.
This is not seen very often, but a path can be specified using / instead of \ and it will function just fine within PATH (as well as in many other Windows contexts)
Windows treats consecutive folder separators as one logical separator.
C:\FOLDER\\ and C:\FOLDER\ are equivalent. This actually helps in many contexts when dealing with a path because a developer can generally append \ to a path without bothering to check if the trailing \ already exists. But this obviously can cause problems if trying to perform an exact string match.
Exceptions: Not only is C:, different than C:\, but C:\ (a valid path), is different than C:\\ (an invalid path).
Windows trims trailing dots and spaces from file and directory names.
"C:\test. " is equivalent to "C:\test".
The current .\ and parent ..\ folder specifiers may appear within a path
Unlikely to be seen in real life, but something like C:\.\parent\child\..\.\child\ is equivalent to C:\parent\child
A path can optionally be enclosed within double quotes.
A path is often enclosed in quotes to protect against special characters like <space> , ; ^ & =. Actually any number of quotes can appear before, within, and/or after the path. They are ignored by Windows except for the purpose of protecting against special characters. The quotes are never required within PATH unless a path contains a ;, but the quotes may be present never-the-less.
A path may be fully qualified or relative.
A fully qualified path points to exactly one specific location within the file system. A relative path location changes depending on the value of current working volumes and directories. There are three primary flavors of relative paths:
D: is relative to the current working directory of volume D:
\myPath is relative to the current working volume (could be C:, D: etc.)
myPath is relative to the current working volume and directory
It is perfectly legal to include a relative path within PATH. This is very common in the Unix world because Unix does not search the current directory by default, so a Unix PATH will often contain .\. But Windows does search the current directory by default, so relative paths are rare in a Windows PATH.
So in order to reliably check if PATH already contains a path, we need a way to convert any given path into a canonical (standard) form. The ~s modifier used by FOR variable and argument expansion is a simple method that addresses issues 1 - 6, and partially addresses issue 7. The ~s modifier removes enclosing quotes, but preserves internal quotes. Issue 7 can be fully resolved by explicitly removing quotes from all paths prior to comparison. Note that if a path does not physically exist then the ~s modifier will not append the \ to the path, nor will it convert the path into a valid 8.3 format.
The problem with ~s is it converts relative paths into fully qualified paths. This is problematic for Issue 8 because a relative path should never match a fully qualified path. We can use FINDSTR regular expressions to classify a path as either fully qualified or relative. A normal fully qualified path must start with <letter>:<separator> but not <letter>:<separator><separator>, where <separator> is either \ or /. UNC paths are always fully qualified and must start with \\. When comparing fully qualified paths we use the ~s modifier. When comparing relative paths we use the raw strings. Finally, we never compare a fully qualified path to a relative path. This strategy provides a good practical solution for Issue 8. The only limitation is two logically equivalent relative paths could be treated as not matching, but this is a minor concern because relative paths are rare in a Windows PATH.
There are some additional issues that complicate this problem:
9) Normal expansion is not reliable when dealing with a PATH that contains special characters.
Special characters do not need to be quoted within PATH, but they could be. So a PATH like
C:\THIS & THAT;"C:\& THE OTHER THING" is perfectly valid, but it cannot be expanded safely using simple expansion because both "%PATH%" and %PATH% will fail.
10) The path delimiter is also valid within a path name
A ; is used to delimit paths within PATH, but ; can also be a valid character within a path, in which case the path must be quoted. This causes a parsing issue.
jeb solved both issues 9 and 10 at 'Pretty print' windows %PATH% variable - how to split on ';' in CMD shell
So we can combine the ~s modifier and path classification techniques along with my variation of jeb's PATH parser to get this nearly bullet proof solution for checking if a given path already exists within PATH. The function can be included and called from within a batch file, or it can stand alone and be called as its own inPath.bat batch file. It looks like a lot of code, but over half of it is comments.
#echo off
:inPath pathVar
::
:: Tests if the path stored within variable pathVar exists within PATH.
::
:: The result is returned as the ERRORLEVEL:
:: 0 if the pathVar path is found in PATH.
:: 1 if the pathVar path is not found in PATH.
:: 2 if pathVar is missing or undefined or if PATH is undefined.
::
:: If the pathVar path is fully qualified, then it is logically compared
:: to each fully qualified path within PATH. The path strings don't have
:: to match exactly, they just need to be logically equivalent.
::
:: If the pathVar path is relative, then it is strictly compared to each
:: relative path within PATH. Case differences and double quotes are
:: ignored, but otherwise the path strings must match exactly.
::
::------------------------------------------------------------------------
::
:: Error checking
if "%~1"=="" exit /b 2
if not defined %~1 exit /b 2
if not defined path exit /b 2
::
:: Prepare to safely parse PATH into individual paths
setlocal DisableDelayedExpansion
set "var=%path:"=""%"
set "var=%var:^=^^%"
set "var=%var:&=^&%"
set "var=%var:|=^|%"
set "var=%var:<=^<%"
set "var=%var:>=^>%"
set "var=%var:;=^;^;%"
set var=%var:""="%
set "var=%var:"=""Q%"
set "var=%var:;;="S"S%"
set "var=%var:^;^;=;%"
set "var=%var:""="%"
setlocal EnableDelayedExpansion
set "var=!var:"Q=!"
set "var=!var:"S"S=";"!"
::
:: Remove quotes from pathVar and abort if it becomes empty
set "new=!%~1:"=!"
if not defined new exit /b 2
::
:: Determine if pathVar is fully qualified
echo("!new!"|findstr /i /r /c:^"^^\"[a-zA-Z]:[\\/][^\\/]" ^
/c:^"^^\"[\\][\\]" >nul ^
&& set "abs=1" || set "abs=0"
::
:: For each path in PATH, check if path is fully qualified and then do
:: proper comparison with pathVar.
:: Exit with ERRORLEVEL 0 if a match is found.
:: Delayed expansion must be disabled when expanding FOR variables
:: just in case the value contains !
for %%A in ("!new!\") do for %%B in ("!var!") do (
if "!!"=="" endlocal
for %%C in ("%%~B\") do (
echo(%%B|findstr /i /r /c:^"^^\"[a-zA-Z]:[\\/][^\\/]" ^
/c:^"^^\"[\\][\\]" >nul ^
&& (if %abs%==1 if /i "%%~sA"=="%%~sC" exit /b 0) ^
|| (if %abs%==0 if /i "%%~A"=="%%~C" exit /b 0)
)
)
:: No match was found so exit with ERRORLEVEL 1
exit /b 1
The function can be used like so (assuming the batch file is named inPath.bat):
set test=c:\mypath
call inPath test && (echo found) || (echo not found)
Typically the reason for checking if a path exists within PATH is because you want to append the path if it isn't there. This is normally done simply by using something like path %path%;%newPath%. But Issue 9 demonstrates how this is not reliable.
Another issue is how to return the final PATH value across the ENDLOCAL barrier at the end of the function, especially if the function could be called with delayed expansion enabled or disabled. Any unescaped ! will corrupt the value if delayed expansion is enabled.
These problems are resolved using an amazing safe return technique that jeb invented here: http://www.dostips.com/forum/viewtopic.php?p=6930#p6930
#echo off
:addPath pathVar /B
::
:: Safely appends the path contained within variable pathVar to the end
:: of PATH if and only if the path does not already exist within PATH.
::
:: If the case insensitive /B option is specified, then the path is
:: inserted into the front (Beginning) of PATH instead.
::
:: If the pathVar path is fully qualified, then it is logically compared
:: to each fully qualified path within PATH. The path strings are
:: considered a match if they are logically equivalent.
::
:: If the pathVar path is relative, then it is strictly compared to each
:: relative path within PATH. Case differences and double quotes are
:: ignored, but otherwise the path strings must match exactly.
::
:: Before appending the pathVar path, all double quotes are stripped, and
:: then the path is enclosed in double quotes if and only if the path
:: contains at least one semicolon.
::
:: addPath aborts with ERRORLEVEL 2 if pathVar is missing or undefined
:: or if PATH is undefined.
::
::------------------------------------------------------------------------
::
:: Error checking
if "%~1"=="" exit /b 2
if not defined %~1 exit /b 2
if not defined path exit /b 2
::
:: Determine if function was called while delayed expansion was enabled
setlocal
set "NotDelayed=!"
::
:: Prepare to safely parse PATH into individual paths
setlocal DisableDelayedExpansion
set "var=%path:"=""%"
set "var=%var:^=^^%"
set "var=%var:&=^&%"
set "var=%var:|=^|%"
set "var=%var:<=^<%"
set "var=%var:>=^>%"
set "var=%var:;=^;^;%"
set var=%var:""="%
set "var=%var:"=""Q%"
set "var=%var:;;="S"S%"
set "var=%var:^;^;=;%"
set "var=%var:""="%"
setlocal EnableDelayedExpansion
set "var=!var:"Q=!"
set "var=!var:"S"S=";"!"
::
:: Remove quotes from pathVar and abort if it becomes empty
set "new=!%~1:"^=!"
if not defined new exit /b 2
::
:: Determine if pathVar is fully qualified
echo("!new!"|findstr /i /r /c:^"^^\"[a-zA-Z]:[\\/][^\\/]" ^
/c:^"^^\"[\\][\\]" >nul ^
&& set "abs=1" || set "abs=0"
::
:: For each path in PATH, check if path is fully qualified and then
:: do proper comparison with pathVar. Exit if a match is found.
:: Delayed expansion must be disabled when expanding FOR variables
:: just in case the value contains !
for %%A in ("!new!\") do for %%B in ("!var!") do (
if "!!"=="" setlocal disableDelayedExpansion
for %%C in ("%%~B\") do (
echo(%%B|findstr /i /r /c:^"^^\"[a-zA-Z]:[\\/][^\\/]" ^
/c:^"^^\"[\\][\\]" >nul ^
&& (if %abs%==1 if /i "%%~sA"=="%%~sC" exit /b 0) ^
|| (if %abs%==0 if /i %%A==%%C exit /b 0)
)
)
::
:: Build the modified PATH, enclosing the added path in quotes
:: only if it contains ;
setlocal enableDelayedExpansion
if "!new:;=!" neq "!new!" set new="!new!"
if /i "%~2"=="/B" (set "rtn=!new!;!path!") else set "rtn=!path!;!new!"
::
:: rtn now contains the modified PATH. We need to safely pass the
:: value accross the ENDLOCAL barrier
::
:: Make rtn safe for assignment using normal expansion by replacing
:: % and " with not yet defined FOR variables
set "rtn=!rtn:%%=%%A!"
set "rtn=!rtn:"=%%B!"
::
:: Escape ^ and ! if function was called while delayed expansion was enabled.
:: The trailing ! in the second assignment is critical and must not be removed.
if not defined NotDelayed set "rtn=!rtn:^=^^^^!"
if not defined NotDelayed set "rtn=%rtn:!=^^^!%" !
::
:: Pass the rtn value accross the ENDLOCAL barrier using FOR variables to
:: restore the % and " characters. Again the trailing ! is critical.
for /f "usebackq tokens=1,2" %%A in ('%%^ ^"') do (
endlocal & endlocal & endlocal & endlocal & endlocal
set "path=%rtn%" !
)
exit /b 0
This may work:
echo ;%PATH%; | find /C /I ";<string>;"
It should give you 0 if the string is not found and 1 or more if it is.
Another way to check if something is in the path is to execute some innocent executable that is not going to fail if it's there, and check the result.
As an example, the following code snippet checks if Maven is in the path:
mvn --help > NUL 2> NUL
if errorlevel 1 goto mvnNotInPath
So I try to run mvn --help, ignore the output (I don't actually want to see the help if Maven is there) (> NUL), and also don't display the error message if Maven was not found (2> NUL).
After reading the answers here I think I can provide a new point of view: if the purpose of this question is to know if the path to a certain executable file exists in %PATH% and if not, insert it (and this is the only reason to do that, I think), then it may solved in a slightly different way: "How to check if the directory of a certain executable program exist in %PATH%"? This question may be easily solved this way:
for %%p in (programname.exe) do set "progpath=%%~$PATH:p"
if not defined progpath (
rem The path to programname.exe don't exist in PATH variable, insert it:
set "PATH=%PATH%;C:\path\to\progranname"
)
If you don't know the extension of the executable file, just review all of them:
set "progpath="
for %%e in (%PATHEXT%) do (
if not defined progpath (
for %%p in (programname.%%e) do set "progpath=%%~$PATH:p"
)
)
Using for and delims, you cannot capture an arbitrary number of fields (as Adam pointed out as well) so you have to use a looping technique instead. The following command script will list each path in the PATH environment variable on a separate line:
#echo off
setlocal
if "%~1"=="" (
set PATHQ=%PATH%
) else (
set PATHQ=%~1 )
:WHILE
if "%PATHQ%"=="" goto WEND
for /F "delims=;" %%i in ("%PATHQ%") do echo %%i
for /F "delims=; tokens=1,*" %%i in ("%PATHQ%") do set PATHQ=%%j
goto WHILE
:WEND
It simulates a classical while…wend construct found in many programming languages.
With this in place, you can use something like findstr to subsequently filter and look for a particular path. For example, if you saved the above script in a file called tidypath.cmd then here is how you could pipe to findstr, looking for paths under the standard programs directory (using a case-insensitive match):
> tidypath | findstr /i "%ProgramFiles%"
This will look for an exact but case-insensitive match, so mind any trailing backslashes etc.:
for %P in ("%path:;=";"%") do #if /i %P=="PATH_TO_CHECK" echo %P exists in PATH
or, in a batch file (e.g. checkpath.bat) which takes an argument:
#for %%P in ("%path:;=";"%") do #if /i %%P=="%~1" echo %%P exists in PATH
In the latter form, one could call e.g. checkpath "%ProgramFiles%" to see if the specified path already exists in PATH.
Please note that this implementation assumes no semicolons or quotes are present inside a single path item.
Just to elaborate on Heyvoon's response (2015-06-08) using PowerShell, this simple PowerShell script should give you detail on %path%:
$env:Path -split ";" | % {"$(test-path $_); $_"}
It is generating this kind of output which you can independently verify:
True;C:\WINDOWS
True;C:\WINDOWS\system32
True;C:\WINDOWS\System32\Wbem
False;C:windows\System32\windowsPowerShell\v1.0\
False;C:\Program Files (x86)\Java\jre7\bin
To reassemble for updating Path:
$x = $null; foreach ($t in ($env:Path -split ";") ) {if (test-path $t) {$x += $t + ";"}}; $x
I took your implementation using the for loop and extended it into something that iterates through all elements of the path. Each iteration of the for loop removes the first element of the path (%p) from the entire path (held in %q and %r).
#echo off
SET MYPATHCOPY=%PATH%
:search
for /f "delims=; tokens=1,2*" %%p in ("%MYPATHCOPY%") do (
#echo %%~p
SET MYPATHCOPY=%%~q;%%~r
)
if "%MYPATHCOPY%"==";" goto done;
goto search;
:done
Sample output:
Z:\>path.bat
C:\Program Files\Microsoft DirectX SDK (November 2007)\Utilities\Bin\x86
c:\program files\imagemagick-6.3.4-q16
C:\WINDOWS\system32
C:\WINDOWS
C:\SFU\common\
c:\Program Files\Debugging Tools for Windows
C:\Program Files\Nmap
You can also use substring replacement to test for the presence of a substring. Here I remove quotes to create PATH_NQ, then I remove "c:\mydir" from the PATH_NQ and compare it to the original to see if anything changed:
set PATH_NQ=%PATH:"=%
if not "%PATH_NQ%"=="%PATH_NQ:c:\mydir=%" goto already_in_path
set PATH=%PATH%;c:\mydir
:already_in_path
I've combined some of the above answers to come up with this to ensure that a given path entry exists exactly as given with as much brevity as possible and no junk echos on the command line.
set myPath=<pathToEnsure | %1>
echo ;%PATH%; | find /C /I ";%myPath%;" >nul
if %ERRORLEVEL% NEQ 0 set PATH=%PATH%;%myPath%
If your question was "why doesn't this cmd script fragment work?" then the answer is that for /f iterates over lines. The delims split lines into fields, but you're only capturing the first field in %%P. There is no way to capture an arbitrary number of fields with a for /f loop.
This version works fairly well. It simply checks whether executable vim71 (Vim 7.1) is in the path, and prepends it if not.
#echo off
echo %PATH% | find /c /i "vim71" > nul
if not errorlevel 1 goto jump
PATH = C:\Program Files\Vim\vim71\;%PATH%
:jump
This demo is to illustrate the errorlevel logic:
#echo on
echo %PATH% | find /c /i "Windows"
if "%errorlevel%"=="0" echo Found Windows
echo %PATH% | find /c /i "Nonesuch"
if "%errorlevel%"=="0" echo Found Nonesuch
The logic is reversed in the vim71 code since errorlevel 1 is equivalent to errorlevel >= 1. It follows that errorlevel 0 would always evaluate true, so "not errorlevel 1" is used.
Postscript: Checking may not be necessary if you use SETLOCAL and ENDLOCAL to localise your environment settings, e.g.,
#echo off
setlocal
PATH = C:\Program Files\Vim\vim71\;%PATH%
rem your code here
endlocal
After ENDLOCAL you are back with your original path.
You mention that you want to avoid adding the directory to search path if it already exists there. Is your intention to store the directory permanently to the path, or just temporarily for batch file's sake?
If you wish to add (or remove) directories permanently to PATH, take a look at Path Manager (pathman.exe) utility in Windows Resource Kit Tools for administrative tasks, http://support.microsoft.com/kb/927229. With that you can add or remove components of both system and user paths, and it will handle anomalies such as duplicate entries.
If you need to modify the path only temporarily for a batch file, I would just add the extra path in front of the path, with the risk of slight performance hit because of duplicate entry in the path.
Just as an alternative:
In the folder you are going to search the PATH variable for, create a temporary file with such an unusual name that you would never ever expect any other file on your computer to have.
Use the standard batch scripting construct that lets you perform the search for a file by looking up a directory list defined by some environment variable (typically PATH).
Check if the result of the search matches the path in question, and display the outcome.
Delete the temporary file.
This might look like this:
#ECHO OFF
SET "mypath=D:\the\searched-for\path"
SET unusualname=nowthisissupposedtobesomeveryunusualfilename
ECHO.>"%mypath%\%unusualname%"
FOR %%f IN (%unusualname%) DO SET "foundpath=%%~dp$PATH:f"
ERASE "%mypath%\%unusualname%"
IF "%mypath%" == "%foundpath%" (
ECHO The dir exists in PATH
) ELSE (
ECHO The dir DOES NOT exist in PATH
)
Known issues:
The method can work only if the directory exists (which isn't always the case).
Creating / deleting files in a directory affects its 'modified date/time' attribute (which may be an undesirable effect sometimes).
Making up a globally unique file name in one's mind cannot be considered very reliable. Generating such a name is itself not a trivial task.
You can accomplish this using PowerShell;
Test-Path $ENV:SystemRoot\YourDirectory
Test-Path C:\Windows\YourDirectory
This returns TRUE or FALSE
Short, simple and easy!
Building on Randy's answer, you have to make sure a substring of the target isn't found.
if a%X%==a%PATH% echo %X% is in PATH
echo %PATH% | find /c /i ";%X%"
if errorlevel 1 echo %X% is in PATH
echo %PATH% | find /c /i "%X%;"
if errorlevel 1 echo %X% is in PATH
Add the directory to PATH if it does not already exist:
set myPath=c:\mypath
For /F "Delims=" %%I In ('echo %PATH% ^| find /C /I "%myPath%"') Do set pathExists=%%I 2>Nul
If %pathExists%==0 (set PATH=%myPath%;%PATH%)
In general, this is to add an EXE or DLL file on the path. As long as this file won’t appear anywhere else:
#echo off
where /q <put filename here>
if %errorlevel% == 1 (
setx PATH "%PATH%;<additional path stuff>"
) else (
echo "already set path"
)
This is a variation of Kevin Edwards's answer using string replacement.
The basic pattern is:
IF "%PATH:new_path=%" == "%PATH%" PATH=%PATH%;new_path
For example:
IF "%PATH:C:\Scripts=%" == "%PATH%" PATH=%PATH%;C:\Scripts
In a nutshell, we make a conditional test where we attempt to remove/replace new_path from our PATH environment variable. If new_path doesn't exist, the condition succeeds and the new_path will be appended to PATH for the first time. If new_path already exists then the condition fails and we will not add new_path a second time.
rem https://stackoverflow.com/a/59571160/2292993
rem Don't get mess with %PATH%, it is a concatenation of USER+SYSTEM, and will cause a lot of duplication in the result.
for /f "usebackq tokens=2,*" %%A in (`reg query HKCU\Environment /v PATH`) do set userPATH=%%B
rem userPATH should be %USERPROFILE%\AppData\Local\Microsoft\WindowsApps
rem https://stackoverflow.com/questions/141344
for /f "delims=" %%A in ('echo ";%userPATH%;" ^| find /C /I ";%WINAPPS%;"') do set pathExists=%%A
If %pathExists%==0 (
echo Inserting user path...
setx PATH "%WINAPPS%; %userPATH%"
)
-contains worked for me
$pathToCheck = "c:\some path\to\a\file.txt"
$env:Path - split ';' -contains $pathToCheck
To add the path when it does not exist yet I use
$pathToCheck = "c:\some path\to\a\file.txt"
if(!($env:Path -split ';' -contains $vboxPath)) {
$documentsDir = [Environment]::GetFolderPath("MyDocuments")
$profileFilePath = Join-Path $documentsDir "WindowsPowerShell/profile.ps1"
Out-File -FilePath $profileFilePath -Append -Force -Encoding ascii -InputObject "`$env:Path += `";$pathToCheck`""
Invoke-Expression -command $profileFilePath
}
This routine will search for a path\ or file.ext in the path variable.
It returns 0 if found. Path\ or file may contain spaces if quoted.
If a variable is passed as the last argument, it will be set to d:\path\file.
#echo off&goto :PathCheck
:PathCheck.CMD
echo.PathCheck.CMD: Checks for existence of a path or file in %%PATH%% variable
echo.Usage: PathCheck.CMD [Checkpath] or [Checkfile] [PathVar]
echo.Checkpath must have a trailing \ but checkfile must not
echo.If Checkpath contains spaces use quotes ie. "C:\Check path\"
echo.Checkfile must not include a path, just the filename.ext
echo.If Checkfile contains spaces use quotes ie. "Check File.ext"
echo.Returns 0 if found, 1 if not or -1 if checkpath does not exist at all
echo.If PathVar is not in command line it will be echoed with surrounding quotes
echo.If PathVar is passed it will be set to d:\path\checkfile with no trailing \
echo.Then %%PathVar%% will be set to the fully qualified path to Checkfile
echo.Note: %%PathVar%% variable set will not be surrounded with quotes
echo.To view the path listing line by line use: PathCheck.CMD /L
exit/b 1
:PathCheck
if "%~1"=="" goto :PathCheck.CMD
setlocal EnableDelayedExpansion
set "PathVar=%~2"
set "pth="
set "pcheck=%~1"
if "%pcheck:~-1%" equ "\" (
if not exist %pcheck% endlocal&exit/b -1
set/a pth=1
)
for %%G in ("%path:;=" "%") do (
set "Pathfd=%%~G\"
set "Pathfd=!Pathfd:\\=\!"
if /i "%pcheck%" equ "/L" echo.!Pathfd!
if defined pth (
if /i "%pcheck%" equ "!Pathfd!" endlocal&exit/b 0
) else (
if exist "!Pathfd!%pcheck%" goto :CheckfileFound
)
)
endlocal&exit/b 1
:CheckfileFound
endlocal&(
if not "%PathVar%"=="" (
call set "%~2=%Pathfd%%pcheck%"
) else (echo."%Pathfd%%pcheck%")
exit/b 0
)

Loop over folder string and parse out last folder name

I need to grab the folder name of a currently executing batch file. I have been trying to loop over the current directory using the following syntax (which is wrong at present):
set mydir = %~p0
for /F "delims=\" %i IN (%mydir%) DO #echo %i
Couple of issues in that I cannot seem to pass the 'mydir' variable value in as the search string. It only seems to work if I pass in commands; I have the syntax wrong and cannot work out why.
My thinking was to loop over the folder string with a '\' delimiter but this is causing problems too. If I set a variable on each loop then the last value set will be the current folder name. For example, given the following path:
C:\Folder1\Folder2\Folder3\Archive.bat
I would expect to parse out the value 'Folder3'.
I need to parse that value out as its name will be part of another folder I am going to create further down in the batch file.
Many thanks if anyone can help. I may be barking up the wrong tree completely so any other approaches would be greatly received also.
After struggling with some of these suggestions, I found an successfully used the following 1 liner (in windows 2008)
for %%a in (!FullPath!) do set LastFolder=%%~nxa
You were pretty close to it :) This should work:
#echo OFF
set mydir="%~p0"
SET mydir=%mydir:\=;%
for /F "tokens=* delims=;" %%i IN (%mydir%) DO call :LAST_FOLDER %%i
goto :EOF
:LAST_FOLDER
if "%1"=="" (
#echo %LAST%
goto :EOF
)
set LAST=%1
SHIFT
goto :LAST_FOLDER
For some reason the for command doesn't like '\' as a delimiter, so I converted all '\' to ';' first (SET mydir=%mydir:\=;%)
I found this old thread when I was looking to find the last segment of the current directory.
The previous writers answers lead me to the following:
FOR /D %%I IN ("%CD%") DO SET _LAST_SEGMENT_=%%~nxI
ECHO Last segment = "%_LAST_SEGMENT_%"
As previous have explained, don't forget to put quotes around any paths create with %_LAST_SEGMENT_% (just as I did with %CD% in my example).
Hope this helps someone...
This question's a little old, but I've looked for a solution more than once so here's a completely new take on it that I've just put together.
The trick is that we take the desired path, back up one level to create a folder mask for substitution and then replace the folder mask with nothing.
To test it, simple copy and paste into a command script (.cmd) in any directory, then run it. It will spit out only the deepest directory you're currently in.
Notes:
Replace %~dp0 with whatever path you like (as it is, it will return the deepest folder the batch file is run from. This is not the same as %cd%.)
When specifying the 'pathtofind' variable ensure there are no quotes e.g. c:\some path and not "c:\some path".
The original idea for folder masking is mine
Spaces in the path are no problem
Folder depth is not a problem
It was made possible by the genius of this batch scripting tip http://www.dostips.com/DtCodeBatchFiles.php#Batch.FindAndReplace
Hope this helps someone else.
#echo off
set pathtofind=%~dp0
if not exist %pathtofind% echo Path does not exist&pause>nul&goto :eof
cd /d %pathtofind%
set path1=%cd%
cd ..
set path2=%cd%
call set "path3=%%path1:%path2%\=%%"
echo %path3%
pause>nul
3 lines of script gets the result...
Found 2 additional ways to accomplish the goal, and unlike the other answers to this question, it requires no batch "functions", no delayed expansion, and also does not have the limitation that Tim Peel's answer has with directory deepness :
#echo off
SET CDIR=%~p0
SET CDIR=%CDIR:~1,-1%
SET CDIR=%CDIR:\=,%
SET CDIR=%CDIR: =#%
FOR %%a IN (%CDIR%) DO SET "CNAME=%%a"
ECHO Current directory path: %CDIR%
SET CNAME=%CNAME:#= %
ECHO Current directory name: %CNAME%
pause
REVISION: after my new revsion, here is an example output:
Current directory path: Documents#and#Settings,username,.sqldeveloper,tmp,my_folder,MY.again
Current directory name: MY.again
Press any key to continue . . .
This means that the script doesn't handle '#' or ',' in a folder name but can be adjusted to do so.
ADDENDUM: After asking someone in the dostips forum, found an even easier way to do it:
#echo off
SET "CDIR=%~dp0"
:: for loop requires removing trailing backslash from %~dp0 output
SET "CDIR=%CDIR:~0,-1%"
FOR %%i IN ("%CDIR%") DO SET "PARENTFOLDERNAME=%%~nxi"
ECHO Parent folder: %PARENTFOLDERNAME%
ECHO Full path: %~dp0
pause>nul
To return to the original poster's issue:
For example, given the following path:
C:\Folder1\Folder2\Folder3\Archive.bat
I would expect to parse out the value 'Folder3'.
The simple solution for that is:
for /D %%I in ("C:\Folder1\Folder2\Folder3\Archive.bat\..") do echo parentdir=%%~nxI
will give 'Folder3'. The file/path does not need to exist. Of course, .... for the parent's parent dir, or ...... for the one above that (and so on) work too.
Slight alteration for if any of the folders have spaces in their names - replace space to ':' before and after operation:
set mydir="%~p0"
set mydir=%mydir:\=;%
set mydir=%mydir: =:%
for /F "tokens=* delims=;" %%i IN (%mydir%) DO call :LAST_FOLDER %%i
goto :EOF
:LAST_FOLDER
if "%1"=="" (
set LAST=%LAST::= %
goto :EOF
)
set LAST=%1
SHIFT
goto :LAST_FOLDER
Sheesh guys, what a mess. This is pretty easy, and it's faster to do this in memory without CD.
This gets the last two directories of a path. Modify it as required to get the last tokens of any line. My original code I based this on has more complexity for my own purposes.
Fyi, this probably doesn't allow paths with exclamation marks since I'm using enabledelayedexpansion, but that could be fixed.
It also won't work on a plain drive root. This could be averted in a number of ways. Check what the input path ends with, or a counter, or modifying the token and check behaviour, etc.
#echo off&setlocal enableextensions,enabledelayedexpansion
call :l_truncpath "C:\Windows\temp"
----------
:l_truncpath
set "_pathtail=%~1"
:l_truncpathloop
for /f "delims=\ tokens=1*" %%x in ("!_pathtail!") do (
if "%%y"=="" (
set "_result=!_path!\!_pathtail!"
echo:!_result!
exit/b
)
set "_path=%%x"
set "_pathtail=%%y"
)
goto l_truncpathloop
I modified answer given by #Jonathan, since it did not work for me in a batch file, but this below does work, and also supports folders with spaces in it.:
for %%a in ("%CD%") do set LastFolder=%%~nxa
echo %LastFolder%
This takes the current directory and echoes the last, deepest folder, as in below example, if the folder is this:
C:\Users\SuperPDX\OneDrive\Desktop Environment\
The batch code echoes this: Desktop Environment
In batch files in the FOR command you'll need to prepend %whatever with an extra % (e.g. %%whatever).
'echo %~p0' will print the currently directory of the batch file.
This is what we had in the end (little bit more crude and can only go so deep :)
#echo off
for /f "tokens=1-10 delims=\" %%A in ('echo %~p0') do (
if NOT .%%A==. set new=%%A
if NOT .%%B==. set new=%%B
if NOT .%%C==. set new=%%C
if NOT .%%D==. set new=%%D
if NOT .%%E==. set new=%%E
if NOT .%%F==. set new=%%F
if NOT .%%G==. set new=%%G
if NOT .%%H==. set new=%%H
if NOT .%%I==. set new=%%I
if NOT .%%J==. set new=%%J
)
#echo %new%
I don't know if it's the version of windows I'm on (win2k3), but the FOR loop isn't giving me anything useful for trying to iterate through a single string.
According to my observation (and the FOR /? info) you get one iteration for each line of input to FOR, and there is no way to change this to iterate within a line. You can break into multiple tokens for a given line, but it is only one invocation of the FOR loop body.
I do think the CALL :LABEL approach in these answers does a great job. Something I didn't know until looking at this was that ";" and "," are both recognized as argument separators. So once you replace backslashes with semicolons, you can call your label and iterate through with SHIFT.
So working off of what is posted by others here, I have the below solution. Instead of grabbing the last folder name, I actually wanted to find everything up until some known directory name.. this is what is implemented below.
#echo off
if "%1"=="" goto :USAGE
set FULLPATH=%~f1
set STOPDIR=%2
set PATHROOT=
:: Replace backslashes with semicolons
set FULLPATH=%FULLPATH:\=;%
:: Iterate through path (the semicolons cause each dir name to be a new argument)
call :LOOP %FULLPATH%
goto :EOF
:LOOP
::Exit loop if reached the end of the path, or the stop dir
if "%1"=="" (goto :EOF)
if "%1"=="%STOPDIR%" (goto :EOF)
::If this is the first segment of the path, set value directly. Else append.
if not defined PATHROOT (set PATHROOT=%1) else (set PATHROOT=%PATHROOT%\%1)
::shift the arguments - the next path segment becomes %i
SHIFT
goto :LOOP
:USAGE
echo Usage:
echo %~0 ^<full path to parse^> ^<dir name to stop at^>
echo E.g. for a command:
echo %~0 c:\root1\child1\child2 child2
echo The value of c:\root1\child1 would be assigned to env variable PATHROOT
Unfortunatelly, this is working great only when put on some depth but have problems with being on the very top of the mountain... Putting this program into "C:\Windows" e.g. will result with... "C:\Windows", not expected "Windows". Still great job, and still damage can be repaired. My approach:
#echo off
set pathtofind=%~dp0
if not exist %pathtofind% echo Path does not exist&pause>nul&goto :eof
cd /d %pathtofind%
set path1=%cd%
cd ..
set path2=%cd%
set path4=%~dp1
call set "path3=%%path1:%path2%\=%%"
call set "path5=%%path3:%path4%*\=%%"
echo %path5%
pause>nul
And it's working just fine for me now, thanks for the idea, I was looking for something like that for some time.

Resources