Exiting a Python Script Cleanly
Scripts need to finish in a way that tells the caller (or the shell) whether they succeeded or failed. The way you exit matters for automation, error handling, and portability.
Common Exit Patterns
1. return from main() (preferred for scripts)
The cleanest exit is to return an integer status from main() and let the if __name__ == "__main__": block pass it to sys.exit().
import sys
def main() -> int:
try:
do_work()
except FileNotFoundError as e:
print(f"Error: {e}", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())
Why raise SystemExit(...) instead of sys.exit(...)? They are functionally the same in most cases, but raise SystemExit is explicit and slightly more readable to some people. Use whichever you prefer, but be consistent.
2. sys.exit()
sys.exit() raises a SystemExit exception, which propagates up to the interpreter and stops the script. It is clean and standard.
import sys
if len(sys.argv) != 2:
print("Usage: script.py <file>")
sys.exit(1)
In scripts, this is fine. In modules meant to be imported, it is usually better to raise a regular exception or return a status so the caller can decide what to do.
3. os._exit()
os._exit() terminates the process immediately without running cleanup handlers (like finally blocks, __exit__ methods from context managers, or atexit functions). This is usually only useful when you want a guaranteed, hard exit from a child process, e.g., after os.fork().
Avoid os._exit() in normal scripts. You risk leaving resources, temporary files, or sockets in a bad state.
import os
os._exit(1) # Use only in special cases, not normal cleanup paths.
4. quit() and exit()
These are built-in helpers intended for the interactive REPL. They work in scripts too, but they are not recommended for production code because they may not be available in all environments (for example, some embedded Python interpreters may not define them). Always use sys.exit() or raise SystemExit instead.
Exit Codes
0= success1= general error2= misuse of command (e.g., bad arguments)
Other non-zero values are possible, but unless you are writing for a specific tool or pipeline that expects specific codes, stick to 0 and 1. Some teams use 2 for argument errors, but consistency matters more than the exact number.
Best Practices for Security Scripts
- Return from
main(), then exit once at the bottom of the script. - Print errors to
sys.stderr, notsys.stdout, so they do not pollute output piped to other tools. - Avoid
os._exit()unless you have a specific reason to bypass cleanup. - Avoid
quit()/exit()in scripts. - Catch expected errors near where they can happen, but let unexpected errors propagate so they produce a traceback.
Example
#!/usr/bin/env python3
import sys
def process_file(path):
with open(path, "r") as f:
return f.read()
def main() -> int:
if len(sys.argv) != 2:
print("Usage: script.py <file>", file=sys.stderr)
return 1
try:
data = process_file(sys.argv[1])
except FileNotFoundError as e:
print(f"Error: {e}", file=sys.stderr)
return 1
print(data)
return 0
if __name__ == "__main__":
raise SystemExit(main())
Related Concepts
- Python Essentials for Security Scripting — clean script structure and file I/O.
- Python Error Handling — catching and reporting errors defensively.
- Python Fundamentals for Security Tasks — the current lesson with practice exercises.