Batch file : how to get a specific part of a folder path - batch-file

I'm currently looping threw the subfolders of a known directly. I would like to grab the folder that contains the file I'm looking for, but only that folder's name, not the whole path, and preferably in a different variable to be use later on in the batch file ...
pause
CD c:\%username%\testFolder\Program\OLD\
For /R %%G in (test.t?t) do Echo %%G
pause
Now, this shows me the file I'm looking for : C:\User\testFolder\Program\OLD\myfile\exemple1\test.txt
what I would like is to replace the Echo %%G to set in a variable the "unknown" folders and subfolder, something along the line of
For /R %%G in (test.t?t) do set var1 = myfile\exemple1
anyone can point to what I'm missing ?

If you for /? in a cmd console, you'll see runtime variables on the last couple of pages. Using %%~dpG notation you can get the full drive\path specification of matched files. Then, using substring substitution and delayed expansion, replace %CD% with nothing. Finally, you can strip the leading and trailing backslash with a numeric substring -- !varname:~1,-1!.
#echo off
setlocal enabledelayedexpansion
cd /d "c:\%username%\testFolder\Program\OLD\"
for /R %%G in (test.t?t) do (
set "var1=%%~dpG"
set "var1=!var1:%CD%=!"
if "!var1!"=="\" (echo .\) else echo(!var1:~1,-1!
)
If you wish, you can prevent echoing duplicates by echoing only if the previous match does not equal the current one.
#echo off
setlocal enabledelayedexpansion
cd /d "c:\%username%\testFolder\Program\OLD\"
for /R %%G in (test.t?t) do (
set "var1=%%~dpG"
set "var1=!var1:%CD%=!"
if not "!var1!"=="!prev!" (
if "!var1!"=="\" (echo .\) else echo(!var1:~1,-1!
)
set "prev=!var1!"
)

Related

How to clean-up file paths (back/forward slashes) provided as arguments in a .bat script to avoid syntax errors

I have a simple .bat script which renames all files in a folder using ren. The input argument is a path to a folder containing the files to be renamed. The script sometimes returns syntax errors which we've traced to the fact that sometimes the input path has forward slashes, backslashes, or a mix of both (and sometimes starts with a double forward slash). We would like to make this script more robust by allowing it to accept any of these types of paths, and cleaning up the path as part of the .bat script before calling the ren command.
So my question is: is there a (set of) command(s) I can apply to the file path argument (%1 in the example below) before calling the ren function that will correct all forward/backslashes to be consistent and avoid syntax errors? I don't have much experience with .bat scripts, so any code examples would be helpful.
#echo off
setlocal ENABLEDELAYEDEXPANSION
for %%F in (%1*.nc) do (
for /F "tokens=1-8 delims=_" %%a in ("%%~nF") do (
ren "%%F" "%%a_%%b_%%c_%%d_%%e_%%g_%%f_%%h.nc"
)
)
UPDATE: In the end, only the last suggestion by Magoo was needed, because changing %1 to "%~f1" fixed the slash issues. I also had to add %~f1\ to the first argument of the ren command because otherwise it was somehow looking in the wrong folder (the first for found the files ok, but the ren command was looking in the wrong folder.
#echo off
setlocal ENABLEDELAYEDEXPANSION
for /F "delims=" %%F in ('dir /b /a-d "%~f1\*.nc"') do (
for /F "tokens=1-8 delims=_" %%a in ("%%~nF") do (
ren "%~f1\%%~nF.nc" "%%a_%%b_%%c_%%d_%%e_%%g_%%f_%%h.nc"
)
)
set "corrected=%~1"
set "corrected=%corrected:/=\%"
Then use %corrected% in place of %1 AND quote the filename thus:
for %%F in ("%corrected%*.nc") do (
If %1 is always a directory-name, then add
if "%corrected:~-1%" neq "\" set "corrected=%corrected%\"
as a third set line before the for line.
The first set assigns the value of %1 to a variable corrected - the ~ removes any enclosing quotes.
The second set changes all strings matching that between the : and = into that between the = and % in the variable given and assigns to the first-mentioned variable (can be the same variable, as in this case)
The third set, if used, checks that the last character is \ and if it is not, appends a \.
The quoting of the filename-string allows there to be spaces in the path/filename and is harmless if there are no spaces.
To avoid attempting to rename a file twice, instead of
for %%F in ("%corrected%*.nc") do (
use
for /F "delims=" %%F in ('dir /b /a-d "%corrected%*.nc"') do (
This builds a list of filenames in memory, then processes that list.

Find the path of executable and store the executable in a variable

I am trying to create a batch file:
The batch file will locate the path of executable first. Then, the path will be stored in a variable for later use.
This is my code:
#echo off
setlocal
set directoryName=dir/s c:\ABCD.exe
rem run command
cmd /c %directoryName%
pause
endlocal
The command prompt does return me with the executable's path but the path is not stored in the variable. Why is it so?
Reading your question, it appears that you're not really wanting to save the path of the executable file at all, but the file name complete with it's full path:
I prefer the Where command for this type of search, this example searches the drive in which the current directory resides:
#Echo Off
Set "mPth="
For /F "Delims=" %%A In ('Where /R \ "ABCD.exe" /F 2^>Nul'
) Do Set "mPth=%%A"
If Not Defined mPth Exit /B
Rem Rest of code goes here
The variable %mPth% should contain what you need. I have designed it to automatically enclose the variable value in doublequotes, if you wish to not have those, change %%A on line 4 to %%~A. If the file is not found then the script will just Exit, if you wish it to do something else then you can add that functionality on line 5.
Note: the code could find more than one match, if it does it will save the variable value to the last one matched, which may not be the one you intended. A robust solution might want to include for this possibility.
Edit (this sets the variable, %mPth% to the path of the executable file only)
#Echo Off
Set "mPth="
For /F "Delims=" %%A In ('Where /R \ "ABCD.exe" /F 2^>Nul'
) Do Set "mPth=%%~dpA"
If Not Defined mPth Exit /B
Set "mPth=%mPth:~,-1%"
Rem Rest of code goes here
Lets walk through your code
set directoryName=dir/s c:\ABCD.exe
This fills the variable directory name with the value dir/s c:\ABCD.exe.
cmd /c %directoryName%
This executes the command in directoryname. There is no line in your code that saves the files location to a variable.
Extracting the path of a file can be done as follows
#echo off
setlocal
set executable=c:\location\ABCD.exe
FOR %%A IN ("%executable%") DO Set executablepath=%%~dpA
echo executablepath: %executablepath%
pause
endlocal
%executablepath% will contain c:\location\
The value that you are assigning to directoryname is dir /s c:\abc.exe.
this value is then substituted for %directoryname% in your cmd line, which executes the command dir/s..., showing you the location(s) of abc.exe in the familiar dir format.
If what you want is just the directoryname in directoryname, then you need
for /f "delims=" %%a in ('dir /s /B /a-d "c:\abc.exe"') do set "directoryname=%%~dpa"
which will first execute the dir command, then process each line of output from that command and assign it in its entirety to %%a.
The dir command shown would "display" the matching names found in the nominated directory (c:\) and its subdirectories (/s) in basic form (/b) - that is, names only, no size or date or report-headers or report-footers, and a-d without directorynames (should they match the "mask" abc.exe)
The delims= option to the for /f command instructs that the entire line as output by the command in single-quotes, be assigned to %%a.
When the result is assigned to the variable directoryname, only the Drive and Path parts are selected by using the ~dp prefix the the a.
Note that only the very last name found will be assigned to the variable as any earlier assignment will be overwritten by a succeeding assignment.
This may or may not be what you are looking for. This script searches through the PATH variable and looks for files that have and extension in the PATHEXT variable list.
#SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
#SET EXITCODE=1
:: Needs an argument.
#IF "x%1"=="x" (
#ECHO Usage: %0 ^<progName^>
GOTO TheEnd
)
#set newline=^
#REM Previous two (2) blank lines are required. Do not change!
#REM Ensure that the current working directory is first
#REM because that is where DOS looks first.
#PATH=.;!PATH!
#FOR /F "tokens=*" %%i in ("%PATH:;=!newline!%") DO #(
#IF EXIST %%i\%1 (
#ECHO %%i\%1
#SET EXITCODE=0
)
#FOR /F "tokens=*" %%x in ("%PATHEXT:;=!newline!%") DO #(
#IF EXIST %%i\%1%%x (
#ECHO %%i\%1%%x
#SET EXITCODE=0
)
)
)
:TheEnd
#EXIT /B %EXITCODE%
Note that this may find multiple executables. It may also find multiple types of executables. The current directory is also included first since that is what the shell, cmd.exe, does.
M:>whence wc
.\wc.BAT
.\wc.VBS
C:\Users\lit\bin\wc.BAT
C:\Users\lit\bin\wc.VBS

