Share via


So what the heck just happened there...?

In the previous post, there was a fair amount of code and basically no explanation... Here's how it breaks down:

Naïve method:

    set FILENAME=basename-%RANDOM%

CMD has a built-in variable called %RANDOM%. Simply put, it returns a random value between 0 and 32767. You can test this yourself by running echo %RANDOM% a few times at a prompt.

This method, then, just creates an environment variable FILENAME as basename-2369 or basename-7, or whatever the random number was. The problem is that if you run this enough times, eventually you'll get duplicate numbers. You also can't determine when that will be, exactly. You could get duplicates after two runs or it might take 32769 runs. So what we need is a...

__
Better way:

    for /f "delims=:. tokens=1-4" %%m in ("%TIME: =0%") do (
set FILENAME=basename-%%m%%n%%o%%p
)

Ok, so this gets a little more complex. First, let's look at the output of echo %TIME% (another built-in variable). Right now, I get 16:21:52.73 as the current time. So the fields from left to right are hours, minutes, seconds, hundredths of a second. Easy enough.

Now, the for loop there is where the magic comes in. Generally the /f switch on for means that you want to operate on a file: say, reading each line of a file and doing something interesting with it. However, the help for for says this:

You can also use the FOR /F parsing logic on an immediate string, by making the filenameset between the parenthesis a quoted string, using single quote characters. It will be treated as a single line of input from a file and parsed. (emphasis mine)

So this is what we're using here. The value of %TIME% is the string we're working on. Note that the script also replaces spaces with zeros. You'll get leading spaces if you run this from 1:00am -9:59am, so to preserve the sorting, we want leading zeros.

The next bit of the for command is this: "delims=:. tokens=1-4"

This delims parameter indicates that we want to use : and . as our field delimiters (that is, split up the string everywhere there's a : or a .). The tokens parameter indicates which fields we're interested in. In this case, we're interested in all four: HH, MM, SS, and hh (for lack of a better abbreviation for hundredths). Then the %%m means we want to assign the first field to the variable %%m.

The nifty (if occasionally annoying) thing about having multiple fields is that CMD automagically puts the rest of the fields we're interested in into subsequent variables. So HH goes into %%m, MM goes into %%n, etc.

Now, once we parse all that, we set FILENAME to basename-HHMMSShh and we're done.

The problem with this is that if you run this loop a bunch of times around midnight, you'll get filenames like:

basename-23500000
basename-23590000
basename-00010000

...and those don't sort properly: The file created after midnight will appear to come before the ones in the 11:00 hour. Again, you could solve this by using dir /od, but that's cheating.

You might be asking at this point, "Why does this use for when it's not really a loop?" Excellent question. That's because *mumble mumble*. The real answer is "beats me". I suppose they (whoever "they" are) needed to stuff this functionality somewhere, and since for already had so many pathological cases, it was as good a place as any.

Ok, so on to the...

__
Best way:

    for /f "delims=/ tokens=1-3" %%a in ("%DATE:~4%") do (
for /f "delims=:. tokens=1-4" %%m in ("%TIME: -0%") do (
set FILENAME=basename-%%c-%%b-%%a-%%m%%n%%o%%p
)
)

This works much the same way as the "better" way, but it adds a couple of wrinkles. First, it parses the %DATE% variable (another built-in). For me, echo %DATE% returns Fri 06/03/2005 at the moment. But it doesn't just parse %DATE%, there's some funny punctuation in there.

The syntax for %DATE:~4% is hidden under set /?, oddly enough. What it means is "take the value of %DATE% and strip off the first four characters - the day of the week and the space. So, in my case, that means that %DATE:~4% results in 06/03/2005. The outer for loop uses / as its field delimiters, so we end up with the fields DD MM YYYY in %%a, %%b, and %%c.

The next for loop is exactly the same as the one in the "better" method. It stuffs HH MM SS hh into %%m, %%n, %%o, %%p.

Finally, we assign FILENAME the value of basename-%%c-%%b-%%a-%%m%%n%%o%%p, or for my example, basename-2005-06-03-16215273.

If you run this a bunch of times, you'll get filenames that sort correctly all of the time.
__

Edit: Added leading zeros to the times, where necessary, to preserve sorting.

Comments

  • Anonymous
    June 08, 2005
    Very cool! Thanks for sharing this.