Zsh script failures are easier to isolate when the parse check, normal output, and trace log are treated as separate evidence. A syntax check catches parse errors before any branch runs, a normal run confirms the result the caller sees, and execution tracing shows the commands that produced that result.
Use zsh -n to parse a file without executing it, then use zsh -x when the script still needs command-by-command evidence. A custom PS4 value that includes %N and %i adds the script or function name and line number to trace lines, which makes a failing branch easier to connect to the source file.
The example runs Zsh with -f while tracing so user startup files do not add aliases or functions to the run. A system /etc/zshenv file can still appear at the top of the trace on some packages, so keep trace logs local until arguments, paths, tokens, hostnames, and environment values have been reviewed.
Related: How to create a function in Zsh
Related: How to compile a Zsh script
Related: How to configure Zsh startup files
Related: Debug a Bash script
Steps to debug a Zsh script:
- Create a small Zsh script with a trace-friendly PS4 value.
- debug-demo.zsh
#!/usr/bin/env zsh emulate -L zsh setopt err_exit PS4='+ %N:%i: ' show_item() { local name=${1:-sample} print -r -- "item=$name" } show_item "${1:-sample}"
The custom PS4 prefix is assigned before the function call so later trace lines show the call site and the lines executed inside show_item.
- Check the script syntax before running it.
$ zsh -n debug-demo.zsh
No output from zsh -n means Zsh parsed the file without finding a syntax error.
- Run the script normally and confirm the caller-visible output.
$ zsh -f debug-demo.zsh report item=report
-f starts Zsh with normal user startup files skipped. A packaged /etc/zshenv file can still run because Zsh reads that system file for every shell.
Related: How to configure Zsh startup files
- Run the script with execution tracing and save the trace stream.
$ zsh -fx debug-demo.zsh report 2>trace.log item=report
Trace output goes to standard error, so 2>trace.log keeps it separate from normal standard output.
- Inspect the trace log.
$ cat trace.log ##### snipped +debug-demo.zsh:2> emulate -L zsh +debug-demo.zsh:3> setopt err_exit +debug-demo.zsh:5> PS4='+ %N:%i: ' + debug-demo.zsh:12: show_item report + show_item:1: local name=report + show_item:2: print -r -- 'item=report'
The snipped line represents package-specific /etc/zshenv startup trace output. The remaining lines show the script entry, the function call site, and the commands executed inside show_item.
- Remove the trace log and sample script after review.
$ rm trace.log debug-demo.zsh
Do not commit trace logs or paste them into tickets until sensitive values have been removed.
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.