Web Dev Drops

Bash vs Zsh vs PowerShell: Which Shell for Developers?

Douglas Matoso
Douglas Matoso
Published
Reading time: 13 min.

Open a terminal on a Mac, on a Linux server and on a Windows PC, and you will probably land in three different shells: Zsh, Bash and PowerShell. In this post I will compare Bash vs Zsh vs PowerShell from a developer's point of view, with commands you can run. The short answer: the gap in Zsh vs Bash is small. They share almost all of their syntax, Zsh has the better interactive features, and Bash is the safer choice for scripts that have to run anywhere. PowerShell works differently: its commands pass objects instead of text, and it's the best tool to automate Windows. A common setup is a Bash-compatible shell for daily work and scripts, plus PowerShell when the job is on Windows.

The Bash logo, a percent sign for Zsh and the PowerShell logo over the title Bash vs Zsh vs PowerShell

Note: I ran the examples with Bash 5.3.15 (Git Bash on Windows 11), Bash 5.2.21 and Zsh 5.9 (Ubuntu 24.04 on WSL), PowerShell 7.6.9 and Windows PowerShell 5.1. The outputs below are the real ones.

Bash vs Zsh vs PowerShell at a glance

BashZshPowerShell
Latest version5.3 (July 2025)5.9.2 (July 2026)7.6 LTS (March 2026)
Default onMost Linux distrosmacOS (since 10.15 Catalina)Windows (Windows PowerShell 5.1)
Runs onLinux, macOS, Windows (Git Bash, WSL)Linux, macOS, Windows via WSLWindows, macOS, Linux
Pipeline passesTextText.NET objects
Syntax familyPOSIX shellPOSIX shell, with extensionsIts own (Verb-Noun cmdlets)
Config file~/.bashrc~/.zshrc$PROFILE
LicenseGPLv3MIT-styleMIT
Best forPortable scripts, servers, CI, DockerDaily interactive useWindows automation, structured data

What is a shell?

A shell is the program that reads the commands you type in the terminal and runs them. The terminal (Windows Terminal, iTerm2, the VS Code terminal) is only the window. The shell inside it decides what ls, $HOME or | mean. That's why you can open Bash, Zsh or PowerShell in the same terminal app.

Each operating system comes with a different one:

Three screens with the default prompt of each system: percent sign on macOS, dollar sign on Linux and PS> on Windows
  • macOS: Zsh. Apple says that "starting with macOS 10.15, your Mac uses zsh as the default login shell and interactive shell". Bash is still there, but it's the old 3.2.57.
  • Linux: Bash on most distributions (Ubuntu, Debian, Fedora).
  • Windows: Windows PowerShell 5.1 is built in. The current version, PowerShell 7, is a separate install. Bash comes with Git Bash or WSL.

To see which shell you're using in Bash or Zsh, run:

echo $SHELL   # your login shell, e.g. /bin/zsh
echo $0       # the shell running right now

In PowerShell, $PSVersionTable.PSVersion shows the version.

Zsh vs Bash: what's the difference?

Zsh (Z shell) and Bash (Bourne Again Shell) come from the same family. Variables, if, for, pipes, redirects and && work the same way, and most commands you copy from a tutorial run in both. The differences show up in two places: some details of the scripting language, and the interactive features.

The same code in both

This works in Bash and in Zsh:

name="Ana"
if [[ -f package.json ]]; then
  echo "Hello, $name. This is a Node project."
fi
for file in *.md; do echo "$file"; done

If you know the basics from my post on Bash scripting, you already know most of Zsh.

Where Zsh behaves differently

Let's run the same script in both shells:

files="a.txt b.txt"
for f in $files; do echo "item: $f"; done
arr=(one two three)
echo "first: ${arr[1]}"
echo "count: ${#arr[@]}"
echo $((7 / 2.0))

In Bash:

item: a.txt
item: b.txt
first: two
count: 3
diff.sh: line 6: 7 / 2.0: arithmetic syntax error: invalid arithmetic operator (error token is ".0")

In Zsh:

item: a.txt b.txt
first: one
count: 3
3.5

Three differences in six lines:

  1. Word splitting: Bash splits an unquoted variable on spaces, so $files became two items. Zsh keeps it as one value. (This is why Bash scripts need "$var" with quotes everywhere.)
  2. Arrays start at 1 in Zsh. ${arr[1]} is the first item in Zsh and the second in Bash.
  3. Math: Bash only does integer math. Zsh handles decimals.

Globs (wildcards) are different too. When a pattern matches nothing, Bash passes it to the command as it is, and Zsh stops with an error before running the command:

$ ls *.xyz          # Bash
ls: cannot access '*.xyz': No such file or directory

