46
\$\begingroup\$

What general tips do you have for golfing in Windows PowerShell? I'm looking for ideas that can be applied to code golf problems in general that are at least somewhat specific to PowerShell (e.g. "remove comments" is not an answer). Please post one tip per answer.

—taken nearly verbatim from marcog's question.

\$\endgroup\$
  • 3
    \$\begingroup\$ When I googled "PowerShell golf" this was the first hit! \$\endgroup\$ – Matt Mar 14 '16 at 15:53

43 Answers 43

1
2
3
\$\begingroup\$

Use variables to store .NET names

As outlined by cogumel0 in this answer, you can use variables to store .NET type names. For example, changing

param($a)$a-[math]::pow(2,[math]::floor([math]::log($a,2)))

into

param($a)$a-($m=[math])::pow(2,$m::floor($m::log($a,2)))

saved 3 bytes in this example.

| improve this answer | |
\$\endgroup\$
3
\$\begingroup\$

A simple one but control-flow statements (as opposed to pipelines), such as if, for, and while, do not need a semi-colon after their braces.

E.g.

# Control-flow statements (All are valid)
if(...){...}"foo"
for(...){...}"foo"
while(...){...}"foo"
switch(...){...}"foo"

# Pipelines
1..2|%{...}"bar" #This will throw an error.
1..2|%{...};"bar" #This will work.

A free byte that's easy to miss.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Good point. I've been using this for ages. I've also taken the liberty of using the correct terms in your answer; I hope you don't mind. \$\endgroup\$ – Joey Oct 26 '18 at 7:29
  • \$\begingroup\$ @Joey S'all good. \$\endgroup\$ – Veskah Oct 26 '18 at 19:59
2
\$\begingroup\$

If you need an if/else at the end of the program (maybe to handle a special case differently), then instead of

if(foo){a}else{b}

do

if(foo){a;exit}b

which saves a character.

| improve this answer | |
\$\endgroup\$
2
\$\begingroup\$

Declaring an anonymous function should come up alot. There are multiple variations of this that are covered here https://stackoverflow.com/questions/10995667/lambda-expression-in-powershell involving scripts blocks. I use a similar one to this

$a={iex([int[]][char[]]$args[0]-join"+")}
&$a 'abcd'

This would save a few character from declaring a function with a name and goes better if used more than once. The one above converts a string into a char array then into an int array. Then creates a string joined with + so that Invoke-Expression will add all the values.

| improve this answer | |
\$\endgroup\$
2
\$\begingroup\$

SLS instead -match

You can use an sls cmdlet (alias for Select-String) with |% M* as shortcut for property Matches instead a -match operator with a $Matches automatic variable. Compare:

$args|sls 'pattern'|% M*

vs.

if("$args"-match'pattern'){$Matches}


You can also use sls with -a parameter (-AllMatches) to find all matches:

$args|sls 'pattern'-a|% M*


Also, you can find all matched lines in a multiline string without explicitly line breaking. It need to use a regexp option ?(m). Compare:

$args|sls '(?m)^pattern$'-a|% M*

vs.

$args-split"``n"|%{$_-match'^pattern$';$Matches}

| improve this answer | |
\$\endgroup\$
2
\$\begingroup\$

When declaring a hard-coded list of strings, depending on the size of the strings and the number of them, it's often shorter to split a single string up by spaces, since space-delimited splitting doesn't require a delimiter provided as a parameter

For example,

Instead of

$numbers='one','two','three','four','five','six','seven','eight','nine','ten'

Do

$numbers=-split'one two three four five six seven eight nine ten'

You can further delimit with another character and split twice to hold shortened hard-coded multidimensional arrays

| improve this answer | |
\$\endgroup\$
  • 1
    \$\begingroup\$ $numbers=echo one two three four five six seven eight nine ten ;) \$\endgroup\$ – briantist Dec 27 '18 at 20:58
1
\$\begingroup\$

Get Length of elements of an array

You can write the name immediately after the point to get the value of the property or method.

$a=gci
$a.fullName

compare to:

$a|%{$_.fullName}

Both expressions returns array like this:

C:\Archive
C:\PerfLogs
C:\Pictures
C:\Program Files
C:\Program Files (x86)
C:\Users
C:\Windows

There is a property Length which is defined for the array itself. So the Powershell expression $a.Length returns the number of elements in an array. You can use the Shortening Names to get length of elements:

