Writing safe scripts: testing, dry runs, logging and secrets
In short: A script can make the same mistake on a hundred devices in a minute. Make scripts safe by linting them, testing in a lab, offering a dry-run mode, limiting scope, logging every action and keeping secrets out of code.
Why safety matters more in automation
A typing error at the command line affects one device. The same error inside a loop affects every device in the list. Automation multiplies both good and bad changes, so scripts that change things need more care than one-off commands. The good news is that a few habits remove most of the risk.
Defensive coding
- Fail fast: in Bash use
set -euo pipefail; in PowerShell set$ErrorActionPreference = 'Stop'and usetry/catch; in Python let exceptions surface rather than hiding them. - Validate input: check that an IP address is valid, a file exists and a list is not empty before you act on it.
- Limit scope: accept a target list as an argument rather than "all devices"; process a small batch first.
- Be idempotent: running the script twice should leave things the same as running it once, for example by checking whether a VLAN exists before creating it.
- Lint: run
shellcheckfor Bash,PSScriptAnalyzerfor PowerShell andrufforpylintfor Python.
Dry runs and confirmation
A dry run shows what the script would do without doing it. Build one in from the start.
#!/usr/bin/env bash
set -euo pipefail
DRY_RUN=1
[[ "${1:-}" == "--apply" ]] && DRY_RUN=0
run() {
if (( DRY_RUN )); then echo "DRY-RUN: $*"; else "$@"; fi
}
for f in /var/backups/net/*.cfg; do
[[ -e "$f" ]] || continue
if [[ $(find "$f" -mtime +90) ]]; then
run rm -- "$f"
fi
done
(( DRY_RUN )) && echo "Nothing deleted. Re-run with --apply to make changes."
Making the safe mode the default means a mistyped command does no harm. PowerShell gives you this for free: add [CmdletBinding(SupportsShouldProcess)] to a script and wrap changes in if ($PSCmdlet.ShouldProcess($target)) { ... } to get -WhatIf and -Confirm support.
Logging
Every run that changes something should leave a record: when it ran, who ran it, which targets it touched and what happened. Write timestamped lines to a log file or syslog, and use Start-Transcript in PowerShell or the logging module in Python. Logs help you troubleshoot, prove what changed during an audit and roll back accurately.
import logging
logging.basicConfig(filename="vlan_push.log", level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s")
logging.info("Adding VLAN 30 to %s", "sw-access-01")
Keeping secrets out of scripts
import os, getpass
user = os.environ.get("NET_USER", "netops")
password = os.environ.get("NET_PASS") or getpass.getpass("Device password: ")
For shared or scheduled automation, use a proper secret store such as HashiCorp Vault, Azure Key Vault, AWS Secrets Manager or Ansible Vault, and give the script an identity with read access to only the secrets it needs. Never print secrets in logs, and add credential files to .gitignore.
- Test in a lab or on a single non-critical device.
- Run the dry run and read the planned changes.
- Apply to a small batch during an approved change window.
- Check the logs and results, then continue with the rest.
Tip: treat automation scripts like any other production change: peer review, change approval and a written rollback plan.
Practice
Why make dry-run the default mode?
What does idempotent mean for a script?
Name two safe ways to give a script a password.
Key takeaways
- Automation multiplies mistakes, so build safety in from the start.
- Fail fast, validate input, limit scope and lint your code.
- Default to a dry run and require an explicit flag to apply.
- Log every change with time, user, target and result.
- Keep secrets in prompts, environment variables or a vault, never in code.
Original content written by Mohamed Fayas for FM TecHub. © 2026 FM TecHub. You're welcome to share a link to this page; please don't copy the text elsewhere without permission.