Bash vs PowerShell
The headline difference: bash pipes bytes of text, PowerShell pipes typed .NET objects.
Summary
Bash is the default Linux shell — POSIX-derived, text-stream–oriented, everything that flows through a pipe is bytes that the next command parses.
PowerShell is Microsoft's object shell — pipes carry typed .NET objects with properties and methods, and most commands are verb-noun cmdlets (`Get-ChildItem`, `Set-Content`) rather than tiny external binaries.
In practice that means bash favors tight one-liners glued together with `grep`/`awk`/`sed`, while PowerShell favors `Where-Object` / `Select-Object` / `ForEach-Object` operating on objects you can introspect with `Get-Member`.
Syntax & semantic differences
Pipe contents
BashBytes of text. Next command parses lines itself.
PowerShellTyped .NET objects. Next cmdlet reads properties directly.
`Get-ChildItem | Where-Object Length -gt 1MB` works because each item is a `FileInfo` object — no `awk '$5 > 1000000'` parsing needed.
Variable syntax
BashNAME=value (no spaces around `=`). Read as `$NAME` or `${NAME}`.
PowerShell$Name = value (spaces allowed). Read as `$Name`. Holds any object, not just strings.
Bash variables are strings by default; PowerShell variables are typed unless declared `[string]$x`.
Command substitution
Bash$(command) or backticks (legacy).
PowerShell$(command) for strings; @(command) to force an array; (command) returns the raw object.
Conditionals
Bash`if [[ -f file ]]; then ...; fi` — `[[ ]]` is the bash test built-in.
PowerShell`if (Test-Path file) { ... }` — comparison operators are `-eq`, `-lt`, `-match`, not `==`/`<`/`=~`.
PowerShell `==` is **not** a comparison operator. Using `if ($a == $b)` is a silent bug — use `-eq`.
Loops
Bash`for f in *.log; do echo "$f"; done` — word-split + glob expansion is shell-side.
PowerShell`Get-ChildItem *.log | ForEach-Object { Write-Output $_.Name }` — pipeline-oriented.
Functions
Bash`my_fn() { echo "$1"; }` — positional args `$1 $2 ... $@`.
PowerShell`function My-Fn { param($Name) Write-Output $Name }` — named, typed params with `param()`.
Exit status
Bash`$?` is the previous command's exit code (0 = success).
PowerShell`$LASTEXITCODE` for external programs; `$?` is a boolean for the *last cmdlet*. Different beast.
Porting a `[[ $? -ne 0 ]]` check straight to PowerShell will read the boolean, not the exit code — use `$LASTEXITCODE` for external `.exe` results.
String interpolation
BashDouble quotes interpolate `$var` and `$(cmd)`. Single quotes do not.
PowerShellSame — but only **double quotes**; single quotes are literal. `$()` is the subexpression operator inside strings.
Globbing
BashShell expands `*` / `?` / `[abc]` before the command sees them.
PowerShell`*` / `?` are expanded by *cmdlets* that opt in. Or use `-like` / `-match` for explicit pattern matching.
Error handling
Bash`set -e` to abort on failure; check `$?` after each command; `||` short-circuit.
PowerShell`try { ... } catch { ... }` — only catches **terminating** errors. Use `-ErrorAction Stop` to make a non-terminating error throw.
This is the #1 source of "my PowerShell script ignored the error" bugs — `Get-Content missing.txt` writes an error record but the script keeps running unless you add `-ErrorAction Stop`.
Aliases
Bash`alias ll='ls -la'` — text substitution, not exported to subshells unless `shopt -s expand_aliases`.
PowerShell`Set-Alias ll Get-ChildItem` — aliases map a name to a cmdlet *only*. They cannot include parameters.
You cannot `Set-Alias ll 'Get-ChildItem -Force'` — for that, define a function: `function ll { Get-ChildItem -Force @args }`.
Script extension
Bash`.sh` (convention only — bash ignores extensions, the shebang `#!/usr/bin/env bash` decides the interpreter).
PowerShell`.ps1`. Execution policy can block running unsigned scripts: `Set-ExecutionPolicy RemoteSigned`.
Side-by-side commands
The 32 most-compared commands in Bash and PowerShell. See all Bash commands · See all PowerShell commands.
- aliasCreate a shortcut name for a longer command line.Bash
alias ll='ls -la'PowerShellSet-Alias ll Get-ChildItem - awkPattern scanning and processing language for structured text.Bash
awk '{print $1}' filePowerShellGet-Content file | ForEach-Object { ($_ -split '\s+')[0] } - catPrint file contents to standard output.Bash
cat file.txtPowerShellGet-Content file.txt - chmodChange file mode bits (read / write / execute permissions) on Unix files.Bash
chmod 755 filePowerShellicacls file /grant Users:(RX) - cpCopy files and directories.Bash
cp source destPowerShellCopy-Item source dest - curlTransfer data from or to a server over HTTP, HTTPS, FTP, and many other protocols.Bash
curl https://example.comPowerShellInvoke-WebRequest https://example.com - cutExtract sections (fields or characters) from each line.Bash
cut -d',' -f1 file.csvPowerShellGet-Content file.csv | ForEach-Object { ($_ -split ',')[0] } - echoPrint arguments to standard output, separated by spaces, followed by a newline.Bash
echo "hello world"PowerShellecho "hello world" - exportSet an environment variable and mark it for export to child processes.Bash
export NAME=valuePowerShell$env:NAME = "value" - findLocate files by name, size, time, or other attributes.Bash
find . -name "*.log"PowerShellGet-ChildItem -Recurse -Filter *.log - grepSearch file contents for a pattern.Bash
grep -r "pattern" .PowerShellSelect-String -Pattern "pattern" -Path *.txt - headOutput the first lines of a file.Bash
head -n 10 filePowerShellGet-Content file -TotalCount 10 - historyShow previously run commands from the shell history.Bash
historyPowerShellGet-History - killSend a signal to a process (typically to terminate it).Bash
kill <pid>PowerShellStop-Process -Id <pid> - lsList directory contents.Bash
ls -laPowerShellGet-ChildItem -Force - mkdirCreate a new directory.Bash
mkdir dirPowerShellNew-Item -ItemType Directory -Path dir - mvMove or rename files and directories.Bash
mv source destPowerShellMove-Item source dest - pingSend ICMP echo requests to test reachability and round-trip latency.Bash
ping example.comPowerShellTest-Connection example.com - psList a snapshot of currently running processes.Bash
ps auxPowerShellGet-Process - rmDelete files and directories.Bash
rm filePowerShellRemove-Item file - sedStream editor for filtering and transforming text.Bash
sed 's/old/new/g' filePowerShell(Get-Content file) -replace 'old', 'new' - sortSort lines of text.Bash
sort filePowerShellGet-Content file | Sort-Object - sshOpen a secure shell on a remote host or run a remote command.Bash
ssh user@hostPowerShellssh user@host - tailOutput the last lines of a file.Bash
tail -n 10 filePowerShellGet-Content file -Tail 10 - tarBundle and unbundle files into a single archive (often combined with gzip / bzip2 / xz).Bash
tar -czf archive.tar.gz dir/PowerShelltar -czf archive.tar.gz dir - topInteractive process viewer — show running processes sorted by CPU or memory, refreshed in place.Bash
topPowerShellGet-Process | Sort-Object CPU -Descending | Select-Object -First 20 - touchCreate an empty file or update its modification time if it exists.Bash
touch file.txtPowerShellNew-Item file.txt - wcCount lines, words, or bytes.Bash
wc -l filePowerShell(Get-Content file).Count - wgetNon-interactive network downloader for HTTP, HTTPS, and FTP.Bash
wget https://example.com/file.zipPowerShellInvoke-WebRequest https://example.com/file.zip -OutFile file.zip - whichLocate an executable in PATH and print its full path.Bash
which python3PowerShellGet-Command python3 - xargsBuild and execute command lines from standard input.Bash
find . -name '*.tmp' | xargs rmPowerShellGet-ChildItem -Recurse -Filter *.tmp | ForEach-Object { Remove-Item $_.FullName } - zipPackage and compress files into a ZIP archive.Bash
zip -r archive.zip dir/PowerShellCompress-Archive -Path dir -DestinationPath archive.zip
Gotchas when porting between them
- PowerShell `ls`, `cat`, `cp`, `mv`, `rm`, `ps` are *aliases* for cmdlets, not the Unix binaries — Unix flags like `ls -la` will error.
- Quoting differs: bash `"$var"` and PowerShell `"$var"` both interpolate, but bash single-quotes block all expansion and PowerShell single-quotes block expansion and the `$()` subexpression operator. Don't copy-paste quoted strings blindly.
- Backticks in bash run a command; backticks in PowerShell are **the line-continuation character** and the escape character inside double-quoted strings — completely unrelated.
- PowerShell ships only on Windows by default. `pwsh` (PowerShell 7+) is the cross-platform Core build and behaves slightly differently — e.g. case-sensitive on Linux/macOS.
- Pipe behavior on Windows: when a PowerShell cmdlet pipes to a legacy `.exe`, the objects are stringified using the host's default formatter — what you see in the terminal is **not** what the `.exe` receives.