$s=@('22','55555','7777777')
$s|% Length       # 11 bytes

or shortcut:

$s|% Le*          # 8 bytes.

Compare to:

$s|%{$_.Length}   # 15 bytes

Result is the array 2,5,7, not the number 3.

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

Use $x-shr$k if you want to get the result of \$ \big\lfloor{x\over2^k}\big\rfloor \$.

The biggest application of this is getting the truncated int result of halving a positive number without having to deal with Banker's rounding.

Works on ints and floats, however it will cast floats to ints before shifting, resulting in the same banker problem as before. Whether or not this is acceptable will obviously depend. Below is a sample script that shows the results of -5..10. 3/2 and 7/2 demonstrate the difference in return value.

Try it online!

Inspired by this answer

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ note that this might not be as useful for negative numbers \$\endgroup\$ – ASCII-only Mar 11 '19 at 3:25
1
\$\begingroup\$

Use Uppercase over lowercase when dealing with ASCII values

When given the choice, it's usually better to deal entirely with Uppercase. This is because their range is 65..90 vs lowercase's 97..122. For example, the one-liner to generate ABCD...Z in PS v5

-join([char[]](65..90))
-join([char[]](97..122))

This still applies when normalizing mix-case because using the |% short-property trick, ToUpper and ToLower shorten to *per and *wer, respectively.

"Lower to upper: "+-join([char[]](97..122))|% *per
"Upper to lower: "+-join([char[]](65..90))|% *wer

Try it online!

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

Use the new() method instead of New-Object

For example, instead of doing the following (37 bytes):

New-Object Drawing.Bitmap(1024,1024)

Try (32 bytes):

[Drawing.Bitmap]::new(1024,1024)

Kind of an edge case, but you can do this pretty much anytime you initialize a new .NET object. Sadly, there's no equivalent for COM objects so you still have to use New-Object for those :P

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

Short way to convert bits to integer:

if you trust that $args contains int only (see Joey's comments below):

$args|%{$r+=$_+$r};$r

if you assume that $args is an array of int or string representation:

$args|%{$r+=+$r+$_};$r

$args is an array of char 48, 49:

$args|%{$r+=+$r+"$_"};$r

A sliding byte containing 8 bits from the array of int:

$args|%{($r=2*$r%256+$_)}

Compare to:

[Convert]::ToInt64($args,2)
| improve this answer | |
\$\endgroup\$
  • 1
    \$\begingroup\$ Any reason why you're using $r+=+$r+$_ instead of just $r+=$_+$r? Coercion should work the same for the $null case. \$\endgroup\$ – Joey Jan 13 at 11:37
  • \$\begingroup\$ Good point. The +$r guarantees that $r will be converted $r to an integer. because the array of int was specified as an incoming condition, you are right: it should be $args|%{$r+=$r+$_};$r. Thanks \$\endgroup\$ – mazzy Jan 13 at 13:00
  • \$\begingroup\$ That's why I proposed $_+$r, as $_ is already numeric and arithmetic operators convert the right operand to the type of the left. If $r were a string $r+$_ would not do the same as +$r+$_. \$\endgroup\$ – Joey Jan 13 at 14:10
  • \$\begingroup\$ $args is input parameters. I'm afraid to trust the input parameters so much :) Text fixed. Joey, freely edit the text as you wish. Thanks. \$\endgroup\$ – mazzy Jan 13 at 14:50
0
\$\begingroup\$

If you're calling a command with a string argument, you don't need quotes unless there are spaces:

gci MyPath
gci 'My Path'

But that space cost you 2 extra bytes. You can save a single byte by escaping the space without quotes:

gci My` Path

This is only worth it when there's a single space, because there's no advantage with 2 or more.

| improve this answer | |
\$\endgroup\$
0
\$\begingroup\$

If a path contains more than 1 space, you can do :

gci C:\My*Example*Path

instead of

gci 'C:\My Example Path'

or

gci C:\My` Example` Path
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Useful to know, although it risks picking up a different but similar path. And I'm not sure how many code golf challenges involve the file system, as I'd be very wary of testing any solution that might be clobbering my system. If you wrote this answer as a continuation of this one, the gci was only for illustrative purposes of a command that may receive an argument with spaces. Not an actual file system golfing trick. \$\endgroup\$ – Joey Oct 10 '18 at 5:12
1
2

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.