The biggest adjustment moving from bash to PowerShell isn't syntax — it's that PowerShell pipes objects between commands, not plain text, which changes how filtering and processing actually work.
The core difference: objects, not text
# bash — everything is text; you parse it with grep/awk/cut
ps aux | grep node | awk '{print $2}'# PowerShell — Get-Process returns real objects with real properties
Get-Process node | Select-Object IdThere's no text-parsing step in the PowerShell version — Get-Process returns actual process objects with typed properties (Id, CPU, Name), and Select-Object picks the property you want directly, rather than counting whitespace-separated columns in a text stream.
Command translation table
| bash | PowerShell | What it does |
|---|---|---|
ls -la | Get-ChildItem (alias ls, dir) | List directory contents |
cd /path | Set-Location (alias cd) | Change directory |
cat file.txt | Get-Content file.txt (alias cat) | Print file contents |
grep "text" file | Select-String "text" file | Search for a pattern |
rm file | Remove-Item file (alias rm) | Delete a file |
export VAR=value | $env:VAR = "value" | Set an environment variable |
which node | Get-Command node | Find where a command resolves to |
curl https://api... | Invoke-WebRequest / Invoke-RestMethod | Make an HTTP request |
Most common bash commands have a PowerShell alias (ls, cat, rm, cd all work) — these are convenience aliases pointing at the real Verb-Noun cmdlet, not separate implementations, which is why behavior can differ subtly from bash's actual ls/cat.
The pipe, with real filtering
Get-Process | Where-Object { $_.CPU -gt 100 } | Sort-Object CPU -DescendingWhere-Object filters based on a real property comparison ($_.CPU -gt 100 — "CPU greater than 100"), not a text pattern match — this is the direct payoff of piping objects instead of text: filtering and sorting by an actual typed field, not a guessed column position.
Variables and environment variables
$name = "world"
Write-Output "Hello, $name"
$env:PATH # read an environment variable
$env:NODE_ENV = "production" # set one, for this session$env: is the PowerShell equivalent of $ for environment variables specifically — a plain $variable is a regular PowerShell variable, scoped to the session, not automatically exported to child processes the way an exported bash variable is.
Scripts: .ps1 files and execution policy
PowerShell scripts (.ps1) don't run by default on a fresh Windows install — an execution policy blocks unsigned scripts as a security default:
Get-ExecutionPolicy
Set-ExecutionPolicy RemoteSigned -Scope CurrentUserRemoteSigned (a common practical choice) allows locally-created scripts to run while still requiring downloaded scripts to be signed — a reasonable middle ground between the very restrictive default and disabling the protection entirely.
Real-world example: bulk-renaming files
Get-ChildItem *.txt | Rename-Item -NewName { $_.Name -replace '\.txt$', '.md' }This renames every .txt file to .md in the current directory — Get-ChildItem returns real file objects, Rename-Item operates on each one via the pipeline, and the script block computes the new name per file using the object's actual .Name property.
Functions and modules
function Get-Greeting {
param([string]$Name)
return "Hello, $Name"
}
Get-Greeting -Name "world"PowerShell functions use named parameters by convention (-Name) rather than positional arguments as the default style — this is idiomatic PowerShell, even though positional arguments also work, because named parameters make a call site self-documenting without needing to check the function definition.
Working with JSON, the way most modern tooling expects
$response = Invoke-RestMethod -Uri "https://api.example.com/data"
$response.items | Where-Object { $_.status -eq "active" }
# Convert an object to JSON, or parse JSON into an object
$data | ConvertTo-Json
'{"name": "Ada"}' | ConvertFrom-JsonInvoke-RestMethod (unlike Invoke-WebRequest) automatically parses a JSON response into a real PowerShell object — no separate parsing step, and the result immediately supports Where-Object/Select-Object the same as any other PowerShell object, which is a genuinely convenient default for anyone working with JSON APIs regularly.
Profile scripts: PowerShell's equivalent of .bashrc
Persistent customization (aliases, prompt configuration, environment variables set on every session) lives in a profile script, analogous to .bashrc/.zshrc:
$PROFILE # shows the path to your current profile script
notepad $PROFILE # or code $PROFILE, to edit it# Inside the profile script
function ll { Get-ChildItem -Force }
$env:EDITOR = "code"Unlike bash, PowerShell has multiple possible profile scopes (current user/current host, current user/all hosts, all users) — $PROFILE alone points at the most specific one (current user, current host), which is the right one to edit for personal customization in most cases.
Common mistakes
- Treating PowerShell output as plain text and reaching for string parsing (
-split, regex) when the actual object already has the property you need —Select-Object -Propertyor.PropertyNameis almost always the more correct, more robust approach. - Assuming an aliased command (
ls,cat,rm) behaves identically to its bash namesake in every edge case. They're aliases for PowerShell cmdlets with PowerShell's own semantics — subtle differences (flag names, output format) can surface in less common usage. - Running scripts with an overly permissive execution policy (
Unrestricted) instead of a more scoped one (RemoteSigned,-Scope CurrentUser) — the scoped, less-permissive option is usually sufficient and meaningfully safer. - Forgetting that
$env:VAR = "value"only sets the variable for the current PowerShell session, the same wayexportin bash is session-scoped — persisting it across sessions needs a profile script or a system-level environment variable setting.
Related reading
- Windows Terminal: Customization Tips for Developers — shares tags: windows (same category).
- WSL2 Setup Guide: Running Linux on Windows for Development — shares tags: windows.
- Essential Linux Commands Every Developer Should Know — shares tags: programming.
- Regular Expressions Explained: A Practical Guide for Developers Who Avoid Them — shares tags: programming.
- Big O Notation Without the Math Panic — shares tags: programming.