Skip to content
shellmap

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

    Bash

    Bytes of text. Next command parses lines itself.

    PowerShell

    Typed .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

    Bash

    NAME=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

    Bash

    Double quotes interpolate `$var` and `$(cmd)`. Single quotes do not.

    PowerShell

    Same — but only **double quotes**; single quotes are literal. `$()` is the subexpression operator inside strings.

  • Globbing

    Bash

    Shell 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'
    PowerShell
    Set-Alias ll Get-ChildItem
  • awkPattern scanning and processing language for structured text.
    Bash
    awk '{print $1}' file
    PowerShell
    Get-Content file | ForEach-Object { ($_ -split '\s+')[0] }
  • catPrint file contents to standard output.
    Bash
    cat file.txt
    PowerShell
    Get-Content file.txt
  • chmodChange file mode bits (read / write / execute permissions) on Unix files.
    Bash
    chmod 755 file
    PowerShell
    icacls file /grant Users:(RX)
  • cpCopy files and directories.
    Bash
    cp source dest
    PowerShell
    Copy-Item source dest
  • curlTransfer data from or to a server over HTTP, HTTPS, FTP, and many other protocols.
    Bash
    curl https://example.com
    PowerShell
    Invoke-WebRequest https://example.com
  • cutExtract sections (fields or characters) from each line.
    Bash
    cut -d',' -f1 file.csv
    PowerShell
    Get-Content file.csv | ForEach-Object { ($_ -split ',')[0] }
  • echoPrint arguments to standard output, separated by spaces, followed by a newline.
    Bash
    echo "hello world"
    PowerShell
    echo "hello world"
  • exportSet an environment variable and mark it for export to child processes.
    Bash
    export NAME=value
    PowerShell
    $env:NAME = "value"
  • findLocate files by name, size, time, or other attributes.
    Bash
    find . -name "*.log"
    PowerShell
    Get-ChildItem -Recurse -Filter *.log
  • grepSearch file contents for a pattern.
    Bash
    grep -r "pattern" .
    PowerShell
    Select-String -Pattern "pattern" -Path *.txt
  • headOutput the first lines of a file.
    Bash
    head -n 10 file
    PowerShell
    Get-Content file -TotalCount 10
  • historyShow previously run commands from the shell history.
    Bash
    history
    PowerShell
    Get-History
  • killSend a signal to a process (typically to terminate it).
    Bash
    kill <pid>
    PowerShell
    Stop-Process -Id <pid>
  • lsList directory contents.
    Bash
    ls -la
    PowerShell
    Get-ChildItem -Force
  • mkdirCreate a new directory.
    Bash
    mkdir dir
    PowerShell
    New-Item -ItemType Directory -Path dir
  • mvMove or rename files and directories.
    Bash
    mv source dest
    PowerShell
    Move-Item source dest
  • pingSend ICMP echo requests to test reachability and round-trip latency.
    Bash
    ping example.com
    PowerShell
    Test-Connection example.com
  • psList a snapshot of currently running processes.
    Bash
    ps aux
    PowerShell
    Get-Process
  • rmDelete files and directories.
    Bash
    rm file
    PowerShell
    Remove-Item file
  • sedStream editor for filtering and transforming text.
    Bash
    sed 's/old/new/g' file
    PowerShell
    (Get-Content file) -replace 'old', 'new'
  • sortSort lines of text.
    Bash
    sort file
    PowerShell
    Get-Content file | Sort-Object
  • sshOpen a secure shell on a remote host or run a remote command.
    Bash
    ssh user@host
    PowerShell
    ssh user@host
  • tailOutput the last lines of a file.
    Bash
    tail -n 10 file
    PowerShell
    Get-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/
    PowerShell
    tar -czf archive.tar.gz dir
  • topInteractive process viewer — show running processes sorted by CPU or memory, refreshed in place.
    Bash
    top
    PowerShell
    Get-Process | Sort-Object CPU -Descending | Select-Object -First 20
  • touchCreate an empty file or update its modification time if it exists.
    Bash
    touch file.txt
    PowerShell
    New-Item file.txt
  • wcCount lines, words, or bytes.
    Bash
    wc -l file
    PowerShell
    (Get-Content file).Count
  • wgetNon-interactive network downloader for HTTP, HTTPS, and FTP.
    Bash
    wget https://example.com/file.zip
    PowerShell
    Invoke-WebRequest https://example.com/file.zip -OutFile file.zip
  • whichLocate an executable in PATH and print its full path.
    Bash
    which python3
    PowerShell
    Get-Command python3
  • xargsBuild and execute command lines from standard input.
    Bash
    find . -name '*.tmp' | xargs rm
    PowerShell
    Get-ChildItem -Recurse -Filter *.tmp | ForEach-Object { Remove-Item $_.FullName }
  • zipPackage and compress files into a ZIP archive.
    Bash
    zip -r archive.zip dir/
    PowerShell
    Compress-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.

Full references

Other comparisons