Rename Files using wildcard paths

Recently I started working and my first task is to write a batch file that automatically changes filenames to filename_date with the original file-ending.
For that you should be able to write paths into a textfile (e.g. paths.txt) and when you start the program, it should take any line (=path->file) from there and rename it.
I got it to work on my PC quiet well but as I gave it to testing they asked to make the use of wildcards Z:\Path\*.* possible.
My current code looks as follows:
#echo off
setlocal EnableDelayedExpansion
cd %~dp0
For /F "tokens=*" %%m in (paths.txt) do (
set path=%%~dpm
set name=%%~nxm
pushd "!path!"
dir
For /r !path! %%f in (!name!) do (
set path=%%~dpf
set name=%%~nf
set ending=%%~xf
set datsave=%%~nxf
set "name=!name!_"
set "name=!name!!date:~6,4!"
set "name=!name!!date:~3,2!"
set "name=!name!!date:~0,2!"
set "name=!name!!ending!"
copy "!datsave!" "!name!"
del "!datsave!"
cls
popd
)
)
I know that a lot of it is probably easier and more efficient to do, but this is my first batch project and I am quiet happy except for the wildcard problem.
So an example would be:
C:\Some\Path\*.*
This line would be in paths.txt.
With the splitting
set path=%%~dpf
set name=%%~nf
set ending=%%~xf
set datsave=%%~nxf
I get the following:
path: C:\Some\Path
name: C:\Some\Path
ending: -empty-
datsave: C:\Some\Path
because name is set to the Path at the start of the first FOR-Loop. But that seems to be working if I do not use wildcards.
Now the question: Why does this happen and how do I get rid of it? Or do I just use the wrong type of wildcards?
Again: This is my first time I work with batch, so it might be something simple ;)
Ok, I figured out 2 problems and now it works
set name=%%~nxm evaluates the wildcard. Even if name is *.txt it will return bar.txt.
I replaced that by a basename computation instead: set name=!name:*\=! done enough times (not very subtle but hey batch files forces us to do such things) which preserves the wildcard
The other problem is the for /R loop: after pushd, the argument needs to be . or it won't be scanned.
Last minor one: use rename instead of copy plus delete. It preserves file time and is very fast. Copying then deleting a large file can take a long time.
#echo off
set DEPTH=20
setlocal EnableDelayedExpansion
cd %~dp0
For /F %%m in (paths.txt) do (
set pth=%%~dpm
set z=%%m
set name=!z!
rem brutal basename. We cannot break the inner loop or
rem it would break the upper loop too
for /L %%I in (1,1,%DEPTH%) do set name=!name:*\=!
rem but we can check if it is really a basename
set chkname=!name:*\=!
if not !chkname!==!name! ( echo please increase DEPTH value
pause
exit /B)
rem set name=%%~nxm
pushd "!pth!"
For /r . %%f in (!name!) do (
set pth=%%~dpf
set name=%%~nf
set ending=%%~xf
set datsave=%%~nxf
set "name=!name!_!date:~6,4!!date:~3,2!!date:~0,2!!ending!
echo renaming "!datsave!" to "!name!"
rem ren "!datsave!" "!name!"
popd
)
)
paths.txt contains just a line C:\full\path\to\test\*.txt
my test directory contains 2 text files and 1 other file
output:
renaming "bar.txt" to "bar_20160812.txt"
renaming "foo.txt" to "foo_20160812.txt"
(just uncomment the ren line to get the job done)
Weeeeell First of all thanks again to #Jean-François Fabre and #aschipfl for their patience with me :)
After the hint with the second batch file I had to test a few things as not everything worked as fine, but now everything works great!
Code of the Main file:
#echo off
setlocal EnableDelayedExpansion
cd %~dp0
set DEPTH=20
For /F %%m in (paths.txt) do (
pause
set pth=%%~dpm
REM pushd !pth!
REM set origpth=!cd!
REM popd
set z=%%m
set name=!z!
For /L %%i in (1,1,%DEPTH%) do set
name=!name:*\=!
set chkname=!name:*\=!
if not !chkname!==!name! ( echo depth to small
pause
exit /B)
rem set name=%%~nxm
pushd "!pth!"
For /r . %%f in (!name!) do (
pushd %~dp0
call renamefiles.bat %%f REM "!origpth!"
popd
)
)
And the code of the sub-file:
#echo off
REM set pth=%~dp1
REM set origpth=%2
REM set origpth=%origpath:"=%\
REM If !pth!==%origpth% (
set path=%~dp1
set name=%~n1
set ending=%~x1
set datsave=%~nx1
pushd !path!
set "name=!name!_!date:~6,4!!date:~3,2!!date:~0,2!!ending!"
pause
echo renaming "!datsave!" to "!name!"
rem "!datsave!" "!name!"
cls
popd
REM )
EDIT: After testing around a bit I figured, that subfolders are included as well! I put extra code to both codes marked with REM and two extra spaces. Take out those REM's and the programm will not longer include subfolders when renaming :)

