Skip to content

Instantly share code, notes, and snippets.

@yeiichi
Last active August 10, 2025 13:27
Show Gist options
  • Select an option

  • Save yeiichi/1f38bb8d124c2d866950eb6eae522ad3 to your computer and use it in GitHub Desktop.

Select an option

Save yeiichi/1f38bb8d124c2d866950eb6eae522ad3 to your computer and use it in GitHub Desktop.
Bash wrapper to run a Python script using a virtual environment.
#!/usr/bin/env bash
# shellcheck shell=bash
# Bash wrapper to run Python scripts using a virtual environment.
set -Eeuo pipefail
# Constants
readonly HOME_DIR="${HOME}"
readonly VENV_DIR="${HOME_DIR}/path/to/venv"
readonly VENV_BIN="${VENV_DIR}/bin"
readonly ACTIVATE="${VENV_BIN}/activate"
readonly PYTHON_BIN="${VENV_BIN}/python3"
# Default scripts to run when no arguments are provided.
readonly -a SCRIPT_PATHS=(
"${HOME_DIR}/path/to/repo/script_1.py"
"${HOME_DIR}/path/to/repo/script_2.py"
"${HOME_DIR}/path/to/repo/script_3.py"
)
# Google Cloud related: Explicitly export vars for crontab environment
export GOOGLE_CLOUD_PROJECT="<PROJECT_ID>"
export CREDENTIALS_FILE_PATH="${HOME_DIR}/path/to/<SERVICE_ACCOUNT_KEY>.json"
export GOOGLE_APPLICATION_CREDENTIALS="${HOME_DIR}/path/to/<ADC>.json"
log() {
# Usage: log "message"
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >&2
}
die() {
# Usage: die "message"
log "Error: $*"
exit 1
}
validate_venv() {
[[ -f "${ACTIVATE}" ]] || die "Virtual environment activate script not found at ${ACTIVATE}"
[[ -x "${PYTHON_BIN}" ]] || die "Python interpreter not found/executable at ${PYTHON_BIN}"
}
resolve_scripts() {
# If script paths were passed as arguments, use them; otherwise use defaults.
# Prints resolved list (one per line) to stdout.
if [[ "$#" -gt 0 ]]; then
for s in "$@"; do
printf '%s\n' "$s"
done
else
for s in "${SCRIPT_PATHS[@]}"; do
printf '%s\n' "$s"
done
fi
}
validate_scripts_exist() {
# Usage: validate_scripts_exist <script1> <script2> ...
for script in "$@"; do
[[ -f "${script}" ]] || die "Python script not found at ${script}"
done
}
run_scripts() {
# Usage: run_scripts <script1> <script2> ...
# Prefer executing via the venv's python directly (no need to source),
# which avoids leaking environment changes to the caller.
for script in "$@"; do
log "Running: ${script}"
"${PYTHON_BIN}" "${script}" || die "Failed to execute script ${script}"
done
}
main() {
validate_venv
# Collect scripts from args or default list
mapfile -t scripts < <(resolve_scripts "$@")
validate_scripts_exist "${scripts[@]}"
# Optionally activate if scripts rely on environment hooks; not required for running via PYTHON_BIN.
# shellcheck disable=SC1090
source "${ACTIVATE}" || die "Failed to activate virtual environment at ${VENV_DIR}"
run_scripts "${scripts[@]}"
log "All scripts completed successfully."
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment