Home Page

 


EARLIER FEATURES

 


FEATURES CONTENTS

 


LATER FEATURES

 

Features Contents


23rd April 2005

SCRIPTING
REMOVE BRACKETS REVISITED

Saint Jude
(Julian Kennedy)

with comments by
Brian Grainger

email.gif (183 bytes)
brian@grainger1.freeserve.co.uk


 

When I wrote an article about Scripting in Windows I chose as an example a keyboard macro. This macro removed the brackets and number from the filename of a file that is dragged from the 'Temporary Internet Files' folder to a standard folder.
E.g. index.htm gets changed to index[1].htm when the file is dragged from the 'Temporary Internet Files' folder to a standard folder. Running the keyboard macro with the index[1].htm file highlighted in Widows Explorer will result in the file name turning back to index.htm.

I have used this macro ever since, but there were some drawbacks. Most irritating was that if you had more than one file to convert you had to repeatedly execute the keyboard macro. Even though it was assigned a shortcut key it could become irritating. Recently, I amended things so when the macro was called it asked how many were to be deleted and then automatically repeat the macro that many times. This was much better but had one slight problem if you input the wrong number, especially if too big a number! The other drawback to this macro was in those rare cases were the number in brackets was 10 or more. The macro failed then.

Well, recently Jude wrote to me as follows:

I've been reading around the ICPUG web site with interest. The section on Windows Scripting caught my eye, in particular. the script for removing the contents of trailing parentheses from filenames.

I realise that the script's emphasis is really a demo for using sendkeys in scripts, but I thought I'd write a little script that does the job without using the method, so..

  • Doesn't rely on window titles (can be unreliable)
  • Can handle numbering >10
  • Will apply itself to all selected files at once

I have done it in JScript, which is by far my own preference.

I used to do a little registry fiddle to run these scripts from context menus, but I have a little tip, which is to, instead, put the script file into the SendTo folder (or a subfolder of SendTo). The benefits are:

  • Minimal "footprint". Just place the script there, and it will be available for all files (or selections of files). No need to mess with the registry.
  • Can hide numerous scripts in a SendTo subfolder, so creating a submenu of the context menu.
  • No trouble with long file names (had a lot of trouble with that when using context menus)

Here it is:

//---------------------------------------------------------------------------
// lib constants & functions
//---------------------------------------------------------------------------

var FSO = WScript.createObject("Scripting.FileSystemObject");

// Makes iteration sweeter
Enumerator.prototype.getAndMove = function(){
var i=this.item();this.moveNext();return i;
}

// Irritating that native function doesn't return file if found.
function fileExists(path){
var file;
return FSO.FileExists(path)? FSO.getFile(path):null;
}
//---------------------------------------------------------------------------
// main script

//---------------------------------------------------------------------------
parensReg = /^(.*)\[[0-9]*\](\.[^\.]*)$/;
parensRep = '$1$2';

var paths = new Enumerator(WScript.arguments);
var path, file;

while( path = paths.getAndMove() )
{
if( parensReg.test(path) && ( file = fileExists(path) ) )
file.move( path.replace(parensReg, parensRep) );
}
//------------------------

I loved this script. I didn't understand it, but I loved it! It solved all the little faults of my keyboard macro and to simply highlight the files in Windows Explorer and SendTo the script was REALLY cool. I didn't know this could be done. I had not done any work with JScript as all my work was done with VBScript. Windows Scripting Host allows JScript as well, which, because it is akin to the more universal Javascript, is probably better.

Unfortunately, when I tried the script out it did not work! It was probably something in the transference from the e-mail into a script file. I got into e-mailing with Jude and he created a slightly different script and explained some of the interesting bits of the routine. This revised script removed some of the cute tricks of the code above, so was slightly easier to understand.

Here is Jude's revised script and explanation:

//---------------------------------------------------------------------------
// constants
//---------------------------------------------------------------------------
var FSO = WScript.createObject("Scripting.FileSystemObject");//#1
var args = WScript.arguments;
//---------------------------------------------------------------------------
// Reject execution with no arguments
//---------------------------------------------------------------------------
if(args.length==0) { WScript.Echo('The script requires arguments'); WScript.Quit()}
//---------------------------------------------------------------------------
// main script
//---------------------------------------------------------------------------
var parensReg = /^(.*)\[[0-9]+\](\.[^\.]*)$/;
var parensRep = '$1$2';
var path, file, k;
for(k=0; k<args.length;k++) //#2
{ path = args(k); //#3
if( parensReg.test(path) && FSO.fileExists(path) ) //#4
{
newPath = path.replace(parensReg, parensRep);
file = FSO.getFile(path);
file.move(newPath);
}
}
//---------------------------------------------------------------------------

Notes:

#1 WScript.createObject

This is a host method. Alternatively, the native JScript (only) constructor, ActiveXObject can be used.

