5.3 Shells, Variables, and the Environment

Key Takeaways

  • zsh has been Kali's default interactive shell since 2020.4, but /bin/sh is dash — so #!/bin/sh scripts must be strictly POSIX, not bash-isms.
  • A user's login shell is set in the seventh (final) field of /etc/passwd, e.g. kali:x:1000:1000:,,,:/home/kali:/usr/bin/zsh.
  • PATH is a colon-separated search list of directories; echo $PATH inspects it and export PATH="$PATH:/opt/tools" extends it for the session.
  • export copies a variable into the environment so child processes (subshells, scripts) inherit it; without export, a variable is shell-local.
  • Login shells read /etc/profile then the user's profile; interactive zsh reads ~/.zshrc — put aliases and PATH tweaks in ~/.zshrc to persist them.
Last updated: July 2026

Which Shell, and Where It Comes From

A shell is the command-line interpreter between you and the kernel. Kali ships several — zsh, bash, dash, sh — and the exam tests their distinct roles. Since Kali 2020.4, zsh is the default interactive shell (previously bash). But look at /bin/sh:

ls -l /bin/sh     # /bin/sh -> dash

dash (Debian Almquist Shell) is a minimal, fast, strictly POSIX shell used for system scripts. This split is deliberate: humans get zsh's completions and themes, while boot scripts get dash's speed and standards compliance.

Each account's login shell lives in the last field of /etc/passwd:

kali:x:1000:1000:Kali,,,:/home/kali:/usr/bin/zsh

Change it with chsh -s /bin/bash (the shell must be listed in /etc/shells). The running shell's path is in $SHELL (your login shell) while $0 shows what is actually executing right now — they can differ if you launched bash from zsh.

Login vs Non-Login Shells and Startup Files

A login shell starts when you authenticate — console sign-in, SSH, su -, or bash --login. A non-login interactive shell starts when you open a new terminal inside a desktop session or type zsh at a prompt. They read different startup files, and misplacing a setting between them is the classic "my alias works in SSH but not in the terminal" bug:

Shell eventFiles read (in order)
zsh login shell/etc/zsh/zprofile, ~/.zprofile, then interactive files
zsh interactive (any)/etc/zsh/zshrc, then ~/.zshrc
bash login shell/etc/profile, then first of ~/.bash_profile, ~/.bash_login, ~/.profile
bash non-login interactive/etc/bash.bashrc, then ~/.bashrc
/bin/sh script (dash)none — non-interactive shells read no rc files

Practical rule for Kali's zsh: put export lines, PATH additions, and aliases in ~/.zshrc so every interactive terminal gets them.

Variables and the Environment

The environment is a set of name=value pairs handed to every process at launch. Inspect it with env or printenv; inspect one variable with echo $PATH. The exam-worthy variables:

  • PATH — colon-separated directory list the shell searches for commands, e.g. /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin. "Command not found" usually means the binary's directory is not in PATH; which nmap shows what PATH resolves.
  • HOME — your home directory (/home/kali); cd with no arguments goes here.
  • USER / LOGNAME — the account name.
  • SHELL — the login shell from /etc/passwd.
  • LANG — locale, e.g. en_US.UTF-8, controlling language and sorting.
  • PS1 — the prompt string.

Shell variables versus environment variables is the key distinction:

MYVAR=hello            # shell-local: visible only in THIS shell
echo $MYVAR            # hello
bash -c 'echo $MYVAR'  # empty — the child did not inherit it
export MYVAR=hello     # now copied into the environment
bash -c 'echo $MYVAR'  # hello — children inherit exported vars

A subshell (parentheses ( ... ), pipelines, command substitution $(...)) is a child process: it inherits exported variables, but changes it makes — cd, variable assignments — never propagate back to the parent. Extend PATH for the session with export PATH="$PATH:/opt/tools"; persist it by adding that line to ~/.zshrc.

Aliases and Shortcuts

An alias substitutes one string for another at the start of a command line:

alias ll='ls -alFh'
alias grep='grep --color=auto'
alias                     # list all aliases
unalias ll                # remove one

Aliases are not exported and do not exist inside scripts — they are interactive conveniences defined in ~/.zshrc or ~/.bashrc. For script logic use shell functions instead.

POSIX: Why #!/bin/sh Scripts Must Behave

The shebang (#!) on a script's first line selects its interpreter. #!/bin/bash gets bash's full feature set; #!/bin/sh gets dash on Kali, which implements only the POSIX standard. Common bashisms that break under dash:

BashismPOSIX-compliant replacement
[[ -f file ]][ -f file ]
== inside [ ]=
source file.sh. file.sh
arrays, declare, local scoping tricksavoid; not in POSIX
echo -eprintf
function name { }name() { }

Test a script's portability with dash -n script.sh (syntax check) or the checkbashisms tool from the devscripts package. The exam angle: a script with #!/bin/sh that works on your bash desktop may fail on Kali or Debian because /bin/sh is dash — always match the shebang to the features you actually use, and when in doubt, write POSIX.

Special Variables, One-Shot Environments, and su

Shells expose built-in special variables that scripts rely on: $? holds the exit status of the last command (0 means success, anything else is an error), $$ is the current shell's PID, $0 the script or shell name, and $#, $1, $2... give the argument count and positional parameters. echo $? immediately after a command is the standard quick success check.

You can also set a variable for a single command without exporting it by prefixing the command: LANG=C sort file.txt runs just that one invocation with the C locale — a common trick for predictable English error messages and sort order. Remove a variable entirely with unset MYVAR.

Be careful with su: plain su switches user but keeps your current environment, while su - (equivalently su -l) starts a login shell for the target user, reading their profile files and resetting PATH, HOME, and the rest as if they had logged in fresh. Test scripts that depend on PATH under su - or a clean env -i environment. Finally, remember that variable expansion happens in the current shell before the command runs — echo $HOME substitutes locally — which is why single quotes (echo '$HOME') are used to pass a literal $ through to tools like awk that do their own expansion.

Test Your Knowledge

A script begins with #!/bin/sh and uses [[ "$a" == "test" ]] for a string comparison. It runs fine on a colleague's machine where /bin/sh is bash, but fails on Kali. Why?

A
B
C
D
Test Your Knowledge

You set TOOLS=/opt/wordlists in your zsh session, but a script you launch cannot see $TOOLS. What is the cause and fix?

A
B
C
D