I am trying to make a relative shortcut using a batchfile and a program to turn the batch in an exe program with an icon.
I need a 'shortcut' to open the next alfabetic parallel folder in an explorer window and one to open the previous. Ideally I would even like it to to close the explorer window used to doubleclick it.
I have so far:
#echo off
echo %cd%
for %%a in ("%cd%") do set folder=%%~na
pushd ..
echo %folder%
echo .
dir /A:D /B
pause
the %folder% has the name of the folder (not path!) from where the batch is executed.
the line: dir /A:D /B gives you an output of multiple lines giving you all the parallel folders (because I go up a level using pushd ..). I really need to find a way to search for the %fodler% value and pick the line above, or below it.
I tried something using for /f but it is not that usefull when processing multiple lines instead of one single string.
anny ideas?
Even though it is not quite clear to me what exactly you want to achieve, the following snippet should do what you want. Simply add it to your script:
setlocal enabledelayedexpansion
for /f %%a in ('dir /b /ad /on') do (
#rem shift the current value through the variables
set previous=!current!
set current=!next!
set next=%%a
#rem check if the "current" value is the right one
if "!current!"=="%folder%" goto :found
)
#rem if we get here the loop has finished without %current% having the expected value
#rem but we need to check if it was the last folder in the directory
if "%next%"=="%folder%" (
set previous=%current%
set current=%next%
set next=
goto :found
)
endlocal
#rem exit here if no match is found (should never happen)
goto :eof
#rem variables should have valid values
:found
echo %previous%
echo %current%
echo %next%
Explanation:
The 3 variables previous, current and next are use like a shift register. In each loop iteration the current directory value is shifted by one place through the variables
At the end of the shifting the current variable is tested for the desired folder
If the loop ends before the condition is true, this means the last folder is the correct one, hence that trailing shifts.
Hope that helps...
Related
I am looking to make a batch file which will sift through a directory full of computer backups. The file format is "computername-date." Since I know the computer name is static, I need to find and take that directory so I can restore it's contents.
I never realized that for loops are so foreign from what I play with in other languages, so I find myself getting nowhere anytime soon.
REM First mount the drive that contains the backed up files
net use P: \\DC1\Shared\1Backups
REM Get the computer's name so we know what PC backup to use.
set SaveDirectory=%computername%
REM For each folder in the directory, do this when the computer name is found.
FOR /R "P:\" %%G in (%SaveDirectory%*) DO (
REM Restore files
echo found it.
REM Copy subdirectories into User Folder
mkdir P:\UGH
)
REM Dismount drive
The problem with what I have now is that when I run the code, the DO never runs. It should find that there is a folder called "INTERN-6.21.2019" by searching "INTERN*"
My impression of the for statement may be wrong. I need it to search through the P:/ Directory, not the subfolders. Compare the folder names to the SavedDirectory, then do something when they match. What am I doing wrong?
I've normally had good results with using CALL statements to invoke a subroutine rather than running items inside ( ).
The trick here is that you need to pass arguments to the the subroutine. Typically you'd pass the variable(s) you use in your FOR statement. These are referenced using %1 (or %1 %2 %3 if you have multiple things to pass) because these act like command line arguments. To exit the subroutine you then jump to EOF which then returns control to your FOR loop. After your FOR loop, don't forget to jump over your subroutine or you'll execute it again. In the example below I jump to :end, but you certainly could jump to somewhere else to do more things.
So using this methodology, your code might be like this:
set SaveDirectory=%computername%
FOR /R "P:\" %%G in (%SaveDirectory%*) DO CALL :process %%G
Goto :end
:process
REM Processing goes here
goto :end
:end
Hope this helps
This might get you going. This will find the "newest" directory. It is not clear from the question exactly what is to be copied and to where.
SET "MOSTRECENT=unknown"
FOR /F "delims=" %%d IN ('DIR /B /A:D /O:D "P:\%COMPUTERNAME%*"') DO (
SET "MOSTRECENT=%%~fd"
)
ECHO The most recent backup is "%MOSTRECENT%"
IF EXIST "MOSTRECENT" (COPY ...
This answer is based upon my understanding of what you're trying to do, copy a network directory named with the computername and a datestring, (with todays date), to another location:
#PushD "\\DC1\Shared\1Backups" || GoTo :EOF
#For /F "EOL=DTokens=1-3" %%A In (
'WMIC Path Win32_LocalTime Get Day^,Month^,Year'
)Do #For /F Tokens^=* %%D In ("%%C"
)Do #XCopy "%COMPUTERNAME%-%%B.%%A.%%D" "P:\UGH\" /H /R /K /Y>NUL 2>&1
#PopD
I'm working with windows' cmd and trying to set a variable in a loop. Here's the code I have:
for /d %%a in ("F:\backup*") do set folder=%%a
ECHO %folder%
PAUSE
I want to look for a folder with name starting with "backup" on drive F and save that folder's name to %folder% variable. So for example if the folder would be called "backup 2017-01-18" I'd like that saved to a var.
Instead it doesn't seem to set anything as the ECHO just prints that "ECHO is on". The for loop is correct and the folder is there as well (I'm already using that piece of code for other batch with robocopy).
I could theoretically put all my code inside the FOR loop and use %%a instead of the %folder% var but that seems like a hacky solution.
All the solutions I found so far pointed to using EnableDelayedExpansion. I modified the code to use it like that:
Setlocal EnableDelayedExpansion
for /d %%a in ("F:\backup*") do set folder=%%a
ECHO !folder!
PAUSE
But now ECHO prints "!folder!" as if it would not detect the variable. If I revert to ECHO %folder% I once again learn that "ECHO is on".
EDIT:
I found the issue here. I was also running another batch file on the backup folder. It turns out that ROBOCOPY (which I used in that batch) is setting the enclosing folder to hidden, system and readonly by default (even if copied files are not hidden or system o_0). When I removed HSR attributes on the directory the code posted here started working fine (the initial version).
If your loop for /d %%a in ("F:\backup*") do does not detect any directories whose names begin with backup, they either do not exist or there are the attributes hidden and/or system set.
To detect also such hidden or system directories, replace the for /D loop by this:
rem Change to parent directory "F:\" temporarily in order for the `~f` modifier to resolve the full path properly:
pushd "F:\" || exit /B 1
for /F "eol=| delims=" %%D in ('dir /B /A:D /O:N "backup*"') do set "folder=%%~fD"
popd
I'm trying to rename .jpg files which is in one of many subdirectories of e:\study\pubpmc\test\extracted.
I want to rename files to LastFolderName_ImageName.jpg.
(For example if Figure1.jpg is in e:\study\pubpmc\test\extracted\folder1
I want it to be renamed like this: folder1_Figure1.jpg)
So I need to take out the last folder name from the file's path.
Since it's my first time with batch scripting, I'm having a hard time.
I googled and made code similar to it
but it doesn't seem to work out.
Can you help me with it and tell me where I've done wrong?
Thank you! :)
#echo off
cd /D "e:\study\pubpmc\test\extracted"
for /r %%f in (*.jpg) do (
set mydir=%%~dpf
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
)
JosefZ explains the obvious problems with your code, but he failed to point out a subtle problem, though his code fixed it:
FOR /R (as well as the simple FOR) begin iterating immediately, before it has finished scanning the disk drive. It is possible for the loop to reiterate the already named file! This would cause it to be renamed twice, giving the wrong result. The solution is to use FOR /F with command 'DIR /B', because FOR /F always processes the command to completion before iterating.
JosefZ also provides code that works for most situations. But there is a much simpler solution that works always:
#echo off
for /f "delims=" %%A in (
'dir /b /s /a-d "e:\study\pubpmc\test\extracted\*.jpg"'
) do for %%B in ("%%A\..") do ren "%%A" "%%~nxB_%%~nxA"
The "%%A\.." treats the file name as a folder and walks up to the parent folder. So %%~nxB gives the name of the parent folder.
The command could be run as a long one liner, directly from the command line (no batch):
for /f "delims=" %A in ('dir /b /s /a-d "e:\study\pubpmc\test\extracted\*.jpg"') do #for %B in ("%A\..") do #ren "%A" "%~nxB_%~nxA"
Avoid using :label and :: label-like comment inside (command block in parentheses). Using any of them within parentheses - including FOR and IF commands - will break their context.
Using variables inside (command block in parentheses). Read EnableDelayedExpansion: Delayed Expansion will cause variables to be expanded at execution time rather than at parse time [and CLI parses all the (command block in parentheses) at once]
Next script should work for you. Note rename statement is merely echoed for debugging purposes.
#ECHO OFF >NUL
SETLOCAL enableextensions disabledelayedexpansion
set "fromFolder=e:\study\pubpmc\test\extracted"
rem my debug setting set "fromFolder=D:\path"
for /F "tokens=*" %%f in ('dir /B /S /A:D "%fromFolder%\*.*"') do (
set "mydir=%%~ff"
set "last=%%~nxf"
call :renameJPG
)
#ENDLOCAL
goto :eof
:renameJPG
rem echo "%mydir%" "%last%"
for /f "tokens=*" %%i in ('dir /B /A:-D "%mydir%\*.jpg" 2^>nul') do (
echo ren "%mydir%\%%~nxi" "%last%_%%~nxi"
)
goto :eof
Resources:
SETLOCAL, disableDelayedExpansion, ENDLOCAL etc.
An A-Z Index of the Windows CMD command line
Windows CMD Shell Command Line Syntax
I already wrote a function for that. You give it any path and it returns you only it's filename or pathname. Works for any path: Url, Windows path, Linux path, etc...
Copy this function at the end of your batch script: (Instructions below)
rem ===========================================================================
:Name_From_Path
SetLocal
set _TMP_FOLDERNAME=%1
for %%g in ("%_TMP_FOLDERNAME%") do set _TMP_FOLDERNAME=%%~nxg
EndLocal & set _Name_From_Path=%_TMP_FOLDERNAME%
goto :EOF
rem ===========================================================================
Usage:
CALL :Name_Of_Path e:\study\pubpmc\test\extracted\folder1
ECHO %_Name_From_Path%
Result: folder1
If your program or com file traverses these folders when renaming, then it should be able to get the present working directory ( path ), pwd. You may be able to chop everything but the LAST_FOLDER out of this by also creating a PREVIOUS_FOLDER and doing a string replacement.
Or you may be able to break the folder names at the '\' token from the pwd into an array and use a -1 array reference to get the last folder name.
In any circumstance you'll want to check for a present working directory command.
If your creating a large text list of all these and issuing a single call to the batch file.
Then you may be better off with something like:
(Symmantic code warning )
(copy) /folderbase/some_folder/oneormore1/image_from_oneormore1.jpg (to) /folderbase/some_folder/oneormore1/oneormore1_image_from_oneormore1.jpg
Instead of copy, window uses rename, linux uses mv.
The latter example would require simply creating a duplicate list and replacing the \ with a _ while parsing through the tokens.
The code you've given is difficult to make sense of, so its hard to discern if you can simple concatenate the current folder and image name (stringify) and then write or rename them where they are.
I want to write batch file which will loop through all directories containing backup directory and remove files older than X days within it.
On computer which I want run my script there's no "forfile" command.
There's no PowerShell, so CMD or VBScripts seems to be only way of accomplishing this task.
Currently I have problem with "set" statement - it seems that when I'm calling %checkpath% I didn't receive expected folder.
rem we will memorize current directory
pushd %cd%
set folder="C:\Documents and Settings\myname\Desktop"
cd %folder%
rem loop only folders with five chars within their names (unfortunately on less also
for /D %%g in (?????) DO (
set checkpath="%cd%\%%g\backup"
if exist %checkpath% (
for %%a in ('%%g\backup\*.*') do (
set FileDate=%%~ta
set FileDate=%%FileDate:~0,10%%
rem here I want to compare file modification data with current date
)
)
popd
pause
You need to use delayed expansion to read a variable that you have set inside a for loop.
Try this instead
setlocal enabledelayedexpansion
rem we will memorize current directory
pushd %cd%
set folder="C:\Documents and Settings\myname\Desktop"
cd %folder%
rem loop only folders with five chars within their names (unfortunately on less also
for /D %%g in (?????) DO (
set checkpath="%cd%\%%g\backup"
if exist !checkpath! (
for %%a in ('%%g\backup\*.*') do (
set FileDate=%%~ta
set FileDate=!%FileDate:~0,10%!
rem here I want to compare file modification data with current date
)
)
popd
pause
Replacing the %'s with !'s on variables you have created will signal it to use delayed expansion instead.
Bali's answer has a slight mistake. The second set filedate is incorrect, otherwise his is fine, but may not work if you do not have delayed expansion enabled. I fixed his mistake and showed you how to ensure delayed expansion is enabled. I have also made some other changes:
::This command will ensure that the delayed expansion, i.e. the "!"s below,
:: will work. Unfortunately, it also means you loose the results of any
:: "set" commands as soon as you execute the "endlocal" below.
setlocal ENABLEDELAYEDEXPANSION
::you might want
::set "folder=%USERPROFILE%\Desktop"
set "folder=C:\Documents and Settings\myname\Desktop"
rem we will memorize current directory
::If you ran this code with the current directory set to a directory on
::a drive other than C:, your previous code would not have worked to
:: change to your desired target directory. this slight change fixes that.
pushd "%folder%"
rem loop only folders with five chars within their names - unfortunately on less also
::Use capitals for your loop variables. The var names are case sensitive, and
::using capitals ensures there is no confusion between the var names and ~modifiers
for /D %%G in ( ????? ) DO (
set checkpath="%CD%\%%G\backup"
if exist !checkpath! (
for %%A in ('%%G\backup\*.*') do (
set FileDate=%%~tA
set FileDate=!FileDate:~0,10!
rem here I want to compare file modification data with current date
)
)
popd
endlocal
pause
But, you don't need the "setlocal" or delayed expansion if you write it like this:
::you might want
::set "folder=%USERPROFILE%\Desktop"
set "folder=C:\Documents and Settings\myname\Desktop"
rem we will memorize current directory
::If you ran this code with the current directory set to a directory on
::a drive other than C:, your previous code would not have worked to
:: change to your desired target directory. this slight change fixes that.
pushd "%folder%"
rem loop only folders with five chars within their names - unfortunately on less also
for /D %%G in ( ????? ) DO (
if exist "%%~G\backup" (
for %%A in ('%%~G\backup\*.*') do (
for /F "usebackq" %%T in ( '%%~tA' ) do (
echo File date is %%T, todays date is %DATE%
)
)
)
popd
pause
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.