Incidentally, Javascript is case-sensitive, but this doesn't appear to apply to any methods of host objects.

#2

There is no need to declare global variables in Javascript. This is mainly for humans. The script would be quite happy without.

There is a difference though. When a var is declared without being initialised, it is assigned the value undefined (the undefined object). Not particularly surprising.

The operational difference is this:

var myVar;

if(!myVar) alert('hello');

If this code is run, you will get a greeting. If the declaration was missed out, you'd just get an error.

However,

if(this.myVar) alert('hello');

This will give a greeting, and no error, whether myVar has been declared or not. Intrigued?

#3

I always forget that the WScript.arguments object is not a Javascript array, and that it doesn't even allow itself to be treated like one like DOM collections do in browsers.

The hardest part of a transition from browser scripting to WSH is getting used to the fact that all the COM objects that you meet just don't play nice.

The strict way to access arguments, again like DOM collections, to use the .item() method [and others]. Square brackets just don't cut it,

args[n]

but they don't raise errors either.

The arguments object does however have item() as its "default member", so just using args(n) directly will do.

#4

Only act if
- filename contains trailing parens, and
- path is not folder

REGULAR EXPRESSION:

/expression/[flags]

 

-->

A Javascript RegExp literal.

^

-->

Beginning of string/line (^ can also mean 'not')

$

-->

End of string/line

.

-->

Any char

.*

-->

Any sequence of chars, or nothing at all

[0-9]+

-->

Any sequence of 1 or more digits

(..)

-->

Submatch

Escaped special chars

\.

-->

A literal dot

\[

-->

A literal left square bracket

[^\.]*

-->

Any series of chars NOT including a dot (ie. keep going until you get to the dot)

Consider the expression with submatches taken out:

^.*\[[0-9]+\]\.[^\.]*$

..in human language:

[beginning]
[whatever]
[left square bracket]
[containing 1 or more digits]
[right square bracket]
[dot]
[any chars, not dot] => dot must therefore be last dot!
[end]

Does not match:

C:\doo\dah\my[35]file.jpg

brackets not next to dot

C:\doo\dah\myfile[3x].jpg

bracket contains non-digits

C:\doo\dah[34].dee\myfile.jpg

brackets not next to last dot

In particular, [whatever] can include bracket chars. It doesn't stop until it reaches the specific sequence:

[square brackets with digits][dot][any chars, not dot][end]

(So, the expression is easier to understand when read backwards.)

Does match:

C:\doo\dah[56].zee\myfile[45].jpg

..but (touch wood) only last parentheses will be removed.

Then we put submatches in, (remember submatches in round brackets):

[beginning]

 

([any chars, until..] )

1st submatch ($1)

[left square bracket] [containing 1 or more digits] [right square bracket]

 

( [dot] [any chars, not dot] => dot must therefore be last dot ! )

2nd submatch ($2)

[end]

 

REGULAR EXPRESSION METHODS

[regexp].test(string)

returns

true|false for match

/aa/.test(baaah)

returns

true

/^aa/.test(baaah)

returns

false ; doesn't begin 'aa'

[string].replace(regexp|string, repExpression|functionRef)

Very powerful.

First arg:

Can be a string, but will then only replace first instance found.
RegExps, with g flag, will replace all matches.
We need no g flag, since we're matching entire string /^...$/

Second arg:

(a) Replacement expression:
Often just a simple string, but may contain references to submatches using $

$&

-->

whole expression

$n

-->

nth submatch

(b) Function:

A function that takes one argument, corresponding to each whole expression match. The match(es) are replaced by the return value of the function.

So finally,

'$1$2', as a replacement expression, effectively rejoins the submatches, leaving out the last square brackets and their contents.

This means that the initial use of test() isn't completely necessary because any path without the target brackets will just be replaced by itself, unchanged - a bit of a waste of time.

If you want to get into scripts it is well worth trying to understand the above explanation. It is not easy, but you will be well rewarded. The Regular Expression technique is clever and powerful. It is used within some text editors within Linux as well.

If you want to understand the original 'cute' code I suggest you spend some time with the Windows Scripting Host help file and look out some of the methods used in the JScript section.

I have placed both versions of the script on the website. Copying from this web page probably will not work.

The original cute code can be found as removeparens.js

The revised code can be found as removebrackets.js

Simply download the one you want to use and copy it to your SendTo folder. This can be found as follows:
Win9x users: c:\Windows\SendTo\
WinXP users: c:\Documents and Settings\<user name>\SendTo

All that remains is to thank Jude for his input. It is great to have material from people who know more than I do. I get to learn something then!

Update

I have found, in use, that this script will only allow 5 files to be highlighted at one time. Any more and it says too many arguments. This is still better than the one at a time of the original keyboard macro, but still leaves room for improvement.


 

 

 

 


TOP