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.
Related
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.
I create a lot of hardlinks every week. When time comes to clean them, I find myself using the "DeleteAllHardlinks.bat" for ln (https://schinagl.priv.at/nt/ln/ln.html) but I have to drag and drop everyfile one after the other.
I would love to find a way to just select 100 files and drop them on the .bat, wait a while and find all those files and hardlinks deleted for good. Is there anyway to change the .bat file to allow this? (or maybe any other different method to acomplish the same?)
#echo off
REM
REM Check for commandline args
REM
if "[%~1]" == "[]" goto error
set LN=ln.exe
REM
REM List hardlink sibblings and delete all siblings
REM
for /f "delims=" %%a in ('#%LN% --list "%~1"') do (
del /f "%%a"
)
goto ausmausraus
:error
echo DeleteAllHardlinks: Argument is missing. Usage DeleteAllHardlinks ^<filename^>
echo e.g. DeleteAllHardlinks c:\data\myfile.txt
:ausmausraus
echo on
Thanks in advance!
Big thanks to Mofi!
The batch file could be very easily modified to support not just first argument, but all file name argument strings passed to the batch file by using one more for loop and %* as explained by call /?, i.e. use as replacement for the existing for loop:
for %%I in (%*) do for /F "delims=" %%J in ('ln.exe --list "%%~I" 2^>nul') do del /F "%%~J"
But the application starting the batch file has to pass each file name enclosed in double quotes to work properly.
Just using the for as offered in the comment solved the issue perfectly.
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 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
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.