Manipulate path string BATCH - windows

I need to process a full path to a file, i.e. C:\fold1\fold2...\foldN\filename.dat in order to get the parent file folder and file name separately, that is root=C:\fold1\fold2...\foldN and file=filaname.dat.
This because I want to generalise a call to a program no matter from where you call the main .bat (this one), such to be able to perform a pushd to %root% before calling this specific program.
I am working at this, but I get stack when posing line=%%c:
: before remove quotes from %var%
set var=%var:"=%
if exist %temp% (
if exist %temp%\filepath.txt del %temp%\filepath.txt
echo %var% > %temp%\filepath.txt
)
for /f "tokens=1,* delims= " %%z in (%temp%\filepath.txt) do (
set line=%%z
echo Line writes %line%.
set root=empty
goto :processtoken
)
:processtoken
for /f "tokens=1,* delims=\" %%b in ("%line%") do (
echo %%b
echo %%c
set line=%%c rem this does not work, why??
echo Now line writes %line%
if "%root%"=="empty" (
echo [INFO] Root is empty, initialising..
echo %%c
set root=%%c
) else (
set root=%root%\%%c
)
echo Root is %root%
goto :end
)
The problem I am facing is that when I print to see how %line% has changed, it shows that %line% (after set line=%%c corresponds to the FULL PATH (while my intention is to recursively get to the file name, I still need to add the condition in finding the "\" string in %%c, when not present anymore it will mean we got to the final step, i.e. %root% will now correspond to the final root folder).
Thanks to who will try to help me resolving this issue.
EDIT:
this main program is called as follow:
prog arg1 arg2 arg3 arg4 arg5
arg5 is the path to the file which will internally be the argument for this other program. To be as general as possible I want to made functional this program no matter from where you call it. Still you have two options:
you are already in the folder containing the file to be processed by this internally called program, in such case arg5 will be passed as filename.dat (without quotes, except if it contains spaces)
you are not in the root folder so you pass directly the FULL PATH (in double quotes if it contains spaces).
The problem is in case 2, since this internal program works properly when you call it from root directory and you pass to it only FILENAME.DAT.
This is why I posed this question. %var% is simply arg5, in the ways I explained hereby.
I hope I have been a little more clear than before.
PS. I'm not an experienced programmer in all ways, so I do apologise if I miss in clearance and professionalism. The write/read from/to %temp% folder was just to exploit a newer way of programming in batch, nothing else. I knew it was superfluous.

since you mentioned set var=%5 in a comment:
set "root=%~dp5"
set "file=%~nx5"
should be all you need. See call /? for more details.

You should take a look at the for-variable modifiers (FOR /?)
root=%%~dpz is used to expand to: d=drive and p=path of z
file=%%~nxz is used to expand to: n=name and x=extension of z
Then you can change your block to
for /f "tokens=1,* delims= " %%z in (%temp%\filepath.txt) do (
set "line=%%z"
set "root=%%~dpz"
set "file=%%~nxz"
REM ** Show the variables
set root
set file
)

Another way to do this in a batch-file would be to use the PowerShell Split-Path command designed to do this.
SET "THEFILE=%TEMP%\file path.txt"
FOR /F "delims=" %%A IN ('powershell -NoLogo -NoProfile -Command
"""$([Convert]::ToChar(34))$(Split-Path -Path """%THEFILE%""")$([Convert]::ToChar(34)) $([Convert]::ToChar(34))$(Split-Path -Path """%THEFILE%""" -Leaf)$([Convert]::ToChar(34))"""') DO (
SET "FILESTRING=%%A"
)
SET "PATHPART="
FOR %%A IN (%FILESTRING%) DO (
IF NOT DEFINED PATHPART (SET "PATHPART=%%~A") ELSE (SET "FILEPART=%%~A")
)
ECHO PATHPART is "%PATHPART%"
ECHO FILEPART is "%FILEPART%"
Of course, it is easier if the script is written for PowerShell.
$TheFile = "$Env:TEMP\filepath.txt"
$PathPart = Split-Path -Path $TheFile
$FilePart = Split-Path -Path $TheFile -Leaf
UPDATE:
Actually, it appears that splitting the path is not needed to use PUSHD. Just add \.. to the end.
PUSHD C:\Users\joe\afile.txt\..

Related

Searching for partial path\filename in bat

Ok, so I've been bating (hehe) my head against a wall here.
I am looking for an option/code that would allow me to search for a partial path and/or filename from a .bat script that I would export to an outside file.
Now, "search", "export" and "outside file" is something I am fine with. The part that is giving me a headache is the "partial".
To elaborate.
I am looking for a folder called DATA and a file called userinfo.txt inside DATA.
Those are constant. So the path I have is DATA\userinfo.txt
I am also 99% certain that this folder will be in D:\ but thats not a concern right now. Where ever it is I'll find it.
But I cannot figure out how to look for a partial path\filename for the life of me.
Reason I have specified that DATA\userinfo.txt is a constant is due to other folders ability to be named arbitrarily. So in my below example 01-12-2016 does not have to be named according to that convention. For USA it would most likely be named 12-01-2016. It is also sometimes named 20161201 or 20160112 or on top of all that has a letter prefix such as d01-12-2016. On that note DATA is always DATA, which is why I said DATA is constant in my search. Another thing that will be the same is the grandparent folder. When i say "same" i mean "shared" between the two applications. It does not mean it will always be named "program" as in my example below.
Googling this and using things I know has got me nowhere.
Reason I cannot simply use
where /r d: userinfo.txt
is that that specific command will return hundreds of results as there is a userinfo.txt created for every.single.day the program was running and is stored separately.
Alternatively - if there would be a way to comb trough those hundreds of results and find the matching part that would also resolve my issue.
This however brings up another headache as there is usually more than one program with this exact file.
so in the example of
d:\users\path\program\storage\01-12-2016\userinfo.txt
d:\users\path\program\otherstorage\01-12-2016\userinfo.txt
d:\users\path\program\storage\02-12-2016\userinfo.txt
d:\users\path\program\otherstorage\02-12-2016\userinfo.txt
d:\users\path\program\storage\03-12-2016\userinfo.txt
d:\users\path\program\otherstorage\03-12-2016\userinfo.txt
d:\users\path\program\storage\04-12-2016\userinfo.txt
d:\users\path\program\otherstorage\04-12-2016\userinfo.txt
d:\users\path\program\storage\05-12-2016\userinfo.txt
d:\users\path\program\otherstorage\05-12-2016\userinfo.txt
d:\users\path\program\storage\06-12-2016\userinfo.txt
d:\users\path\program\otherstorage\06-12-2016\userinfo.txt
d:\users\path\program\storage\data\userinfo.txt
d:\users\path\program\otherstorage\data\userinfo.txt
Note: storage, otherstorage, storageother, storage2, storagegh are all arbitrary names as these folders are named accoring to end-user wishes.
I would want to export two separate variables for
d:\users\path\program\storage
and
d:\users\path\program\otherstorage
I would also need to do this for \data\userinfo.txt
So if searching for \data\userinfo.txt it would return
d:\users\path\program\storage\data\userinfo.txt
d:\users\path\program\otherstorage\data\userinfo.txt
I would also want to isolate both
d:\users\path\program\storage
and
d:\users\path\program\otherstorage
and use it as (separate) local variables.
I would need to note that installing/downloading any external scripting tools/aids would not be a suitable solution as I work on a lot of computers, most of which I do not have internet access and/or sufficient permissions for external downloads/installations so anything that is not integrated into the bat and needs to be imported separately is a bad idea.
Also, I am working on Windows XP SP3 but I would need this bat to be able to run on XP SP2, XP SP3, Windows 7, Windows 10, Windows NT, Windows 2000.
Any help would be appreciated.
Please note that
d:\users\path\program
would also be an acceptable variable. In this case I would manually amend the remainder of the path or would rely on end-user (my coworkers) input to complete the path correctly. The last has proven to be a fools errand.
The way that I've been handling it until now is to look for a .exe that I KNOW will be in both folders. This is a part of my code below edited to match the current example.
#echo off
SETLOCAL
echo Program will now look for program.exe and programgh.exe. Please input, when asked, matching part of the path for these files.
echo Example:
echo d:\users\path\program\storage\bin\program.exe
echo d:\users\path\program\otherstorage\bin\programgh.exe
echo In above example matching part is d:\users\path\program so you would enter that when prompted
echo Please do not input the last pathing mark: \ (backslash)
echo -------------searching---------------
::I am exporting errors to nul as I don't want them to be spammed by errors and other data that they would think is their fault
where /r c: program*.exe 2>nul
where /r d: program*.exe 2>nul
where /r e: program*.exe 2>nul
where /r f: program*.exe 2>nul
set /p dualpath="Please enter matching paths for program folder: "
After that I would proceed to work with %dualpath% variable.
As it usually happens (to me at least) most people would just copy the example path without taking a look at what the program has spat out and would be confused as to why the program did not work. Either that or would copy everything up to program.exe and programgh.exe - including the otherstorage\bin\ without noticing that \storage\ and \otherstorage\ do not match.
I think this now covers all the comments or additional questions and clarifies a bit better what I need. Thank you all for help so far and I hope that this is easier to understand.
If a Windows cmd command allows wildcards in a (partially or fully qualified) path then wildcards must be used only in the path leaf (i.e. the last item or container in the path). However, you could apply findstr regex to narrow command output e.g. as follows:
where /r d:\ userinfo.txt | findstr /I "\\storage2*\\data\\userinfo.txt"
above command wold narrow output to paths ending with \storage\data\userinfo.txt and \storage2\data\userinfo.txt
Another example - narrow output to paths ending with \storageX\data\userinfo.txt where X is either nothing or any decimal cipher [0-9]:
dir /B /S d:\userinfo.txt | findstr /I "\\storage[0-9]*\\data\\userinfo.txt"
Put the paths to environment variables (with _var prefix for easier next identification), e.g. _varstorage, _varstorage2, …
#ECHO OFF
SETLOCAL EnableExtensions
for /F "delims=" %%F in ('
dir /B /S "d:\userinfo.txt" ^| findstr /I "\\storage[0-9]*\\data\\userinfo.txt"') do (
for /D %%D in ("%%~dpF..") do (
set "_var%%~nxD=%%~fD"
rem %%~fD path
rem %%~nxD last item in above path
rem _var variable name prefix
)
)
rem show result:
set _var
See also next %%~nxD and %%~D explanation: Command Line arguments (Parameters): Parameter Extensions
If I got your intention right, the following script should do what you want:
#echo off
setlocal EnableExtensions DisableDelayedExpansion
rem // Define constants here:
set "_ROOT=D:\" & rem "D:\", "D:\users",..., or "D:\users\path\program"
set "_FILE=userinfo.txt"
rem // Initialise index:
set /A "INDEX=1"
rem // Search for the specified file in the given root directory:
for /F "delims=" %%F in ('dir /B /S "%_ROOT%\%_FILE%"') do (
rem // Iterate once over the grandparent directory itself:
for /D %%D in ("%%F\..\..") do (
rem // Resolve the path of the grantparent directory;
set "ITEM=%%~fD"
rem // Initialise flag (non-empty means not yet stored):
set "FLAG=#"
rem // Toggle delayed expansion to avoid trouble with exclamation marks:
setlocal EnableDelayedExpansion
rem // Iterate over all currently stored grantparent paths:
for /F "tokens=1,* delims==" %%V in ('2^> nul set $ARRAY[') do (
rem // Clear flag in case current grandparent has already been stored:
if /I "!%%V!"=="!ITEM!" set "FLAG="
)
rem // Check flag:
if defined FLAG (
rem // Flag is empty, so current grandparent needs to be stored:
set "$ARRAY[!INDEX!]=!ITEM!"
rem // Transfer stored grandparent over localisation barrier:
for /F "delims=" %%E in ("$ARRAY[!INDEX!]=!ITEM!") do (
endlocal
set "%%E"
)
rem // Increment index
set /A "INDEX+=1"
) else endlocal
)
)
rem // Retrieving final count of grandparent directories:
set /A "INDEX-=1"
rem // Return stored grandparent paths:
set $ARRAY[
endlocal
exit /B
This should return D:\users\path\programs\otherstorage and D:\users\path\programs\storage in your situation, which are stored in the variables $ARRAY[1] and $ARRAY[2], respectively. Due to the array-style variables, this approach is flexible enough to cover also cases where more than two grandparent directories are present.
Based on your above sample this batch
#Echo off
Set Search=\\data\\userinfo.txt
pushd "D:\Users\path\program
For /f "Delims=" %%A in (
'Dir /B/S/A-D userinfo.txt ^|findstr "%Search%$"'
) Do Call :Sub "%%~fA" "%%~dpA.."
Popd
Goto :Eof
:Sub FullName DrivePath
Echo Found %~nx1
Echo in %~dp1
Echo Granny %~nx2
Set "Granny=%~nx2"
Echo in %~dp2
Echo -------
Should give this output (only partially tested)
Found userinfo.txt
in D:\Users\path\program\storage\data\
Granny storage
in D:\Users\path\program\
-------
Found userinfo.txt
in D:\Users\path\program\storage2\data\
Granny storage2
in D:\Users\path\program\
-------
The backslash in Search has to be doubled as it is an escape char for findstr

Windows batch version of Unix select command?

I'm trying to create a Windows version of a simple bash script I have, but I cannot seem to find a Windows version of the Unix 'select' command. Is there one?
Here's the script:
#!/bin/bash
echo "Enter the number of the file you want to select:"
select THE_FILE in someDir/*;
do
echo "You picked $THE_FILE ($REPLY)"
# Will do some stuff here.
break;
done
It was very easy to find this Linux example, so I am a bit perplexed that I cannot seem to find a Windows equivalent.
Edit: The select command prompts the user to select a file from a given directory (that's an oversimplification). To clarify, the script from above will produce the following output, assuming there is a subdirectory 'someDir' with only those three text files in it.
Enter the number of the file you want to select:
1) someDir/somefile1.txt
2) someDir/somefile2.txt
3) someDir/somefile3.txt
#? 2
You picked someDir/somefile2.txt (2)
Windows batch commands does not include any equivalent to the select command. So you will have to build your own version.
You will need:
call command. Create a subroutine and reuse it
for command to iterate over the files, or for /f to iterate over the output of another command returning the list of files
For short lists, choice command is more friendly to the user as it is not needed to press enter, but for longer lists or if you don't know the number of files to select, set /p is a better option
Here, just a sample
#echo off
setlocal enableextensions disabledelayedexpansion
call :select "someDir\*" THE_FILE
echo You picked %THE_FILE%
goto :eof
:select mask returnVar
setlocal enableextensions disabledelayedexpansion
rem Configure internal variables
set "fileNumber="
set "maxFiles=-1"
for /f "delims==" %%a in ('2^>nul set f[') do set "%%a="
echo Enter the number of the file you want to select:
rem Search files, show list and create array with file list
rem Using xcopy to get the list of files because it will show
rem relative paths when a relative mask is used as input
for /f "tokens=1,* delims=:" %%a in ('
xcopy "%~1" "%temp%" /l ^| findstr /n /r /c:"[\\\.:]"
') do (
echo %%a^) %%b
set "f[%%a]=%%~fb"
set "maxFiles=%%a"
)
rem Prompt
:select.ask
set /p "fileNumber=#? "
rem Validate input
set /a "fileNumber=fileNumber+0" 2>nul
if %fileNumber% gtr %maxFiles% set "fileNumber=-1"
if %fileNumber% lss 1 set "fileNumber="
if not defined fileNumber (
echo Wrong selection
goto :select.ask
)
rem Retrieve file from array
setlocal enabledelayedexpansion
for %%a in ("!f[%fileNumber%]!") do (
endlocal
set "selectedFile=%%~a"
)
rem Return selection to caller
endlocal & set "%~2=%selectedFile%"
goto :eof
See Choice /? or set /?.
choice /c:yn
If errorelevel 1 if not errorlevel 2 echo Y was chosen
Although a bash select equivalent is not included in Windows Batch, it is very easy to write your own. It may be a subroutine with the exact same parameters of Linux select so you don't need to learn something new in order to use it; this way, the "in" word in second parameter will not be used in the Batch code.
#echo off
call :select THE_FILE in someDir/*
echo You picked %THE_FILE% (%errorlevel%)
goto :EOF
rem Subroutine that emulates Linux's select
:select returnVar in directory
setlocal EnableDelayedExpansion
echo Enter the number of the file you want to select:
rem Show the files and create an array with them
set n=0
for %%a in (%3) do (
set /A n+=1
set file[!n!]=%%a
echo !n!^) %%a
)
rem Get the number of the desired file
:getNumber
set /P "number=#? "
if not defined file[%number%] goto getNumber
rem Return selected file to caller
for /F "delims=" %%a in ("!file[%number%]!") do endlocal & set "%1=%%a" & exit /B %number%
Previous code is straigthforward, but post a comment here if you have any doubt about it. Perhaps the most complex part is the for /F command at last line, that is required to save the value of the !filename[%number%]! in the %%a FOR parameter before execute the endlocal and the assignment to the first parameter, that is the way to return that value to the calling program. If endlocal would be executed first, the !delayed expansion! of the variable will no longer work...
Although this code does not return the path of the selected file, it is very easy to add such feature, but the code will complicate a little.
While a batch-file solution is desired by the OP, it is worth presenting a PowerShell solution that is much more concise:
($files = get-childitem -file someDir\) | % { $i=0 } { ++$i; "$i) $_" }
$reply = read-host -p "Enter the number of the file you want to select"
"You picked $($files[$reply-1]) ($reply)"
$files = get-childitem -file someDir\ collects all files in directory someDir in variable $files
Note that the -file option for restricting child items to files requires PowerShell 3.0 or higher; on earlier versions, pipe to ? { -not $_.PSIsContainer } instead.
% { $i=0 } { ++$i; "$i) $_" } outputs each filename prefixed with its 1-based index.
% is shorthand for the ForEach-Object object cmdlet, which processes a block of code for each input object
{ $i=0 } initializes the index variable (executed once, before iteration)
{ ++$i; "$i) $_" } is executed for each input object, $_; ++$i increments the index, "$i) $_" prints the index followed by ) and a space, followed by the input object's default property, which is the filename in this case. (If you wanted to print the full path instead, for instance, you'd use "$i) $($_.fullname)").
Note how no explicit output (print) command is needed - the results are output to the terminal by default.
$reply = read-host -p "Enter the number of the file you want to select" reads a single line from the terminal with the specified prompt and stores the user's input in variable $reply.
"You picked $($files[$reply-1]) ($reply)" outputs the result; again, no explicit output command is required.

How to delete first 7 characters of folder name by using batch script?

I did do research but I could not figure out, sorry!
Google can only help me to add characters but not delete characters...
say under the dir E:\Movies\2011\
there are several folders
e.g.
[0603] Movie_1
[0708] Movie_2
[0811] Movie_3
and so on..
So I want to run a batch script to remove date tag
i.e.
Movie_1
Movie_2
Movie_3
and so on
I am using windows 8.
RENAMER.CMD
#echo off
setlocal enableextensions enabledelayedexpansion
for %%i in (*) do (set name=%%i && ren "!name!" "!name:~7!")
endlocal
Explanation:
The setlocal / endlocal stuff just makes sure that the script will work no matter what your default commandline settings are. Those lines are optional if you make sure that command extensions and delayed expansion are enabled by default, or if you start your command prompt using cmd /e:on /v:on.
The for statement is the heart of the thing. It selects every file in the directory using (*). If you only want to change files with certain extensions you can use a different pattern such as (*.avi). Then !name:~7! takes seven characters off the front. set /? lists some of the other manipulations you can perform. Most of the examples use % instead of ! but all string manipulations work with both.
Updated to answer comment
You can perform a % substitution inside a ! substitution.
#echo off
setlocal enableextensions enabledelayedexpansion
set numtrim=7
for %%i in (*) do (set name=%%i && ren "!name!" "!name:~%numtrim%!")
endlocal
You can use the enhanced variable substitution features to do this (SET /? for information):
#echo off
setlocal
for /d %%i in (*) do call :rename %%i
goto :eof
:rename
set CURDIR=%1
echo ren "%CURDIR%" "%CURDIR:~7%"
The magic happens at %CURDIR:~7%, which means "the value of %CURDIR%, but skip the first 7 characters".
The batch file above does not rename anything (it just prints the rename command), so you can do a dry run first and then remove the final echo to get the real job done.
Since you are using Win8, you can use powershell to also accomplish this. Here is what I used to turn
blah11-1
blah12-1
blah13-1
to
blah11
blah12
blah13
C:\testing> dir -Directory | foreach { if($.Name.Length -gt 6){ $fileName = $.Name.SubString(0,6)} else { $fileName = $.Name }; rename-item -path $.fullname -newname $fileName; }
Now this is just what I ran on the powershell command line, but it could be moved into a script to make it more reusable.

How would you write a .bat or .cmd file to remove an element from the PATH?

Related:
How to list the elements of the path in a batch file?
How does FOR work?
How would you write a batch file or CMD file to remove an element from the path? It should handle gracefully:
differences in case
shortnames and long names
I've done this using tr.exe but it's slow and complicated and uses temporary files, which makes it even more complicated.
I think the answer is something like this:
setlocal
set tpath=""
set _path="%PATH:;=" "%"
for %%p in (%_path%) do (
call :KeepIfNotEqual %%p %elementToRemove%
)
endlocal & set path=%tpath%
...where %elementToRemove% is the path element to remove. KeepIfUnique would have to be a subroutine that takes two arguments - directory names, normalizes them, and appends the first argument to tpath if it is not equal to the 2nd argument (elementToRemove).
As I said, I can do this with tr.exe, but can I do it with just built-in commands in the windows cmd.exe shell?
EDIT: I guess when you get right down to it, the question is, how to do case-conversion in cmd.exe?
This is what I came up with, using Igor's hint.
#echo off
goto START
-------------------------------------------------------
rmpath.bat
remove a path element from path
Created Tue Sep 15 21:33:54 2009
-------------------------------------------------------
:START
SETLOCAL ENABLEDELAYEDEXPANSION
#REM require one argument (the path element to remove)
if _%1==_ goto USAGE
#REM ~fs = remove quotes, full path, short names
set fqElement=%~fs1
#REM convert path to a list of quote-delimited strings, separated by spaces
set fpath="%PATH:;=" "%"
#REM iterate through those path elements
for %%p in (%fpath%) do (
#REM ~fs = remove quotes, full path, short names
set p2=%%~fsp
#REM is this element NOT the one we want to remove?
if /i NOT "!p2!"=="%fqElement%" (
if _!tpath!==_ (set tpath=%%~p) else (set tpath=!tpath!;%%~p)
)
)
set path=!tpath!
#call :LISTPATH
goto ALL_DONE
-------------------------------------------------------
--------------------------------------------
:LISTPATH
echo.
set _path="%PATH:;=" "%"
for %%p in (%_path%) do if not "%%~p"=="" echo %%~p
echo.
goto :EOF
--------------------------------------------
--------------------------------------------
:USAGE
echo usage: rmpath ^<arg^>
echo removes a path element from the path.
goto ALL_DONE
--------------------------------------------
:ALL_DONE
ENDLOCAL & set path=%tpath%
if command has ignore case option
if /I "Blah"=="blah" (echo true) else (echo false)
See if /? for more help on using it. I generally found these commands helpful in determining how to do some cmd line trickery:
if /?
call /?
for /?
set /?
I want to add something to Cheeso's answer... but I don't have enough rep to add a comment.
If you want the script he provided to work even after the batch file is finished, then change the last line like this this:
#echo off
. . .
. . .
SETLOCAL ENABLEDELAYEDEXPANSION
. . .
. . .
endlocal & set path=%tpath%
This employs a neat trick: the 'tpath' variable is available when that line is read-in and parsed by cmd.exe and expanded prior to getting deleted by 'endlocal'.
This allows the changes to the 'PATH' variable to persist even after the batch file exits.

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