Tools#

The toolchain for zsh scripts is mostly POSIX utilities plus a few zsh-specific helpers. zsh itself ships zprof for profiling and zmodload for runtime module control; third-party tooling adds linting (with caveats) and formatting.

Interpreter#

  • zsh, the Z shell.

  • sh , POSIX shell (often dash, ash, or busybox sh on minimal images).

Check the version.

$ zsh --version

Useful invocation flags.

Flag

Effect

-x

Trace each command before it runs (same as set -x)

-n

Parse but do not execute (syntax check)

-f

Skip ~/.zshrc and /etc/zshrc (clean shell)

--no-rcs

Skip all init files

-c '...'

Run the command string

$ zsh -n script.zsh                # syntax check
$ zsh -f                            # clean interactive shell
$ zsh -xc 'cmd'                     # trace a one-liner

Linting#

  • ShellCheck, the standard shell linter. Limited zsh support. ShellCheck targets bash / sh / dash / ksh; running it on a zsh script catches the bash-shared bugs but misses zsh-specific surface (glob qualifiers, parameter-expansion flags, zparseopts, setopt names).

$ shellcheck --shell=bash script.zsh
  • zsh -n, syntax check only. Catches parser errors, not semantic bugs.

$ zsh -n script.zsh

Formatting#

  • shfmt, formatter for sh / bash / mksh. No native zsh dialect; pass -ln bash and expect some hand-fix on zsh-specific constructs (glob qualifiers, =() process substitution, foreach form).

$ shfmt -i 2 -ci -ln bash -w script.zsh

Profiling#

  • zsh/zprof, ships with zsh; profile startup and function calls.

$ # at the top of ~/.zshrc
$ zmodload zsh/zprof

$ # at the bottom
$ zprof | head -20

Reads the table top-down. The largest entry at the top is the biggest share of the startup budget; mitigations include autoload, zinit turbo-mode, and trimming compinit -d to a cached digest.

For per-call timing of a function, time works.

$ time my_function arg1 arg2

Testing#

  • zunit, TAP-style test framework written for zsh.

  • bats-core, TAP style; runs zsh scripts too.

  • shunit2, xUnit-style assertions; portable across POSIX shells.

$ #!/usr/bin/env zunit
$ @test 'addition works' {
  $ result=$(( 2 + 3 ))
  $ assert "$result" equals 5
$ }

Debugging#

  • set -x , trace each command before it runs.

  • set -e , exit on the first failed command.

  • set -u , error on unset variables.

  • zsh -x , trace mode without modifying the script.

  • functrace / PS4, customise the trace prefix to include line numbers and function names.

A useful trace prefix.

$ PS4='+%N:%i> '                    # function-name:lineno
$ set -x
  • zshdb, gdb-style debugger for zsh.

Editing#

Most editors recognise zsh by shebang or extension. Useful add-ons.

  • bash-language-server, LSP backed by ShellCheck. Recognises #!/usr/bin/env zsh but lints under bash rules.

  • zsh-mode (Emacs), vim-zsh (Vim), Shell-syntax (VS Code), syntax highlighting and indentation.

Cross-Platform#

For scripts that target multiple shells, validate against POSIX with the tools the POSIX neighbors ship.

$ shellcheck -s sh script.sh
$ shfmt -ln posix -d script.sh

For zsh-only features that the operator does want to lock in, declare the shebang and stop worrying about portability.

References#

  • Patterns for the strict-mode / profiling discipline these tools support.

  • Libraries for zmodload, autoload, and the built-in module catalog.

  • man 1 zsh, man 1 zshoptions, man 1 zshbuiltins.

  • ShellCheck (lint).

  • zprof manual