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
Related
I am trying to rename every image in a directory to add the date that each file was created, however, I keep either getting "invalid syntax" or "A duplicate file name exists, or the file cannot be found"
I am running Windows 10, and accessing the images off a flash drive (hence the short file path). I tried having all the code in one for-loop, when that didn't work I tried using batch functions, no dice. I did see someone mention on another thread to use delayed expansion, I would be up for using this if someone could give a better explanation than the /? command.
#echo off
REM batch file is placed in top of F drive, same as "images 2017+"
cd "F:\images 2017+"
FOR /R "F:\images 2017+" %%F in (*.jpg) do call :renER "%%~nF" "%%~tF"
goto :eof
:renER
cd "F:\images 2017+"
pause
echo %1
echo %2
rename %1.jpg %1_%2.jpg
pause
goto :eof
:end
For every .jpg file in "images 2017+", the date which that file was created would be stuck onto the end after a space.
thisIsMyFile.jpg made at 5-13-2017, would become thisIsMyFile 5-13-2017.jpg
Current output
EDIT:
I am CDing into the same directory as the images are, then using the passed variables to locate the correct image (The date is one of the passed variables, and shows up in the echo command).
I notice that you only want the date, not the time so you can do that as follows using your existing Call to a label, There is also no need to use FOR /R in this case so I'll use a normal for loop:
#echo off
FOR %%A IN ("F:\images 2017+\*.jpg") DO (
CALL :RenER "%%~fA" %%~tA
)
GOTO :eof
:RenER
PAUSE
ECHO %1
ECHO %2
SET "_tmp=%~2"
SET "_tmp=%tmp:/=-"
REN "%~1" "%~n1_%_tmp%%~x1"
PAUSE
GOTO :eof
Notice how above we are dropping the Time off immediately by not wrapping it in quotes since you don't want that to be part of the file name.
You can also forgo the call to a label entirely without needing delayed expansion by using a second loop, as a matter of preference I think this is quite a bit cleaner!
#echo off
FOR %%A IN ("F:\images 2017+\*.jpg") DO (
FOR /F "Tokens=1-3 Delims=/ " %%a IN ('echo.%%~tA') DO (
PAUSE
ECHO.%%~fA
ECHO.%%~tA
REN "%%~fA" "%%~nA_%%a-%%b-%%c%%~xA"
PAUSE
)
)
this is nice and clean and with a minor edit we can paste it directly into the CMD Prompt which is nicer still This is because we are not using DelayedExpansion, Calling a Label, or using Temp variables so by changing the %%s to %s, we can then Paste this directly into the CMD Line which is often more convenient when doing these sorts of operations:
This Multi-line will do just fine to be pasted into CMD directly:
FOR %%A IN ("F:\images 2017+\*.jpg") DO (
FOR /F "Tokens=1-3 Delims=/ " %a IN ('echo.%~tA') DO #(
PAUSE
ECHO.%~fA
ECHO.%~tA
REN "%~fA" "%~nA_%a-%b-%c%~xA"
PAUSE
)
)
or, as a single line to paste into CMD if you prefer:
FOR %A IN ("F:\images 2017+\*.jpg") DO #( FOR /F "Tokens=1-3 Delims=/ " %a IN ('echo.%~tA') DO #( PAUSE& ECHO.%~fA& ECHO.%~tA& REN "%~fA" "%~nA_%a-%b-%c%~xA"& PAUSE ) )
no need to cd anywhere. ren takes a full path/filename for source - just the destination must be a filename only. So ... do call :renER "%%~fF" "%%~tF" is fine (no need to snip the extension and add it again later). In the subroutine reformat the time to a valid string and reassemble the destination file name:
#echo off
FOR /R "F:\images 2017+" %%F in (*.jpg) do call :renER "%%~fF" "%%~tF"
goto :eof
:renER
pause
echo %1
echo %2
set "string=%~2"
set "string=%string::=-%"
set "string=%string:/=-"
ECHO rename "%~1" "%~n1_%string%%~x1"
pause
goto :eof
:end
NOTE: I disarmed the rename command. Remove the ECHO after troubleshooting, if it works as intended.
#Stephan's answer is probably the best approach. But if you want to change directories ...
The windows shell has a working drive/volume, and on each drive/volume a current working folder. cd changes the working folder on a disk; to change the working folder on a drive (which is not the working drive) and to make that drive the working drive, you need to use cd /d, in this case cd /d "F:\images 2017+".
(A plain cd in this instance changes the working folder on F:\, but if your working folder is on C: -- as I'm guessing is the case -- it will not be changed.)
Assuming command extensions are enabled, you should also be able to use pushd and popd. pushd behaves like cd /d but also saves your previous location; popd returns you to that previous location. (And IIRC pushd will accept UNC paths.)
So at the beginning of your script, pushd "F:\images 2017+", and at the end popd.
I tend to favor pushd/popd over cd because invocations can be nested. So you can do things like
(assume working directory is C:\Users\IoCalisto):
pushd "F:\images 2017+"
(working directory is now F:\images 2017+)
pushd "Z:\images 2015-2016"
(working directory is now Z:\images 2015-2016)
popd
(working directory is now F:\images 2017+)
popd
(working directory is now C:\Users\IoCalisto)
... with this approach, your scripts will have fewer "side effects" and be more modular, or at least modularizable.
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 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.
Due to a Dreamweaver setting mess-up, we've had thousands of "_notes" folders pop up in our websites dev & qa areas. There's too many to delete through Windows Explorer - everything just locks up - so I was hoping to run a batch script to sort it out for us once and for all. The problem is I'm not entirely sure that "rd /S" will do what I want.
My understanding is that rd /S will look recursively in the folder I tell it, so if I say:
rd /S r:/<siteName>/_notes/
then it will just look in the _notes folder and delete what's in there and then try to move further down that tree. What I need is a script that would take into account things like the following:
r:/<siteName>/_notes/
r:/<siteName/<someFolder>/_notes/
r:/<siteName/<someOtherFolder>/_notes/
r:/<siteName/<someFolder>/<someSubFolder>/_notes/
r:/<siteName/<someFolder>/<iThinkIveMadeMyPoint>/_notes/
Hope I made sense...
I found this in another thread, but it doesn't work with folders with a . in the name, so it's no use for site names...
#Echo OFF
REM Important that Delayed Expansion is Enabled
setlocal enabledelayedexpansion
REM This sets what folder the batch is looking for and the root in which it starts the search:
set /p foldername=Please enter the foldername you want to delete:
set /p root=Please enter the root directory (ex: C:\TestFolder)
REM Checks each directory in the given root
FOR /R %root% %%A IN (.) DO (
if '%%A'=='' goto end
REM Correctly parses info for executing the loop and RM functions
set dir="%%A"
set dir=!dir:.=!
set directory=%%A
set directory=!directory::=!
set directory=!directory:\=;!
REM Checks each directory
for /f "tokens=* delims=;" %%P in ("!directory!") do call :loop %%P)
REM After each directory is checked the batch will allow you to see folders deleted.
:end
pause
endlocal
exit
REM This loop checks each folder inside the directory for the specified folder name. This allows you to check multiple nested directories.
:loop
if '%1'=='' goto endloop
if '%1'=='%foldername%' (
rd /S /Q !dir!
echo !dir! was deleted.
)
SHIFT
goto :loop
:endloop
read HELP FOR, HELP SET and HELP IF
note that FOR /D /R will recursively walk the directory tree.
note also that %~na is the funny syntax to extract the name part of a full path.
so, putting this little pieces togethere, try this command on the command line
for /d /r %a in (*) do #if %~na==_notes #echo rd %a
after careful testing, remove the echo command.
This command has worked for me and I hope this could help. Switch to the common root folder, and type in CMD:
for /d /r . %d in (<folder name>) do #if exist "%d" rd /s/q "%d"
Change the to the name of folder you want to remove. Then all children folders with this name would be removed.
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...