Over the past few years I've really found Stackoverflow to be very helpful, and decided to create an account - this is my first post.
Example situation - I have a fair few of these images, of different subjects -
AAA_BBB_randomDigits_front.jpg
AAA_BBB_randomDigits_left.jpg
AAA_BBB_randomDigits_right.jpg
ZZZ_EEE_randomDigits_front.jpg
ZZZ_EEE_randomDigits_left.jpg
ZZZ_EEE_randomDigits_right.jpg
I would like them to all be grouped up in folders as -
AAA_BBB_randomDigits\(contains left, front and right)
ZZZ_EEE_randomDigits\(contains left, front and right)
The code I currently have works -
#echo off
for /f "tokens=1-3 delims=_" %%a in ('dir /b /a-d *_*_*_*.*') do (
md "%%a_%%b_%%c" 2>nul
move "%%a_%%b_%%c*" "%%a_%%b_%%c"
)
pause
However, I would love it if someone could explain to me -
What's %%a?
What's dir /b /a-d and why do I need it?
Is it neccessary to have #echo off and pause?
Thanks guys, I really appreciate it.
For documentation, see commandname /? from the prompt.
dir /b /a-d filemask performs a directory listing /b specifies filenames only - no size, date, header or footer. The /a-d excludes directorynames.
You need it to provide the names to the for /f command.
for /f reads the "filename" in parentheses (it can be a real filename or a single-quoted command (like dir) or a double-quoted literal string) and assigns values to the metavariable (in this case, %%a) according to the specified options (the part in quotes directly following the /f).
The delims option specifies which set of characters is used for parsing the line of data arriving from the "file" specified. The line is then interpreted as a series of tokens, separated by delimiter-sequences. By default, delims is Space and Tab. It's common to turn delims off entirely using "...delims=" in which case, there is but one token (the entire line). Note that any characters between delims= and " are equally-ranking and case-sensitive - it is a set of delimiters which replaces Space and Tab, not a delimiter-string.
The tokens option specifies which tokens are selected, by number, starting at 1. The special token * means "the remainder of the line following the highest-number token specified (including any delimiter characters)". By default, tokens=1.
%%a is a metavariable. It is the variable that holds the first token number selected from the tokens= list. Each selected token number is assigned to the next metavariable name in alphabetical sequence, hence in your example, since you have tokens=1-3 then %%a is the first token, %%b the second and %%c the third. Metavariables are always one letter (some other characters are sometimes used - but numerics are definitely not allowed) and the name is case-sensitive (normally, batch is case-insensitive). %%a, %%A and %a% are all different variables. %a% and %A% are the same variable.
A metavariable is only valid within the for loop where it was created. When the for loop ends, the variable disappears.
#echo off simply turns off the command-echoing that batch would otherwise produce (show the command on the console, then execute it). It's used to reduce clutter on the display. When debugging a section of code, it's normal to set echo to on (echo on) and then off again (echo off) to show precisely what instructions are being executed. The # means "don't report this command to the console"
The pause simply waits until a response is received from the keyboard. It's used to allow the display to be read rather than simply continuing directly to the next instruction. It's often used in debugging and also to allow the result of a batch to be held for the user if the batch is executed by using point-click-and-giggle.
Related
I am looking to automatically clean directories that contain original photos and smaller resolutions of a single photo.
I have the following structure for file names
original_image.jpg
original_image-1024x768.jpg
original_image-800x600.jpg
original_image-640x480.jpg
Is there a way, using a windows script (cmd, not PowerShell) to look through files in a directory, and delete any files that has the same name followed by a dash, a group of digits, and x, another group of digits, then the samme extension as the original file?
OOh - not that easy! Be careful!
#ECHO OFF
SETLOCAL enabledelayedexpansion
rem The following setting for the source directory is a name
rem that I use for testing and deliberately include names which include spaces to make sure
rem that the process works using such names. These will need to be changed to suit your situation.
SET "sourcedir=u:\your files"
FOR /f "delims=" %%b IN (
'dir /b /a-d "%sourcedir%\*" ^|findstr /i /v /r ".*-[0-9]*x[0-9]*.*" '
) DO (
SET "filter=%%~nb-[0-9]*x[0-9]*\%%~xb"
SET "filter=!filter: =\ !"
FOR /f "delims=" %%e IN (
'dir /b /a-d "%sourcedir%\%%~nb*%%~xb" ^|findstr /i /r "!filter!" '
) DO ECHO DEL "%%e"
)
GOTO :EOF
Always verify against a test directory before applying to real data.
The required DEL commands are merely ECHOed for testing purposes. After you've verified that the commands are correct, change ECHO DEL to DEL to actually delete the files.
The outer loop (%%b) processes a 'dir' list of the directory /a-d without directorynames and /b in basic form (names only - no headers. footers or details.)
The list is passed to a findstr command to filter the filename pattern required. The pipe | must be escaped by a caret^ to tell cmd that the pipe belongs to the single-quoted command to be executed, not to the for.
The findstr filter is /i case-insensitive /r using a regular expression. The /v option outputs those lines that do not match the filter. The regular expression is .* any number of any character, - -literal dash [0-9]* any number of numeric characters x literal "x" [0-9]* any numerics again and .* any characters.
The delims= causes the filenames to be delivered literally to %%b by setting no delimiters and hence just one token. See for /? from the prompt or endless examples on SO for documentation.
Next step is to set up the filter for the next findstr. This is %%~nb the name part of the filename in %%b and %%~xb the extension part (including the dot). The \ escapes the dot contributed by %%~xb, making it a literal dot instead of a single-character-match.
The next step replaces each "space" with "<kbd>space" See set /? from the prompt or endless examples on SO for documentation.
Finally, execute another dir but this time, look for files matching the pattern of the filename and the extension of %%b, separated by anything and filtering using the string established in filter.
delayedexpansion is required since filter is being changed within a code block (parenthesised sequence of lines) - so !var! retrieves the current value of the variable where %var% is the original vale (when the block was encountered).
Why the extra complexity?
Suppose the file list includes
original_image.jpg
original_image-1024x768.jpg
original_image-800x600.jpg
"original image.jpg"
"original image-1024x768.jpg"
"original image-800x600.jpg"
Then because a Space in a findstr causes an or of the strings before and after the space, so whereas the %%e dir selects on those files matching "original image*.jpg", this includes "original image.jpg". The regex constructed would be "original image-[0-9]*x[0-9]*\.jpg" which matches "original" and therefore "original image.jpg" will be selected for deletion.
I am looking for a list of attributes and what they do in a for loop inside the command prompt.
Specifically I have a .bat file that copies a file from the root of the C:\ drive and pastes it inside all folders found in a pre-specified directory (i.e. C:\Users\John\Test Directory).
This is the command:
#echo off
for /D %%a in (C:\Users\John\Test Directory\*.*) do xcopy /y /d C:\test_file.txt "%%a\"
The .bat does exactly what I need it to do, but I do not understand what the "%%a" does in the command. I see similar commands that use %%g, %%f, etc, but nothing that defines why those were chosen or what they specifically do. Are those attributes arbitrary or do they have a defined function? I seemingly can't find any information on the attributes so any insight is appreciated!
Arbitrary. You can use any letter, upper or lower, and even symbols.
for %%# in... do command %%#
would work just as well. But when working with multiple tokens per iteration, it's better to use the alphabet. Here's an example why:
for /f "usebackq tokens=1* delims==" %%I in ("textfile.txt") do (
set "config[%%~I]=%%~J"
)
This is because %%I contains the text matched prior to the first equal sign, and %%J contains everything after the first equal sign. This answer shows that example in context.
The answer to your question is hinted in the for command's documentation. help for in the cmd console for full details. Specifically:
Some examples might help:
FOR /F "eol=; tokens=2,3* delims=, " %i in (myfile.txt) do #echo %i %j %k
would parse each line in myfile.txt, ignoring lines that begin with
a semicolon, passing the 2nd and 3rd token from each line to the for
body, with tokens delimited by commas and/or spaces. Notice the for
body statements reference %i to get the 2nd token, %j to get the
3rd token, and %k to get all remaining tokens after the 3rd.
This page explains further:
FOR Parameters
The first parameter has to be defined using a single character, for example the letter G.
FOR %%G IN ...
In each iteration of a FOR loop, the IN ( ....) clause is evaluated and %%G set to a different value
If this clause results in a single value then %%G is set equal to that value and the command is performed.
If the clause results in a multiple values then extra parameters are implicitly defined to hold each. These are automatically assigned in alphabetical order %%H %%I %%J ...(implicit parameter definition)
If the parameter refers to a file, then enhanced variable reference can be used to extract the filename/path/date/size.
You can of course pick any letter of the alphabet other than %%G.
%%G is a good choice because it does not conflict with any of the pathname format letters (a, d, f, n, p, s, t, x) and provides the longest run of non-conflicting letters for use as implicit parameters.
G > H > I > J > K > L > M
Format letters are case sensitive, so using a capital letter is also a good way to avoid conflicts %%A rather than %%a.
Just in the interest of thoroughness, it should be pointed out that:
when using for in a cmd console, use single percents.
when using for in a bat script, use double percents.
Use a tilde when retrieving the iterative variable to strip surrounding quotation marks from the value. (e.g. "Hello world!" becomes Hello world!). It's convenient to use this to force a desired format. "%%~G" would always be quoted, whether the captured value was quoted or not. You should always do this when capturing file names with a for loop.
Tilde notation also allows expanding paths, retrieving file size, and other conversions. See the last couple of pages of help for in a cmd console for more details.
So I have a collection of art from the internet; it is very large and spans an extreme amount of subdirectories under the main folder I keep it all under (entitled Uda). I've built up this collection over a period of about 4 years. In the most recent 2 years, I've been organising things I save by artist (most of this comes from the site DeviantArt if it matters). But I have 2 years' worth of files unsorted, all over the place and I'd like to sort them.
I figure this is an ideal time to practice some batch scripting, but... I've not the faintest idea where to start. Google hasn't been very helpful, it's too hard to explain what I want to do in one question, let alone find someone who has needed the same thing and been guided through it. Also, I'd honestly much prefer to pull apart and figure out an already-made script to understand it and learn from it (that's how I tend to learn best).
So can anyone help me? If you don't understand what I want to do:
I wish to make a script where I can input something (i.e. an artist's name) and have a folder made for it under a certain directory (no matter where the batch is being run from or the files drawn from) with the name of the term that was entered, and then all files under another directory with that term found and moved.
For the record, even if I weren't interested in batch scripting, I couldn't use Windows Explorer to search for them all then cut them because I disabled it (for personal reasons).
#ECHO OFF
SETLOCAL
:: Note that SPACES are significant in simple SET statements
:: Set relative or local root (start-point of subtree to scan)
:: Use normal then safemethod 1 then safemethod2 - all do the same thing
:: Note : from the prompt,
:: command /?
:: will generally show help...
SET relroot=c:\wherever
(SET relroot=c:\wherever)
SET "relroot=c:\wherever"
:: repeat for destination directory
SET "destdir=c:\destination"
::
:: Read Artist
:: Make sure ARTISTS has no existing value, then prompt for input
::
:again
(SET artist=)
SET /p artist="Artist ? "
::
:: Check for input, exit if none
:: Note :EOF is a special predefined label meaning "END OF FILE"
:: character case is generally insignificant
::
IF NOT DEFINED artist GOTO :eof
::
:: make a new directory
:: the 2>nul suppresses an error message like 'it already exists'
:: In quotes in case variables contain spaces
MD "%destdir%\%artist%" 2>nul
::
:: Now look for filenames containing the data entered
:: Note: here, the metavariable %%i IS Case-sensitive
:: >nul means 'don't show message like '1 file(s) moved'
FOR /f "delims=" %%i IN (
' dir /s /b /a-d "%relroot%\*%artist%*" '
) DO (
ECHO %%i
IF EXIST "%destdir%\%artist%\%%~nxi" (
ECHO Cannot MOVE "%%i" because "%destdir%\%artist%\%%~nxi" exists
) else (ECHO MOVE "%%i" "%destdir%\%artist%\%%~nxi" >nul)
)
GOTO again
Well, here's a starter script - assuming that artists' names are in the filename.
Most of the documentation is in-line.
Note that the ::... form of documentation is actually a broken-label and is inadvisable within loops and parenthesised code generally - use REM there. It is however easier to type and less intrusive
The FOR loop needs a little explanation: much of it can be deciphered with a bit of persistence from the documentation
for /?
from the prompt. But the heads-up is:
for /f reads a "file" line-by-line and applies each successive line to the metavariable after being tokenised between delimiters. You can specify the tokens by number counting from 1 and the delimiters are any characters appearing between the delims= and the closing quote. Default delimiters are [space],[tab],[comma],[semicolon] and default tokens is 1. "delims=" specifies that there are no delimiters, so the entire line is applied to the metavariable.
It's possible to use this facility on a data line like Wed. 07/11/2012 by using FOR/f "tokens=2,3,4delims=/ " %%i in... to apply 07 to %%i, 11 to %%j and 2012 to %%k - the tokens are Wed.,07,11 and 2012 when the line is tokenised using [either space or /] and the 2nd token is applied to the loop metavariable %%i, the third to %%j and so on through the list of token numbers. The special token "*" means 'the rest of the line following the highest-number token nominated'
AND.. a single-quoted "filename" is the output of a command. dir /s /b /a-d "%relroot%\*%artist%*" is the directorylist in /b basic form (filenames only) /s scan subdirectories /a-d but don't mention the war directorynames starting at %relroot% and having %artist% somewhere in the filename - all quoted in case spaces are present.
I wont write it for you but, but here is a list of roughly the things you have to achieve. Try looking for each of those and then combine them
input a name
create a directory with this name
find the files with this name
move found files to the directory
if you post code of what you are trying i'll gladly help you.
I'm not sure whether it is possible, but what I need is a plain bat/cmd file that runs on windows 7 and does such things:
Step 1. findstr - it should find a specific string using regular expressions engine. Suppose we're looking for a number enclosed in tags <id>123</id> (suppose such a file is unique, so one value is returned). The command would print 123 to the screen, but I need to save it in a variable (don't know how).
Step 2. Another call to findstr on another directory. Now we want to find a file NAME (/m option) containing the value that we saved on Step 1 (in another group of files i.e. another directory). And again, save the result (name of the file) in a variable. Say, file_123.txt matches the criteria.
Step 3. Copy the file that we got as a result of the second findstr call (file_123.txt) to another location.
The whole question turns around the point about how to save the result of windows commands to variables to be able to provide these values to subsequent commands as parameters.
The general way of getting command output in variables is
for /f %%x in ('some command') do set Var=%%x
(with various variations, depending on context and what exactly is desired).
As for your steps, I elaborate after lunch. There are some intricacies.
Step 1:
FOR /F "USEBACKQ tokens=1-20 delims=<>" %%A in (`FINDSTR "123" "path of file to search in"`) DO (
SET var=%%B
)
ECHO %var%
Understand that delims will change depending on what 'separates' the parts of the output (whether its a space, a special character, etc.)
Step 2 & 3:
FOR /F "USEBACKQ tokens=*" %%A IN (`DIR "Path" /A ^| FIND /I "%var%"`) DO (
COPY /Y "%%A" "C:\New\Path\%%~nxA"
)
What does this bat code do?
for /f %%i in ('dir /b Client\Javascript\*_min.js') do (
set n=%%~ni
set t=!n:~0,-4!
cp Client\Javascript\%%i build\Client\Javascript\!t!.js
)
What does %%~ni,~n:~0,-4!,%%i,!t! mean?
Keep in mind that in batch files, you need to escape percentage signs unless you're referring to arguments given to the batch file. Once you remove those, you get
for /f %i in ('dir /b Client\Javascript\*_min.js') do (
set n=%~ni
set t=!n:~0,-4!
cp Client\Javascript\%i build\Client\Javascript\!t!.js
)
%i is the declaration of a variable used to place the current file for has found. %~ni extracts the filename portion of %i. !n:~0,-4! uses delayed expansion to remove the last four characters from %n% (set in the previous line) !t! is simply delayed expansion of the %t% variable set in the previous line.
Delayed expansion is used because otherwise, the variables will be substituted as soon as the line is encountered, and future iterations will not re-expand the variable.
for /f %%i in ('dir /b Client\Javascript\*_min.js') do (
Iterate over every file in the Client\Javascript folder that match "*_min.js". Thedircommand andfor /f` are totally unneeded here, though and only complicate things, especially when file names contain spaces, commas and the like. A more robust and simpler alternative would be
for %%i in (Client\Javascript\*_min.js) do (
But that's just beside the point. People tend to write unelegant batch files sometimes, ignoring the pitfalls and common errors. That's just one example of that.
set n=%%~ni
Creates a variable n, containing the file name (without any directory information or extension) of the file currently processed. We remember that the for statement iterates over every file it finds. With this line starts what it does with those files.
set t=!n:~0,-4!
Creates a second variable, t, containing everything but the last four characters of the file name. This essentially strips away the "_min"
cp Client\Javascript\%%i build\Client\Javascript\!t!.js
Finally, this copies the original file to the directory build\Client\Javascript with the new name, just constructed. So a file like Client\Javascript\foo_min.js will be copied to Client\Javascript\foo.js. The !t! here is just a delayed-evaluated environment variable. More on that below. Here it should suffice that it just inserts the contents of said variable at that point in the line.
Again, bad practice here that will break in numerous interesting ways:
cp is not a command on Windows so this batch will assume cygwin, GNUWin32 or similar things installed. I tend to avoid having too many unneeded dependencies and stick to what Windows provides; in this case the copy command. Two bytes won't kill anyone here, I think.
No quotes are around either argument. Leads to interesting results when spaces start appearing in the file name. Not good, either.
As for why delayed expansion was used (! instead of % surrounding the variables: The for command consists of everything in the block delimited by parentheses here as well. The entire block is parsed at once and normal variable expansion takes place when a line/command is parsed. That would mean that every variable in the block would be evaluated before the loop even runs, leaving just the following:
for /f %%i in ('dir /b Client\Javascript\*_min.js') do (
set n=%%~ni
set t=
cp Client\Javascript\%%i build\Client\Javascript\.js
)
which is certainly not what you want in this case.
Delayed expansion is always needed when creating and using variables in a loop such as this. A workaround not needing delayed expansion would be to offload the loop interior into a subroutine:
for /f %%i in ('dir /b Client\Javascript\*_min.js') do call :process "%%i"
goto :eof
:process
set n=%~n1
set t=%n:0,-4%
copy "Client\Javascript\%~1" "build\Client\Javascript\%t%.js"
goto :eof
Since the subroutine is not a single "block" (something delimited by parentheses) it will be parsed line by line as usual. Therefore it's safe to use normal expansion instead of delayed expansion here.
A complete help for the FOR command can be found on the Microsoft TechNet site. See here for more information on delayed expansion :
// Pseudo code
for each file named *_min.js in the specified directory
n is set to the file name (*_min)
t is set to the file name, excluding the last 4 characters (*)
the file is copied and renamed t.js to the specified directory
%~ni expands to just the filename part of i.
!n:~0,-4! expands to all but the last four characters of n.
In general, help for at the command prompt will give an overview of the multitude of ways for can expand variables these days.