Write list of files with relative path, to file, via batch script

I want to list all files (with relative path) in a folder with a specific sub-folder, and write that list to a text file. The folder is a network folder so I can not set it as current directory. All this via batch script.
So like this:
Folder structure:
\\OtherComputer\MyFiles\libs
--File1.dll
--File2.dll
\\OtherComputer\MyFiles\libs\Editor\
----File3.dll
I want to generate a file with the following text:
File1.dll
File2.dll
Editor\File3.dll
This is how far I have come:
SET LibPath=\\OtherComputer\MyFiles\
break >"%LibPath%metadata"
for %%F in (%LibPath%*.*) do (
echo %%F>>"%LibPath%metadata"
)
for %%F in (%LibPath%Editor\*.*) do (
echo Editor\%%F>>"%LibPath%metadata"
)
But this solution write the full path. I tried this to remove the path:
set str=%%F:%LibPath%=%
But it does not seem to handle variable SET or the %%F variable too well inside a for-loop.
Is it even possible? Would like to not have to write a C# executable for this.
Thanks in advance!
Try the following:
#echo off
setlocal enabledelayedexpansion
set "dir=\\OtherComputer\MyFiles\"
FOR /F "tokens=*" %%f IN ('dir /s /b /A-d "%dir%"') do (
set "full=%%f"
echo !full:*%dir%=!
)
setlocal enabledelayedexpansion, in addition to creating a localized environment, turns on delayed expansions of variables, which allows variables to be modified inside the for loop's body and used with their modified values. (see help set). Note that in order for a variable to be expanded dynamically, you must enclose it in !, not %; e.g., !full! instead of %full%.
Note: A side effect of enabling delayed expansion is that any ! characters are interpreted as part of a variable reference, resulting in potentially unwanted removal of ! chars. from strings, such as in echo hi!. To output a literal ! while delayed expansion is enabled, use ^^! in unquoted strings, and ^! in double-quoted strings. Furthermore, literal ^ chars. in double-quoted strings then have to be represented as ^^.
dir /s /b /A-d "%dir%" lists all files - and files only, due to excluding directories with /A-d - in the current subtree (/s), as paths only (b). Note that using /s implies that full paths are output.
set "full=%%f" sets aux. variable full to the absolute path being processed in the current loop iteration.
!full:*%dir%=! then strips the input dir.'s path from the absolute path using prefix string substitution, leaving the relative path desired (again, see help set).
Just to contrast this with a PowerShell (v3+) solution, which demonstrates how much more advanced PowerShell is compared to cmd.exe.:
$dir = '\\OtherComputer\MyFiles\'
(Get-ChildItem -File -Recurse $dir).FullName.replace($dir, '')
I have a way to to this with two bat files:
code1.bat: Looping through all the files in "C:\Mypath"
#echo off
FOR /F "tokens=*" %%G IN ('dir /s /b C:\Mypath\*.*') do (
CALL code2.bat %%G
)
code2.bat: removing the main path string
SET _var=%1
SET _endbit=%_var:*Mypath\=%
Echo %_endbit%
Probably can be done in one file... take a look at http://ss64.com/nt/
The xcopy commmand is capable of returning relative paths. If the /L switch is given, nothing is actually copied but all items are listed that would be copied without /L. The pushd command handles UNC paths correctly (type pushd /? in command prompt for details). So the following code snippet should do what you want:
#echo off
pushd "\\host\share\folder"
> "\path\to\listfile.txt" xcopy /L /Y /C /I /E ".\*.*" "%TMP%"
popd
The above code results in each line to be prefixed with .\. In addition, it returns a summary line like # File(s). The code below gets rid of all those artefacts:
#echo off
pushd "\\host\share\folder"
> "\path\to\listfile.txt" (
for /F "tokens=2 delims=:" %%I in ('
xcopy /L /Y /C /I /E "*.*" "%TMP%"
') do (
echo(%%I
)
)
popd
Here I changed the file pattern from .\*.* to *.* which results in outputs like Z:rel\path\to\item.ext (rather than .\rel\path\to\item.ext). The for /F loop parses the output of xcopy and removes everything up to the first :, so the drive letter Z: is deleted. Since the : is a forbidden character for file and directory names, it cannot appear in any of the paths. Since the summary line # File(s) does not contain any columns, it is not enumerated by for /F.

Batch Script to create folder based on name, add to existing code

The code below works fine, here is a list of it's functions:
It moves files based on the fist 4 characters to a pre-created folder with the same first 4 characters
If the folder does not exist, it will not move the file, as there is no folder with the same fist 4 chars.
#echo on
setlocal enabledelayedexpansion
cls
pushd R:\Contracts\Sites
for /f "tokens=*" %%1 in ('dir /a-d /b *') do (
set filename=%%1&set dirname=!filename:~0,4!
for /f "tokens=*" %%A in ('dir /ad /b') do (
set dirid=%%A & set dirid=!dirid:~0,4!
if "!dirid!" equ "!dirname!" move %%1 %%A
)
)
I would like to add one extra function to this code please. Pleas have a look at the example below.
I have 5 files
X32A-test.docx or X32A-test.pptx (there will only be one docx or pptx, "NEVER two with the same name")
X32A-test.pdf
X32A-test.avi
X32A-test-eng.sub
X32A-test-small.jpg
I would like the code to CREATE a folder if it does not exist, based on the file name if it has the extension docx or pptx.
So with the above example it would create a folder named: "X32A-test". Then all the other files with "X32A" in the front of the name will be moved to that newly created folder "X32A-test".
I hope it is clear enough. If not please ask me for more information.
Thank you
It is much simpler and more efficient to use the simple FOR instead of FOR /F in your case.
And rather than looping through every file and moving them individually, it is easier and more efficient to use wildcards.
The first loop finds the .pptx and .docx files and creates folders as needed
The second loop finds all the directories and moves all files that start with the directory name into the directory.
#echo on
setlocal enableDelayedExpansion
cls
pushd R:\Contracts\Sites
for %%F in (*.docx *.pptx) do (
set "folder=%%F"
2>nul md !folder:~0,4!
)
for /d %%F in (*) do move %%F* %%F
popd
If needed, you can protect yourself against directory names shorter than length 4.
#echo on
setlocal enableDelayedExpansion
cls
pushd R:\Contracts\Sites
for %%F in (*.docx *.pptx) do (
set "folder=%%F"
set folder=!folder:~0,4!
if !folder:~0,3! neq !folder! 2>nul md !folder!
)
for /d %%F in (????) do (
set "folder=%%F"
if "!folder:~0,3!" neq "%%F" move %%F* %%F
)
popd
Note that this solution may fail if a file name contains !. If that arises then you need to toggle delayed expansion on and off within the loop(s).
I can see the entire process (including the part already implemented) like this:
All the files that are not yet in their "home" directories are moved there.
For all .docx and .pptx files left, create directories based on the files' names.
Obviously, the step #2 creates new "homes" and those will still be "uninhabited" this far. So all that is left to do now is to repeat the step #1.
So I would probably reorganised your process and, with the additional requirement, it could be implemented this way:
…
PUSHD your_root_directory
FOR /D %%D IN (*) DO (
CALL :movefiles "%%D"
)
FOR %%F in (*.docx *.pptx) DO (
MKDIR "%%~dpnF"
CALL :movefiles "%%~dpnF"
)
…
GOTO :EOF
:movefiles
SET "dirname=%~n1"
SET "mask=%dirname:~0,4%*"
MOVE "%~dp1%mask%" %1
Note: The steps #2 and #3 could be either implemented as separate loops or combined in one. The above script uses the latter approach.
You can use negative offsets in the !var:~offset,len! evaluation as follows:
set fspec=X32A-test.docx
echo !fspec:~-10!
echo !fspec:~0,-10!
That second line above gives you -test.docx and you can simply check that against your two desired possibilities with an if statement (or two).
Then you can use the third line to get the rest of the name for creating a directory.
The following example script shows how this could be done:
#setlocal enableextensions enabledelayedexpansion
#echo off
set fspec=X32A-test.docx
set bit1=!fspec:~-10!
set bit2=!fspec:~0,-10!
if .!bit1!.==.-test.docx. echo mkdir !bit2!
if .!bit1!.==.-test.pptx. echo mkdir !bit2!
endlocal
I'm echoing the mkdir command rather than executing it so you need to take out the echo. You'll also need to integrate the set and if statements into your loop but, based on what you have so far, you should have little trouble with that.
If, as you seem to indicate in a comment, the first four characters are the key and the last five decide on whether to make the directory, as in:
x32s-test.docx
a21w-production.pptx
xxxx-whatever_the_blazes_you_want.some_other_rubbish.docx
Then you're really only interested in the first four and last five:
#setlocal enableextensions enabledelayedexpansion
#echo off
set fspec=a12b-whatever_the_blazes_you_want.some_other_rubbish.docx
set bit1=!fspec:~-5!
set bit2=!fspec:~0,4!
if .!bit1!.==..docx. echo mkdir !bit2!
if .!bit1!.==..pptx. echo mkdir !bit2!
endlocal
This checks the correct extensions and outputs:
mkdir a12b
as expected.

Resources