NOTE: I do not encourage anyone to use these suggestions for real code that needs to be maintained. I won't take any responsibilities for exploded brains and the like. Although I have to admit, the amount of obfuscation you can do with Powershell with these tips is minimal and in most cases still understandable. Certainly not harder than trying to recognize design patterns in a gazillion lines of Java code.
I am currently playing some sort of code golf with a friend. We're both trying to solve the Project Euler problems (the easier ones, for now, as I am not yet that far) in as little code as possible. He's using Ruby, while I try my best in Windows Powershell.
So probably by not using (or even knowing) Perl or Ruby I am as far as I could get from the whole code golf thing but nevertheless, it's fun and for some solutions he beat me just by mere two or four bytes. But still, he consistently beats me. Now, if only Powershell could lose all those $ for variables or operators as unwieldy and lengthy as -eq or -lt … (although Ruby has similar problems with such horrendously long names like times or inject)
However, I've gained a few insights on how you can still squeeze some bytes out of Powershell scripts by knowing how the shell behaves in certain situations.
The first thing you should do when aiming for as little code as possible is remove all whitespace from the code. This can be done even around operators such as -lt, -eq, -contains, etc. A snippet like $_%7-eq0 is perfectly valid.
Take advantage of predefined aliases, such as % for ForEach-Object and ? for Where-Object. Many commands also have very short aliases, should you need them; the command
will list all aliases with a name shorter than three characters.
Booleans are cumbersome. Period. Using $true and $false just gobbles up too many characters. You can take advantage of the following implicit conversions to boolean:
| True | False |
|---|---|
$TRUE |
$FALSE |
| Any string of length > 0, except the word “false” | Empty string or the string “false” |
| Any number ≠ 0 | Any number = 0 |
| Array of length > 1 | Array of length 0 |
| Array of length 1 whose element is true | Array of length 1 whose element is false |
| A reference to any object | Null |
So using $b=1 instead of $b=$true is much shorter. In many cases, however, you won't need explicit boolean variables. I'll illustrate this with the following example. Suppose you just want to check whether an array contains a multiple of 7 and take that result to do something else. Then you need a boolean value, somehow. A naïve implementation known from Java or so might look like the following:
This reflects how I have done things at first. I've already omitted all whitespace, but still, this is 32 characters long for returning a boolean. We can rewrite the check for the modulus as !($_%7) since everything zero gets casted to false. This saves one precious character:
Next we can get rid of the helper variable $b altogether, since we can just use the result of the pipeline as a boolean value:
Where-Object or ? and use the resulting output directly:
which reduces the length to 16 characters. Half of what we needed at the beginning.
Another thing that tends to come up sometimes is a logical operation of two values, like for example !($a)-or!($b). With De Morgan's laws you can easily rewrite that to !($a-and$b), saving two characters (or even four, if you had -and to begin with).
Maybe you won't need them often, but sometimes they come in handy. Why write out a number like 10000 or even 10000000 when you can shorten it to 1e4 or 1e7, respectively?
Semicolons separate commands in one line. However, when you are just doing imperative programming crammed into one line it might look like this:
The $s at the end simply drops the variable off the pipeline into the shell so it gets printed. However, you can omit the semicolon directly after a script block:
is perfectly valid and a whole character shorter.
for loopsThe for loop in Powershell looks very much like its cousin from C-like languages:
Many times you're probably better of rewriting such a snippet (29 characters) to a pipeline (14 characters):
But sometimes you need a for loop since maybe the terminating condition isn't that simple. I'll take the above example nonetheless. What you can do first here is getting rid of the increment part and pack it into the terminating condition (27 characters):
we need to use the prefix increment since we are comparing $i after the increment (which happens usually at the end of the loop). Unfortunately this messes up the loop since now the start of the loop isn't right. In fact, now we start at $i = 2 because of the additional increment at the start of the loop.
This can be easily fixed, however, by changing the initialization:
What we notice now, however, is that we actually don't need to use the initialization anymore. Variables get initialized to when they are first used (a fact that we already use with the summation variable $s). So the complete initalization can be removed (23 characters):
And then there is a last small thing with for loops and that is that any trailing semicolons in the loop header seem to be optional. So if we don't need the increment part of the header, we can throw away the semicolon as well:
Down to 22 characters now. Using a pipeline is more code-size-efficient in many cases (see above), but still, the good old for loop can be optimized a bit as well.
Powershell has a very powerful pipeline, based on objects instead of strings which is a great help in many cases. We have already seen that the for loop can be shortened tremendously in some cases by simply using the range operator and piping that array.
Some things lend themselves quite well for pipeline processing. Basically every time you need to do something with the members of an array or a range. For example the following while loop terminates only if $n contains nothing but ones:
Accomplished by a rather small but nice pipeline in an otherwise imperative environment. Some things can be quite cumbersome and long when done imperatively and reduce to very small amount of code when done in a pipeline.
ETA: I have to admit, that by now I have discovered an even shorter way of checking this:
This is certainly shorter than
and can be helpful in some circumstances. Careful though, in using the result in a boolean context, since the boolean conversion rules are a bit confusing at times.
Select-ObjectSelect-Object or its predefined alias select is a rather long way of getting the first or last element of a pipeline. Compare
with
for getting the first element of a pipeline. Similarly for getting the last element.
Sometimes they are necessary, for example if you want to treat a string or substring as a number or convert a number to a string. The straightforward approach would be
Not very short. In fact, I try to avoid typecasts whenever possible, at least when trying to write concise code no-one else needs to read. You can, however, take advantage of the fact that Powershell converts operands. So you can just add the neutral element to something you want to convert:
And we just got down the cost of a string typecast to two bytes, instead of eight, and for integers we're also at two bytes instead of five, while a double typecast can be shortened to three bytes instead of eight.
I have presented some techniques for getting Powershell scripts smaller by taking advantage of some mathematical properties, quirks in the Powershell syntax as well as different methods of accomplishing the same goal, sometimes with large differences in code size. Mostly accumulated over the past few days but I'm still learning.
If I find anything new then this list will get updated.