Command-line shells
Interactive shells and their scripting languages, from the Bourne and POSIX family to cross-platform newcomers. Interviewers ask because the shell is the interface to every server, container and CI runner you will ever touch.
on this pageshowhide
guide
overview
~1 minA shell is two things at once: the interactive program you type commands into, and the language your scripts, container entrypoints and CI steps are written in. Interviewers probe both halves because many shell bugs live in the gap between them — a line that works at your prompt and then fails in a container, over `ssh` or under cron. A good answer names which shell is actually running, in which mode, and which configuration it has read. The hub splits by lineage. [POSIX & Bourne-family shells](/topics/os-posix-shells) covers the shells that share the Bourne grammar: [POSIX sh, dash and ksh](/topics/os-posix-sh) as the portable baseline, [bash](/topics/os-bash) as the interactive default on most Linux hosts, [zsh](/topics/os-zsh) with its own expansion rules and completion system, and [fish](/topics/os-fish), which drops POSIX syntax on purpose in exchange for friendlier defaults. [Other shells](/topics/os-other-shells) is almost entirely [PowerShell](/topics/os-powershell), where commands exchange objects instead of text, and error handling, modules and remoting follow .NET rules rather than Unix ones. Junior rounds ask what a shebang does, why a pattern expanded unexpectedly, or what travels through a PowerShell pipe. Senior and principal rounds turn to portability decisions, startup-file debugging, completion design for a team's tooling and fleet-scale remoting — questions where the expected answer weighs a trade-off rather than recalls a flag. Start with the POSIX baseline, because every Bourne-family shell is described by how far it departs from it. Then learn bash and zsh as the shells you will meet at a prompt, and PowerShell as the separate model it is.
primer
### The shell at your prompt is not the shell running your script An interactive session and a script are separate processes that can run different programs. When a file is executed, the kernel reads its `#!` line and starts that interpreter; your login shell plays no part. Switching your own shell to zsh or fish changes what you type, not what your scripts do — and a script tested only by pasting it into a prompt has not been tested under its real interpreter. ### POSIX sh is a contract, not a program `/bin/sh` names whichever shell a distribution put at that path. POSIX defines a common grammar and a set of utilities; real shells implement it and then add extensions. A script that declares `sh` but leans on those extensions works only where the local `/bin/sh` happens to accept them, which is why portability is a decision you state, not a property you assume. ### Mode decides configuration Every shell starts as login or non-login, interactive or not, and each combination reads a different set of startup files. That is the root of a familiar shell support ticket: an environment variable or `PATH` entry that exists in a terminal but not in a remote command, a cron job or a CI step. Interviewers expect you to reason from the mode to the file rather than guess. ### Shared syntax, different semantics bash, zsh and ksh accept much of the same text and still mean different things by it. Splitting of unquoted variables, array indexing, what happens to a glob that matches nothing, and which options are on by default all vary — and options can change them again at runtime. Porting code between Bourne-family shells is a semantic review, not a copy. ### Text streams versus object pipelines Unix shells connect processes with byte streams, so each stage re-parses what the previous one printed. PowerShell connects commands with typed objects inside one process, so a stage reads properties directly. Each model has its own failure modes: fragile column parsing on one side, lost types and formatting placed too early on the other. ### Failure travels on different channels In Unix shells a command reports failure through its exit status, and the script must choose to act on it. PowerShell adds error records, split into those that stop execution and those that are only logged, while external programs it launches still report through exit codes. Knowing which channel a failure uses is the core of most shell error-handling questions. ### The interactive layer is a subsystem of its own Line editing, history, prompts and programmable completion are sizable parts of each shell, with their own configuration languages. In interviews they matter at two ends: a newcomer's everyday productivity, and a senior's job of shipping completions and a standard setup that stay fast and correct for a whole team.
- Shebang
- The #! first line of an executable script. The kernel runs the interpreter it names, passing the script's path as an argument.
- Login shell
- A shell started as the first process of a session, such as a console or SSH login, that reads profile-style startup files once for that session.
- Interactive shell
- A shell reading commands from a terminal and showing a prompt. It loads rc-style startup files that scripts and one-off remote commands skip.
- POSIX sh
- The shell command language standardised by POSIX, derived from the Bourne shell. Portable scripts target it; the /bin/sh on a given system may accept more.
- Bashism
- A construct bash accepts but POSIX sh does not guarantee, such as double-bracket tests or arrays. Harmless under bash, a failure under a strict sh.
- Word splitting
- The step in which a shell breaks the result of an unquoted expansion into separate arguments on whitespace. sh and bash do it by default; zsh does not.
- Globbing
- Expansion of filename patterns such as *, ? and bracket sets into matching paths. Shells differ on what they do when nothing matches.
- Shell option
- A named switch that changes shell behaviour at runtime, set with set, bash's shopt or zsh's setopt. Options can change how identical code is expanded.
- Programmable completion
- The mechanism by which a shell asks a per-command function or specification what to offer on Tab, instead of completing filenames only.
- Cmdlet
- A PowerShell command implemented as a .NET class and named in Verb-Noun form. It runs inside the PowerShell process and emits objects rather than text.
- Non-terminating error
- A PowerShell error that is recorded while the command keeps running; try/catch does not see it unless that call is told to stop.
- PowerShell edition
- Desktop for Windows PowerShell 5.1 on .NET Framework, Core for cross-platform PowerShell 6 and later on modern .NET. The two install side by side.
Follow one line from the keyboard to the processes it starts. The shell's line editor — Readline in bash, ZLE in zsh, fish's own — handles keys, history search and Tab, calling the completion system for candidates. On Enter, the shell parses the line and runs its expansions: variables, command substitution, globs and, in the Bourne family, word splitting. Builtins such as `cd` or `export` run inside the shell itself; everything else becomes a child process, and pipes connect each child's standard output to the next one's standard input. When the child is itself a script, the kernel's shebang handling picks its interpreter, which starts non-interactive and reads its own, shorter list of startup files. PowerShell reshapes the middle of that path. Cmdlets and functions run in the same process and hand objects to each other; a native program such as `git` is still a child process exchanging text and returning an exit code, so one pipeline can mix both models. Configuration attaches to this path by mode: profile files for login shells, rc files for interactive ones, and in zsh a file that every instance reads. Options and emulation modes sit underneath all of it, changing how the parser and expander behave. That is why the same few lines can carry three meanings: ```sh #!/bin/sh # the shebang picks the interpreter: dash on Debian, bash on RHEL files="a.txt b.txt" for f in $files; do # sh and bash split this into two words printf '%s\n' "$f" done # typed at a zsh prompt, the loop runs once with one argument # adding [[ -n $files ]] works where /bin/sh is bash, fails under dash ``` Reading the snippet well means naming the interpreter first, then its expansion rules, then its extensions — the order in which a strong answer handles most questions in this hub.
- POSIX sh, dash and ksh →
The portable baseline: what /bin/sh promises, where dash and ksh stop, and how to tell a real POSIX script from a bash one.
- bash (interactive shell) →
The interactive default on most Linux servers: startup files, history, line editing and completion you will use daily.
- zsh →
The default macOS shell, read against bash: different expansion rules, option-driven behaviour and a richer completion system.
- fish →
A deliberately non-POSIX shell, useful for separating which habits belong to your prompt and which to your scripts.
- PowerShell →
A separate model: object pipelines, two kinds of error, modules and remoting. Learn it after the Unix shells so the contrasts are visible.
Testing a
#!/bin/shscript only in bash and calling it portable — see the RHEL-versus-Debian failure for how that ends.Putting a
PATHexport in an interactive-only startup file, then debugging why the same command fails overssh, in cron or in a CI step.Claiming that changing your login shell with
chshbreaks your scripts or fixes them: the shebang, not your login shell, picks the interpreter.Treating zsh as bash with a nicer prompt: unquoted variables, array indexes and unmatched globs behave differently, so ported code needs a review, not a copy.
Sending PowerShell output onward after a
Format-*cmdlet, or scraping its display text as if it were a Unix pipeline.Assuming a PowerShell try/catch sees every failure: non-terminating errors and a native program's non-zero exit code both slip past it unless handled explicitly.
Saying "PowerShell" without naming the edition; Windows PowerShell 5.1 and PowerShell 7 differ in cmdlets, module compatibility and supported platforms.
Confusing the terminal emulator with the shell: the terminal draws text and forwards keys, the shell interprets commands, and a bug usually belongs to only one of them.
This guide assumes bash 5.x, zsh 5.x, current fish and PowerShell 7.x. Several interview questions turn on machines that run something else: - **macOS** still ships bash 3.2, the last release under GPLv2, and has made zsh the default login shell since Catalina (10.15). Advice about "bash on a Mac" often describes features the system bash lacks. - **Debian and Ubuntu** point `/bin/sh` at dash rather than bash, a switch made long ago for startup speed; Red Hat-family systems still link it to bash. The same script can therefore pass on one family and fail on the other. - **PowerShell** exists as two products. Windows PowerShell 5.1 is bundled with Windows and runs on .NET Framework; PowerShell 6 began the cross-platform line on .NET Core, and 7 is its current form, installed separately as `pwsh`. Some Windows-only cmdlets did not make the move. - **fish 4.0** moved the implementation from C++ to Rust without redesigning the language users write. When an answer depends on version — an array feature, a cmdlet, a default option — say which one you mean.
Interviewers expect you to separate the pieces around a shell. The **terminal emulator** draws characters and forwards keystrokes; a **multiplexer** such as tmux keeps sessions alive across disconnects; the **shell** interprets what is typed. Prompt themes, plugin managers and frameworks such as oh-my-zsh are configuration layered on a shell, not shells of their own. For scripting, the usual comparison is with a general-purpose language. A shell suits gluing programs together, short automation and container entrypoints; once a script grows data structures, involved error handling or tests, Python or Go is usually cheaper to maintain. ShellCheck, a static analyser for sh and bash scripts, is the common answer to "how do you catch shell bugs before running them". Among interactive shells, bash is what most servers offer, zsh is the macOS default and popular for completion and plugins, and fish trades POSIX compatibility for defaults that need little configuration. Newer shells such as Nushell bring the structured-data side of the divide to Unix, passing tables rather than text. PowerShell on Linux is mostly chosen by teams that already automate Windows with it, and for fleet state it is weighed against configuration tools such as Ansible.
explore
- POSIX & Bourne-family shells30 questions
- zsh17 questions
- fish3 questions
- bash (interactive shell)5 questions
- POSIX sh, dash and ksh5 questions
- Other shells32 questions
- PowerShell32 questions
questions
62 · 2 sectionsOn a machine whose shell is zsh, `pip install requests[security]` fails instantly with `zsh: no matches found: requests[security]` and pip never runs, while the identical line works in bash. Why does zsh reject it, and how do you fix it?
basics
~20 szsh reads the brackets as a filename pattern and, under its default NOMATCH option, refuses to run a command whose pattern matches nothing. bash instead passes an unmatched pattern through literally. Quote the argument: pip install 'requests[security]'.
A CLI you installed dropped a zsh completion function file named `_mytool` into a directory on disk, but in your zsh session pressing Tab after `mytool ` still only completes filenames. How does zsh's completion system find completion functions, and what has to be in place before that file takes effect?
basics
~20 szsh looks for completion functions in the directories listed in its fpath array, and only uses them once the completion system has been initialised by running autoload -Uz compinit followed by compinit. The directory must be on fpath before compinit runs.
In bash, which startup files does a login shell read compared with an interactive non-login shell, and why does almost every ~/.bash_profile contain a line that sources ~/.bashrc?
basics
~10 sA login bash shell reads /etc/profile and then the first existing of ~/.bash_profile, ~/.bash_login or ~/.profile. An interactive non-login shell reads ~/.bashrc instead. Sourcing ~/.bashrc from ~/.bash_profile makes both entry paths behave identically.
A deploy script whose first line is `#!/bin/sh` runs fine on a RHEL host but on Debian fails with `[[: not found` and `Syntax error: "(" unexpected`. What is /bin/sh on each system, and why do those lines fail on one and not the other?
basics
~20 sOn RHEL /bin/sh is a symlink to bash, which still accepts [[ ]] and arrays even when invoked as sh. On Debian and Ubuntu /bin/sh is dash, a small strict POSIX shell that has neither. The script was never portable; RHEL hid the bug.
In zsh, `files="a.txt b.txt"; touch $files` creates one file literally named `a.txt b.txt`, while bash creates two files. Why do the shells differ, and what are the correct ways to get the two-file behaviour in zsh?
basics
~20 szsh does not split unquoted parameter expansions into words on whitespace the way bash does, so $files stays a single argument. Use a real array — files=(a.txt b.txt) — or force splitting for one expansion with ${=files}.
In PowerShell, what actually travels between two commands joined by the pipe operator, and how does that differ from a pipeline in bash?
basics
~20 sPowerShell pipes live .NET objects rather than text. Each command hands the next typed objects with named properties and methods, so downstream commands read properties directly instead of re-parsing columns of formatted text the way bash pipelines must.
PowerShell command names follow a Verb-Noun convention such as `Get-Process`. What does that convention buy you, and how would you find a command whose name you do not know?
basics
~20 sVerb-Noun names make PowerShell predictable and searchable: the verb comes from an approved list describing the action, the noun names the thing acted on. Because the shape is uniform, Get-Command can filter by verb or noun and Get-Help documents any command you find.
In PowerShell, what is the difference between a .psm1 script module file and a .psd1 module manifest, and what does adding a manifest give you?
basics
~20 sA .psm1 file holds the module's actual code — its function definitions. A .psd1 manifest holds metadata about the module as a PowerShell hashtable: version, GUID, author, dependencies, and the exact list of commands the module exports.
In PowerShell remoting, what is the difference between Enter-PSSession and Invoke-Command, and when would you use each?
basics
~20 sEnter-PSSession opens an interactive prompt on one remote machine, so you type commands there and see results immediately. Invoke-Command sends a script block to one or many machines, runs it there, and returns the results into your local session.
In PowerShell, what do the `@()` and `@{}` literals each create, and how do you add an item to each afterwards?
basics
~20 s@() builds an array — an ordered list of values indexed by position. @{} builds a hashtable — unordered key/value pairs. Append to an array with $a += $item; add or update a hashtable entry with $h['key'] = $value.