% ls *.xyz          # Zsh
zsh: no matches found: *.xyz

And Zsh expands **/*.ts recursively by default (all .ts files in all subfolders). Bash only does that after shopt -s globstar.

Important! These differences only matter for code that runs inside your shell, like functions in ~/.zshrc or commands you paste. A script that starts with #!/usr/bin/env bash runs in Bash even if you call it from Zsh. So switching your terminal to Zsh doesn't break your Bash scripts.

Interactive features: completion, plugins and Oh My Zsh

This is where most people pick Zsh. Its completion system can show a menu you move through with the arrow keys and complete the options and subcommands of tools like git and npm. It also has options for spelling correction and for sharing history between terminal windows, and a prompt that's easy to customize. Most of this is off in a plain Zsh: you turn it on in ~/.zshrc (for completion, autoload -Uz compinit && compinit) or let a framework do it.

A popular add-on is Oh My Zsh, a framework that bundles 150 themes and 300+ plugins (Git aliases, Node, Docker, kubectl...). The install is one command:

sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

Plugins go in the plugins=(...) line of ~/.zshrc. Two popular ones that aren't bundled are zsh-autosuggestions (suggests commands from your history in gray as you type) and zsh-syntax-highlighting.

Bash can get some of this with bash-completion and Oh My Bash, but it takes more work to get close.

Tip: If Oh My Zsh makes your terminal slow to open, a cross-shell prompt like Starship is a lighter option. It works in Bash, Zsh and PowerShell, so your prompt looks the same in all three.

Is Zsh better than Bash?

For typing commands all day, it usually is: the completion and the plugins save time. For scripts, no. Bash is installed on nearly every Linux server, Docker image and CI runner, while Zsh usually isn't. A common setup is Zsh as your interactive shell and Bash for the scripts you commit to a repository.

PowerShell vs Bash

PowerShell is Microsoft's shell and scripting language, built on .NET. Since version 6 it's open source (MIT) and runs on Windows, macOS and Linux. Its syntax is different from Bash, but the bigger change is what flows through the pipe.

Text vs objects

In Bash, every command prints text, and the next command has to parse that text with tools like grep, awk, cut or jq. In PowerShell, commands (called cmdlets) pass .NET objects with properties, so you filter and sort by property name.

Diagram of two pipelines: in Bash, lines of text flow between commands; in PowerShell, structured objects flow between commands

Let's list the three largest files in a folder. In PowerShell:

Get-ChildItem src/libs -File | Sort-Object Length -Descending | Select-Object -First 3 Name, Length
Name     Length
----     ------
posts.ts   3683
rss.ts     2051
theme.ts   1150

Sort-Object Length works because each item is a System.IO.FileInfo object with a Length property. In Bash, ls gives you text, and you rely on its flags:

ls -S src/libs | head -3
posts.ts
rss.ts
theme.ts

For quick jobs like this, Bash is shorter. The object model pays off when the data has structure. Reading a field from package.json in PowerShell needs nothing extra:

(Get-Content package.json | ConvertFrom-Json).name
webdevdrops

In Bash you'd install jq and run jq -r .name package.json. The same goes for CSV (Import-Csv), XML and API responses (Invoke-RestMethod returns objects).

Everyday commands side by side

TaskBash / ZshPowerShell
List files, including hiddenls -laGet-ChildItem -Force
Delete a folderrm -rf distRemove-Item dist -Recurse -Force
Search text in filesgrep -rn "TODO" srcGet-ChildItem src -Recurse -File | Select-String "TODO"
Set an environment variableexport NODE_ENV=production$env:NODE_ENV = 'production'
Read a variableecho $HOME$HOME or $env:USERPROFILE
Run B only if A workednpm test && npm run buildSame (PowerShell 7+)
Config file~/.bashrc, ~/.zshrcnotepad $PROFILE

Unix habits that break in PowerShell

PowerShell has aliases with Unix names (ls, cat, rm, cp), which helps at first. But they point to cmdlets, and the cmdlets don't know the Unix flags:

PS> ls -la
Get-ChildItem: A parameter cannot be found that matches parameter name 'la'.

rm -rf fails the same way. Other things to watch for:

  • && and || only exist in PowerShell 7. In Windows PowerShell 5.1, npm test && npm run build is a syntax error.
  • curl in Windows PowerShell 5.1 is an alias for Invoke-WebRequest, so curl flags don't work. PowerShell 7 runs the real curl.exe. In 5.1, type curl.exe.
  • Scripts may be blocked. On Windows clients, if nobody set an execution policy, the effective one is Restricted, which doesn't run .ps1 files at all. To allow your own scripts, run Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser. On macOS and Linux there's no execution policy.

PowerShell 7 vs Windows PowerShell 5.1

There are two PowerShells on Windows, and it's easy to mix them up:

  • Windows PowerShell 5.1 (powershell.exe) comes with Windows and runs on the older .NET Framework.
  • PowerShell 7 (pwsh) is the cross-platform version under active development. 7.6 is the current LTS, released in March 2026 on .NET 10 and supported until November 2028.

They install side by side. As a developer, install PowerShell 7 and use it as your default:

winget install --id Microsoft.PowerShell --source winget

It also has a good interactive experience now: PSReadLine shows suggestions from your history in gray as you type (press the right arrow to accept, F2 to switch to a list view), much like zsh-autosuggestions.

Is PowerShell better than Bash?

It depends on where the code runs. PowerShell is better for managing Windows (services, the registry, Active Directory, Azure) and for scripts that work with structured data. Bash is better for Linux servers, Docker images and CI pipelines, and for following docs and tutorials, which are almost always written for a POSIX shell. You can install PowerShell on Linux, and Bash on Windows, but each one is at home on its own system.

Git Bash vs PowerShell on Windows

On Windows you can have both installed. Git works the same in either. A simple rule: Git Bash (or WSL) when you're following a README or running project scripts written for Mac and Linux, and PowerShell 7 when you're automating something on Windows itself.

If you go with Git Bash, see my guide Git Bash: How to Install and Use It on Windows, and to set up Git and GitHub from scratch, Git on Windows and GitHub: How to Install and Configure.

Note: Zsh doesn't run natively on Windows. To use it there, install WSL (wsl --install) and then Zsh inside the Linux distro.

Which shell should you use?

  • On macOS: keep Zsh as your terminal shell and add Oh My Zsh or Starship. Write scripts for Bash with #!/usr/bin/env bash.
  • On Linux: Bash works fine. Install Zsh if you want the extra completion and plugins.
  • On Windows: install PowerShell 7 for Windows tasks, and Git Bash or WSL for Unix-style work. Use WSL with Bash or Zsh if your project runs on Linux in production.
  • For scripts other people will run (CI, Docker, repo tooling): Bash. On a Windows-only team, PowerShell 7 is a reasonable choice, since pwsh also runs on the Linux CI runners of GitHub Actions.

How to change your default shell

On macOS or Linux, use chsh with a path listed in /etc/shells:

cat /etc/shells         # shells you can choose
chsh -s $(which zsh)    # switch to Zsh
chsh -s /bin/bash       # back to Bash

Open a new terminal to see the change. On Ubuntu, install Zsh first with sudo apt install zsh. On macOS 13 or later you can also do it in System Settings > Users & Groups: Control-click your user, choose Advanced Options and pick a Login shell.

On Windows, pick the default profile in Windows Terminal > Settings > Startup > Default profile.

In VS Code, the terminal shell is set per operating system in settings.json:

{
  "terminal.integrated.defaultProfile.osx": "zsh",
  "terminal.integrated.defaultProfile.linux": "bash",
  "terminal.integrated.defaultProfile.windows": "PowerShell"
}

FAQ

Can I run Bash scripts in Zsh?

Yes, if the script has a Bash shebang (#!/usr/bin/env bash or #!/bin/bash) and you run it as ./script.sh or bash script.sh. It runs in Bash no matter which shell you're in. Problems only show up when you source a Bash script inside Zsh, because of the differences in arrays and word splitting shown above.

Why did my Mac say "The default interactive shell is now zsh"?

macOS shows that message when you start Bash. Apple switched the default to Zsh in macOS 10.15 Catalina. You can run chsh -s /bin/zsh to switch, or keep Bash and hide the message by adding export BASH_SILENCE_DEPRECATION_WARNING=1 to ~/.bash_profile.

Can I use PowerShell on Mac and Linux?

Yes. PowerShell 7 supports macOS 15, 26 and 27, and Linux distributions like Ubuntu, Debian and RHEL. Install it from the releases page or your package manager and run pwsh. Windows-specific cmdlets (services, registry) aren't available there.

What about Fish?

Fish is another interactive shell with autosuggestions and syntax highlighting built in, with no plugins to install. The catch is that its syntax isn't POSIX-compatible, so commands you copy from tutorials sometimes need changes. Zsh is the safer choice if you want those features and still want Bash-like syntax.

Conclusion

Bash and Zsh are close relatives: learn one and you know most of the other. Use Zsh for its interactive features and keep your scripts in Bash so they run anywhere. PowerShell is a different tool, with objects instead of text, and it's the best way to automate Windows.

If you want to go deeper on the scripting side, my post on Bash scripting covers variables, loops and functions with tested examples.

See you next time!