Skip to content

differences_from_bash

Carl C edited this page Jun 22, 2021 · 4 revisions

Differences From Bash/Zsh

Here I will list the differences I've noticed between Powershell and the standard Unix shell Bash.

They both have their pros and cons.

I already wrote about this extensively in my mdbook git repo, but here is where the information should be.

Commands

Powershell has a Verb-Noun nomenclature for their commands (called cmdlets in the powershell world). But to help people who are transitioning from a unix shell environment to Powershell, they have added convenient aliases to these commands.

For instance, to get your current location on the file-system, in Bash you would just type pwd.
In Powershell, the official command is Get-Location, but they have implemented an alias by the name of pwd which does the same thing. The same goes with cd. In Powershell it is Set-Location, but it is also aliased to cd and chdir as well.

A lot of people coming from Bash or Zsh will find this Verb-Noun stuff irritating, and I agree with them, but I understand why they named certain cmdlets like this.

Why these long commands

The reason is because in Powershell, everything is an object. And also, you can traverse more than just the file system. You have these things called PSProviders which give access to certain system resources and environment variables.

Set-Location is more fitting than pwd mainly because print working directory doesn't fit when you've set your location to the Windows Registry for example. On the other hand, the Registry does kind of resemble a file-system hierarchy.

PSProviders

You can get a list of PSProviders by typing Get-PSProvider. The standard list of providers is as follows:

  • Filesystem
  • Alias:\
  • Env:\ - Environment variables
  • Function:\
  • Variable:\
  • HKLM:\
  • etc

Example: You can type cd Function:\ and then type ls or Get-ChildItem and get a list of functions currently in the Powershell Session. At this point you can use cmdlets like Get-Item or Set-Item to get information about a function or to set attributes of a function. The actual commands inside a function is called the Definition.

Powershell equivalents of common Unix commands

Example 1 (which)

which is a unix/linux command that gives you the path of any command in the system's $PATH. It is also a builtin for bash and zsh. It has a bit more options when used from zsh, but the idea is that you can type which vim and get either the location of the vim executable, or nothing if it isn't installed.

In Powershell you can do the same thing, but you have to filter the result a bit first. I've created a function to do just that:

function pswhich {
    Get-Command "$args" | Select-Object -expandproperty Source
}
# Or this works too
function pswhichalt {
    gcm "$args" | foreach Source
}

Select-object is used because Get-Command always returns an object containing the data and some properties and methods associated with the data. Select-Object has an alias for it which is just the word select. Also, alias for Get-Command is gcm. The foreach in the second function acts like a shorter Select-object -expandproperty. You can also get the "$PATH" variable by typing in $env:PATH. The $env: prefix just means that it is an environment variable and not a Powershell variable. Environment variables are available to all running processes in the system, while regular $variables are only available to the current powershell session.

This is why in order to set the $PAGER variable to less (if you have it installed), you have to use $env:PAGER = "less".

Example 2 (echo)

echo in Powershell is an alias for Write-Output. But Powershell is interesting in the way it handles quoted strings. If you just type in "hello world" in the pwsh command line (surrounded by quotes) and press enter, it will simply echo that string back to you. This is obviously different from Bash which would emit an error if the same thing was done in a Bash prompt. "strings" can also be modified in different ways, similar to sed.
You can do a simple replace/subsitute a word into another word like so:

"This is a test.  Hello".Replace('Hello','Hi') # this echoes "This is a test. Hi"

In sed that same command would be this: echo "This is a test. Hello" | sed 's/Hello/Hi/'

Variables in Powershell

Variables work a little differently in Powershell.

The first difference you will notice is that Powershell variables are based on scope, meaning you can define where the variables will be accessible from. Creating a variable in Powershell is a lot like in bash, but there are a few differences. In Bash, whitespace matters, meaning you cannot have a space in between the = equals sign and the value.
In Powershell, you can.

# Bash create variable
BASHVAR="This is a bash variable"
# Powershell create variable
$PwshVar = "This is a powershell variable"

Interacting with Variables

Since everything in pwsh is an object, they all have methods as well. You can try this in your console:

# Type this in your console and press Ctrl-Space or Tab
$PwshVar. # Ctrl-Space will show you all available completions, and Tab will cycle thru them.
# A menu pops up showing you all the methods pertaining to the object inside your variable.
# The proper syntax is this though:
($PwshVar). # With the parentheses, you can do the same thing but with cmdlets as well.
(Get-Command notepad).Source # This will output a string containing the location of the notepad.exe executable.

I could go into much more detail but it would take a long time to list all the functionality. Maybe I'll continue in a future wiki entry.

Handling errors

I wrote up a little something at this location here about handling errors in Powershell. Usually, in a Unix-like shell, you can pipe output to /dev/null so that it essentially hides the error message so that you can handle the error yourself with your conditional expression. In Powershell there is a common-parameter called -ErrorAction which does essentially the same thing. Also check out Get-Help about_commonparameters for more info.

Control Operators

Control Operators like && work a bit differently in Powershell in comparison to how Bash operates. In fact, the || (aka OR) operator currently does not work in pwsh as of this writing. When trying to use it in a statement, everything after the || token will be ignored as far as I know.

Also, certain True/False operations will print "True" or "False" to stdout in places where you wouldn't expect.

Take this line for example:

Test-Path nothingnot && write-output "It exists" || Write-Output "It does not exist"

Test-Path nothingnot is like the unix test command but only works with file system paths. It is the equivalent to [ -f ./nothingnot ].

Teriary Operations in Powershell

Take this snippet for example:

($codepage.EndsWith("65001")) ? (Write-Output "codepage is correctly set") : $(
Write-Output "Setting codepage"
chcp 65001 | Out-Null
$codepage = $(chcp)
Write-Output "Codepage is now set to 65001"
)

This is what has to be done instead of using the traditional Bash && and || operators for test operations. Unlike bourne-shells, you have access to the statement ? true statement : false statement teriary operations. This should be familiar for people familiar with C or C++.

These two snippets are equivalent:

[[ -f ./file.txt ]] && {
echo "file.txt exists"
} || {
echo "file.txt does not exist"
}
# The test statement must be within parenthesis in order to keep separate.
# The dollar-signs are optional in this case.
(test-path ./file.txt) ? $(
Write-Output "./file.txt exists"
) : $(
Write-Output "./file.txt does not exist"
)
=> ./file.txt does not exist

If you were to replace the parentheses with curly-braces, you will output not only the string arg to Write-Output, but the Write-Output text as well.

(test-path ./file.txt) ? {
Write-Output "./file.txt exists"
} : {
Write-Output "./file.txt does not exist"
}
=> Write-Output "./file.txt does not exist"
Clone this wiki locally