tags:

views:

252

answers:

4
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?

I'm totally missed up.

+1  A: 

%~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.

moonshadow
+2  A: 

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.

Michael Madsen
What does ~ mean?
Shore
I still doesn't see why delayed expansion is needed.
Shore
Delayed expansion is needed to maintain backward compatibility. Usually, once a %variable% had been used on some line in a batch file, it won't re-read the variable - the line will essentially have been replaced. Delayed expansion prevents that by re-reading the variable every time the line is executed. Since the script runs lines 2-4 for each line matched by for, these will be executed several times, and if delayed expansion wasn't used, you'd just keep copying the same file, which is obviously not what was intended.
Michael Madsen
If you want to read more details about these things, I recommend starting the command prompt and checking "help set" and "help for". It's described quite well there.
Michael Madsen
Michael, partially true explanation, though incorrect in one crucial point: The variable expansion would take place *before* the loop would actually run since the entire "block" enclosed in parentheses is one large statement which will be read completely before being executed by `cmd`.
Joey
Ah, of course. My mistake - I deal too rarely with multi-line statements, so that's probably why I got that bit confused.
Michael Madsen
+1  A: 

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
Mac
+2  A: 
for /f %%i in ('dir /b Client\Javascript\*_min.js') do (

Iterate over every file in the Client\Javascript folder that match "*_min.js". The dir command and for /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:

  1. 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.
  2. 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.

Joey
Another workaround is to use some undocumented functionality of the "call" command: "call set t=%%n:~0,-4%%"
bk1e