A subshell, written ( commands ) or produced by backgrounding, runs its commands in a child of the current shell. Any changes to variables, the working directory, or shell options do not affect the parent once the subshell exits, which makes subshells ideal for isolating a temporary cd — as in (cd /tmp && cmd) — or for short-lived option changes such as (set -e; cmd). Commands can also be run in the background by appending &; the shell prints a job ID and PID, and synchronization is achieved with wait %1 (a job specifier) or wait $pid (a specific PID). The trap builtin registers code to run when the shell receives a signal or exits, and the most common pattern is trap 'cleanup' EXIT, which fires the cleanup code on any termination path — normal exit, error, or signal, with the lone exception of SIGKILL, which cannot be caught. Pairing trap 'rm -f "\(tmpfile"' EXIT with tmp=\)(mktemp) guarantees reliable cleanup.
Many practical automation tasks involve combining Bash with external commands. find ... -print0 | xargs -0 -n 1 cmd processes filenames safely even when they contain spaces or newlines, and find /path -type f -mtime +7 -delete deletes files older than seven days. The timeout command from coreutils, invoked as timeout 30s cmd, runs a command with a hard deadline and returns non-zero on expiry. To time how long a block takes, the simplest in-script trick is SECONDS=0; cmd; echo "$SECONDS", which gives elapsed seconds in Bash, or use the time builtin for the more detailed real/user/sys breakdown. To schedule periodic execution, crontab -e with an entry like 0 3 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1 runs nightly at 03:00; remember that cron runs scripts with a minimal PATH and environment, so any variables the script depends on must be set explicitly inside the script itself.
To catch real bugs, develop with the linter shellcheck (shellcheck.net) in CI, validate syntax with bash -n script.sh before running, and use set -x (or run as bash -x script.sh) to trace each command just before it executes. Setting PS4='+ \({BASH_SOURCE}:\){LINENO}: ' adds source-file and line-number prefixes to trace output, which is invaluable in long scripts. BASH_ENV is a variable that, when exported, points non-interactive Bash scripts at a startup file to source — useful for ensuring every script on a system runs with the same strict-mode preamble. By default aliases are not expanded in non-interactive scripts; shopt -s expand_aliases turns that on when needed. To log every line of script output with a timestamp, exec > >(while IFS= read -r line; do printf '%s %s\n' "\((date '+%F %T')" "\)line"; done) 2>&1 prepends each line with the current time, or you can pipe everything through ts '[%F %T]' from moreutils.
Finally, a short list of pitfalls that defeat even experienced scripters: never parse ls output, because for f in $(ls) mangles filenames containing spaces or newlines through word splitting and glob expansion — prefer globs (for f in *.txt) or find -print0 with read -d ''; never pass a huge glob directly to a command such as rm *, because Argument list too long errors result — use find ... -exec rm {} + or xargs instead; remember that NUL bytes cannot appear in Bash strings, which is precisely why find -print0 paired with read -d '' exists; and when portability matters, prefer #!/usr/bin/env bash with a runtime guard, avoid Bash-4-only features on older systems, prefer command -v to which, and prefer printf to echo for arbitrary data.