Slumming with the Windows Command Intepreter

February 29th, 2008 by Adrian

Yesterday marked my brief foray into the funky little world of the Windows command line. Not a place I’d usually be programming, but I had a really specific problem and it seemed like I should be able to whip up a sol’n there without a ton of wasted work.

Here’s the deal: usually when I download digital albums, all the tracks are named in the form <Bandname> – <Songname>.wma. I just don’t like the band name being in the file name, since I’m already organizing tracks by band & album folders. I end up spending a couple of minutes in Explorer renaming tracks manually: start at the top of the listing, hit F2, CTRL-Left Arrow a few times until everything but the band name & hyphen prefix is selected, hit enter, down arrow, rinse & repeat.

Not the end of the world. But like any geek, I’d rather spend five hours once solving a programming problem (and maybe learning something in the process) than spend fifteen minutes every week executing a mindlessly repetitive manual task. And I just picked up a couple of anthologies with over 80 tracks a piece — that’s critical mass, baby.

Initially I thought I’d whip up a little EXE in C or VB or something (Flash not being suited to this kind of local file I/O) that snarfed a directory listing, automatically figured out the prefix (if any) that all file names had in common, and renamed all the files to eliminate that prefix. But then I wondered how far I could get with just a simple batch file. The answer: close enough. No automatic detection, but a decent little utility to walk through a folder hierarchy recursively and strip a specified string out of all music filenames in there. Potentially boring technical details follow, so only click the “more>>” link if that’s your bag…

A little creative googling turned up this site, which was way more helpful than the official Microsoft docs on Technet. I mainly worked from SS64, diving into Technet only when there was a distinction in the SS64 notes that seemed like it might point to something more general. So, first step: learn the looping conventions for the cmd interpreter. Easy peasy:

FOR /R [[drive:]path] %%parameter IN (set) DO command

I assume the /R is for “recursion”, since this will walk from the given path on down and execute <command> against each matching file. And the command I want to iterate is pretty straightforward:

REN <oldfilename> <newfilename>

…where <newfilename> is calculated by stripping out <rmvString> from <oldfilename>. This is the part that I wasn’t really sure I’d be able to do @ the command line level. Were there even any explicit string manipulation routines down here? Why yes, Virginia, there are:

%variable:StrToFind=NewStr%

will return the string in variable with all occurrences of <StrToFind> replaced with <NewStr>. And if you leave out <NewStr>, <StrToFind> is just deleted. I have to admit, I was pleasantly surprised to see this implemented in the command interpreter. Maybe my expectations of OS command lines are just really low. Or my expectations of Microsoft. Or both. Anyway, I figured I was most of the way home now, with a skeleton batch file. Just a couple of problems.

One is that REN is a little funky in its parms. In fact, that’s why I needed a batch file in the first place. It’d be nice if wildcard binding worked so I could just type:

REN “bandname – “*.wma *.wma

but, as this article points out, REN is a little funky in how it handles wildcards in the new filename. Yes, the legacy of 8.3 filenames can still reach out from the grave and lay an icy hand on your shoulder, even in WinXP. But guess what else won’t work?

REN <oldname> <newname>

where both <oldname> and <newname> are fully qualified path names. <oldname> needs to be fully qualified (at least in this context — the %%G parm in the FOR /R will evaluate as fully qualified), but <newname> can’t contain any path component. That makes things a little tougher — now I can’t just take <oldname> and strip out my specified string, I also need to pull out all the path stuff.

There’s no way I’m going to traverse the string in the batch file looking for the last backslash and stitch things together from there. It’s not worth the hassle at this point; I’d just call it quits and do everything in C. But wait! There is something that will help:
Windows supports a special syntax for parameter manipulation when the parameter happens to be a filename.

Normally, the first parm following the command is referenced as %1, %2 is the second, etc. up to %9. But if you slip a tilde in after the percent sign, there’s a little alphabet of parameter extensions: %~x1 will give you just the filename extension, %~p1 will give you just the path, and… yes, %~n1 will give you just the name of the file: no extension, no path. And you can combine these options, so %~nx1 will get you just the file name + extension, just what we want. (There are other parameter extensions, too, btw, check ’em out here.)

OK, getting close now. I just need to structure the batch file so the FOR loop calls into a routine that breaks down the new name with %~nx1 before I strip out the desired substring:

FOR /R %%GG IN (*wma) DO (CALL :RmvChars %GG)

:RmvChars
SET _newname=%~nx1

etc.

Setting this up though, I run into my next issue: almost all the song filenames contain spaces, and when I call my routine with this parm, the space is parsed as a delimeter. So %1 doesn’t contain the whole fully qualified file name, just the part up ’til the first space. %* is a nice batch operator that will stitch all the parms on the command line together, but none of the paramter extensions work in this batch mode (you can’t have %~nx*). Not too bad, I’ll just wrap the long filename in quotes — I’ll probably need ’em anyway to get REN to work right. Oh, and don’t forget to escape the quotes:

FOR /R %%GG IN (*wma) DO (CALL :RmvChars ^”%GG^”)

Getting closer. (Are you still reading? Wow.) Just one more gotcha worth mentioning: the string manipulation needs to be massaged a bit to work. Since I’ve stashed the string I need to remove in a variable, I need to evaluate the variable inside the string replacement command (I’m not literally trying to replace the text “varName,” I’m trying to replace the string that var contains). To do this, I have to take my SET statement

SET _newname=%_newname:%_rmvString%=%

and structure it as a CALL (which happens to work for shell commands as well as routines and other batch files), encapsulating the whole expression in another set of %’s to get it evaluated on the CALL:

CALL SET _newname=%%_newname:%_rmvString%=%%

OK, that’s pretty much it. There are a couple of interesting notes, like how

(SET _newname=%~xn1)

will fail if the filename has a right-paren in the name, because after evaluation it parses out as:

(SET _newname=longfilenameblahblah) restoffilename.xxx)

…prematurely closing the open-paren that started the line. But the simpler

SET _newname=%~xn1

works just fine. But all that’s all pretty straightforward, and I think if you’re still reading – or skimming – this far, you deserve a break. So here’s the final .BAT in case you want to take a look. Total time start to finish, including googling, reading, trial, error (LOTS of error!), and bug fixing: a little over 3 hrs.

The Windows command line is a kind of interesting environment. It was a nice place to visit, but I wouldn’t want to live there.

Posted in Technobabble


(comments are closed).

About Sips from the Can

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam justo tortor, dignissim non, ullamcorper at, lobortis vitae, risus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam erat volutpat. Aenean mi pede, dignissim in, gravida varius, fringilla ullamcorper, augue.

(edit footer.php to change this text)