PowerShell equivalent of ack

Today I found a typo in some docs: “account” was misspelled as “accuont.” I want to find that in the repository. As a bash user, I tried

ack accuont

but of course that command is not found in PowerShell.

It took me a while to find the equivalent so here it is:

Select-String -Path .\**\*.md -Pattern accuont

You need to list the fileglob or else it’ll search binary files and mess up your screen.

Comma-separated lists work for both globs and patterns. Regex strings work in patterns.

Source: https://devblogs.microsoft.com/scripting/use-an-easy-powershell-command-to-search-files-for-information/

Rebase on the World: personal shell choice

“Why use bash when you have PowerShell?” <– words I did not expect to hear from my own mouth.

Over the past few weeks I’ve begun learning PowerShell, and it’s an improvement over the UNIX (and family) shells, bash and ksh etc.

PowerShell is newer. It builds on what those shells did right, and then gets it more right.

The UNIX-y shell commands have this magical power of composition: you can pipe the output of one to the input of another, and chain these to build tiny programs right on your command line. One sends text to STDOUT, the next reads it on STDIN.

PowerShell does this piping, except with objects instead of lines of text. The data is structured and interrogable (I can ask it what fields it has). In bash, you output your data as text, and the next program parses that text.

Here’s an example from Chapter 1 of PowerShell in Action. In bash, the sort command can parse the piped text and sort on it. You can specify numeric sorting.

ls -l | sort -k 5 -r -n

In PowerShell, the sort command works on named properties of the piped object. And it knows the type of the property, so nobody has to tell it what’s a number.

ls | sort -Property length -Descending

More: PowerShell standardizes parsing of command-line arguments. This gives me consistency when I use the command, and saves painful work when I write a command.

More: PowerShell gives me multiple output paths, one for data (to the next program) and another to the user who typed the command. In bash, commands like git abuse STDERR to send info back to the user.

When PowerShell was only for Windows, by far the most powerful command-line shell for me was bash (or fish or zsh, pick your favorite). Bash worked in the worlds I lived in, and that matters most. Now that PowerShell runs on Mac and Linux, it is the most powerful command-line shell for me … except for one thing.

The biggest thing bash has on PowerShell is: I already know bash. I can type stuff there and it just works. With PowerShell I have to look things up all the time.

But every time I look something up, I increase my abilities. PowerShell raises the floor I build on, compared to bash.

It is always a tradeoff between sharpening the tools we have, vs trading them in for a better model. In this case, I’ll take some slowness in the beginning for a faster top speed.

By switching, I gain advantages that the software world has created since I started programming twenty years ago. I rebase myself on the latest version of the world.

Development aesthetic: experiments

Software development is a neverending string of “What happens if I…?” Each new runtime, language, or tool is a new world with its own laws of nature. We get to explore each one with experiments.

Objective

Today I added another alias to my PowerShell $profile:

echo "Good morning!"
# ...

Function ListFilesWithMostRecentAtBottom {
    Get-ChildItem | Sort-Object -Property LastWriteTime
}
Set-Alias ll ListFilesWithMostRecentAtBottom

To use that alias in my current shell, I need to source the profile again. I googled how to do this. The first useful page said:

& $profile

So I typed that. It echoed “Good morning!” but the alias did not work.

Hmm, did it not save?

I can test that. I changed the echo to “Good morning yo!” and tried again.

It printed the new text, but still didn’t get the alias.

Hmm, is something wrong with the alias?

I opened a new shell window to test it.

The new alias works in the new window. Therefore, it’s the & $profile command that is not doing what I want.

Investigation

I could ignore the problem and continue work in the new window. My alias is working there. But dang it, I want to understand this. I want to know how to reload my $profile in the future.

Time for more googling. The next post had a new suggestion:

. $profile

I typed that, and it worked. yay!

But wait, was that the old window or the new window? What if it only worked becuase I was in the new window?

I want to be certain that running . $profile brings in any new aliases I just added. For a proper experiment, I need to see the difference.

Experiment

I add a new alias to my $profile, and also change the echo so that I’ll be sure it’s running the new version.

echo "Good morning yo tyler!"
# ...

Function ListFilesWithMostRecentAtBottom {
    Get-ChildItem | Sort-Object -Property LastWriteTime
}
Set-Alias ll ListFilesWithMostRecentAtBottom
Set-Alias tyler ListFilesWithMostRecentAtBottom

In my terminal, I run tyler as a test case, then the command I’m investigating (. $profile), then the test case tyler again.

Now I can see the before and after, and they’re different. I can tell that . $profile has the desired effect. Now I have learned something about PowerShell.

Epilogue

I remove the extra tyler stuff from $profile.

As far as I can tell, & runs the script in a subshell, and . runs the contents of the script in the current shell. The . command works like this in bash too, so it’s easy for me to remember.

Today I took a few extra minutes and several extra steps to make an experiment and figure out what PowerShell was doing. Now I know how to reload my $profile. Now you know how to run a tiny experiment to ascertain that what just happened, happened for the reason you think it did.

This verbosity makes me happy

Today I learned how to create aliases in PowerShell. I’m switching from Mac to Windows, and I want the terminal in VS Code to do what I want.

No terminal will work for me until it interprets gs as git status. I type that compulsively.

In bash, setting that up looks like this:

alias gs='git status'

But in PowerShell, aliases can only refer to single words. No parameters. Wat.

You can make a function with the whole command in it, and then set an alias to that function.

Function GitStatus { git status }
Set-Alias gs GitStatus

The first time I did this it felt kinda silly. But then the second time …

Function CommitDangit { 
    git add .
    git commit -m "temp" 
}
Set-Alias c CommitDangit

This alias c makes a crappy commit as quickly as possible. I use it when live coding, to make insta-savepoints when stuff works. (I’m a bit compulsive about committing, too. Just commit, dangit!)

The PowerShell syntax requires a long name for my command before I give it a short one. This is more expressive than the bash:

alias c='git add . && git commit -m "temp"'

My CommitDangit function is named for readability, plus a tiny alias for fast typing.

This is a win. I like it more than the bash syntax. PowerShell is a more modern scripting language, and it shows.

Bonus: in bash I put those aliases in a file like .bashrc or .bash_profile or sometimes another one, it depends. In PowerShell, I put the aliases in a file referenced by $profile. Edit it with: code $profile, no figuring out which file it is.

Next: reload the $profile in an existing window with . $profile