--- layout: layouts/base.njk --- # Patterns The task file from the specification is very basic. Commands can also have parameters and call each other. The following is a collection of more complex task file patterns. ## Set default parameter Fallback to a default value for a parameter. ```bash build() { local platform="amd64" if [[ -n "$1" ]]; then platform="$1" fi ``` ## Ensure parameter is not empty Check the first param and exit if it is empty. ```bash deploy() { if [[ -z "$1" ]]; then echo "\$1 is empty."; exit; fi ``` ## Prompt for input Use `read` to ask for inputs. ```bash if [[ -z "$2" ]]; then read -p 'Enter the task description: ' task_description else task_description="$2" fi ``` ## Static env vars These vars are exported from the script and used in external tools. ```bash CONFIGURATION_FILE=${CONFIGURATION_FILE:=file.conf} GIT_BRANCH=$(git symbolic-ref --short -q HEAD) ``` ## Updateable vars This variables can be configured from the environment, but is used only internally. ``` container_engine=${CONTAINER_ENGINE:=docker} compose_command=${COMPOSE_COMMAND:=docker compose} ``` ## Conditional vars Depending on the value of another var, this vars value changes. ```bash if [[ "$container_engine" == "podman" ]]; then compose_command="podman-compose" fi ``` ## Template with env vars Create a parameterized file from a template. Requires `envsubst`. ```bash template-with-env() { echo "Template $CONFIGURATION_FILE" export CONFIGURATION_1 export CONFIGURATION_2=${CONFIGURATION_2:=value} envsubst < 'file.conf.template' > "$CONFIGURATION_FILE" } ``` ## Create Python virtual env Initialize Python virtual env with uv. ```bash init-venv() { if [[ ! -d ".venv$GIT_BRANCH" ]]; then echo "Init .venv$GIT_BRANCH with $(uv --version)." uv venv ".venv$GIT_BRANCH" fi } ``` ## Call a Python script Run a Python script. ```bash generate-password-hash() { if [[ -z "$1" ]]; then echo "\$1 is empty."; exit; fi export PASSWORD_PLAIN="$1" bin/password-hash } ``` The Python script: `bin/password-hash` ```python #!/usr/bin/env python3 import os from passlib.context import CryptContext crypt_context = CryptContext(schemes=['pbkdf2_sha512', 'plaintext'], deprecated=['plaintext']) password = os.environ.get('PASSWORD_PLAIN') print(crypt_context.hash(password)) ``` ## Command with named parameters Assuming you have a `docker-compose.yml` and would like to start selected or all containers. ```bash start() { if [[ "$1" =~ 'db' ]]; then docker compose up -d db fi if [[ "$1" =~ 'admin' ]]; then docker compose up -d admin echo 'Open http://localhost:8000 url in your browser.' fi if [[ "$1" =~ 'odoo' ]]; then docker compose up -d odoo echo 'Open http://localhost:8069 url in your browser.' fi if [[ "$1" =~ 'mail' ]]; then docker compose up -d mail echo 'Open http://localhost:8025 url in your browser.' fi } ``` Start all containers with `task start` and selected with `task start db,admin`. ## Run commands in container Use `docker exec -i` to run commands in a container. ```bash drop-db() { local database="$1" if [[ -z "$database" ]]; then database="example" fi docker exec -i db psql "postgres://odoo:odoo@localhost:5432/postgres" -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '$database';" docker exec -i db psql "postgres://odoo:odoo@localhost:5432/postgres" -c "DROP DATABASE \"$database\";" } ``` ## Loop over files and folders Use `for` to loop over file, folders or arrays. ```bash render() { echo "Update index.html for all folders" for folder in ./*; do if [[ -f "$folder/README.md" ]]; then cd "$folder" || exit md2html README.md _site/index.html cd .. || exit fi done } ``` ## Parse and convert a file With this function you can split a file into multiple parts whenever a specific keyword is matching. In this example the keyword is `!vault`. ```bash convert-vault-file() { local file_path="$1" local temp_file=$(mktemp) local temp_part_file=$(mktemp) local write_finished=false local current_key="" while IFS= read -r line; do # Check for keyword if [[ "$line" =~ "!vault" ]]; then # Process previous vault entry if it exists if [[ "$write_finished" = true ]] && [[ -n "$current_key" ]] && [[ -s "$temp_part_file" ]]; then # Decrypt part file and write to assemble file ansible-vault decrypt "$temp_part_file" local value=$(cat "$temp_part_file") echo "$current_key: $value" >> "$temp_file" fi # Set up for new vault entry current_key=$(echo "$line" | cut -d':' -f1) # Clear the file : > "$temp_part_file" # Flag as ready to write write_finished=true else if [[ "$write_finished" = true ]]; then # Pipe into part file echo "$line" >> "$temp_part_file" fi done done < "$file_path" # Process the final vault entry if [[ "$write_finished" = true ]] && [[ -n "$current_key" ]] && [[ -s "$temp_part_file" ]]; then ansible-vault decrypt "$temp_part_file" local value=$(cat "$temp_part_file") echo "$current_key: $value" >> "$temp_file" fi # Output assembled file cat "$temp_file" # Cleanup temp files rm -f "$temp_file" rm -f "$temp_part_file" } ``` ## Process data This pattern tries to match the worklow of a jupyter notebook. Create a folder with the data processing scripts: ``` scripts ├── 01_import-mail-data ├── 02_transform-mail-data └── 03_export-mail-data ``` The following function lists the scripts and asks for the number of scripts to run. Every script until the entered number is concatenated and executed. ```bash process-data() { while true; do local scripts=($(ls scripts)) echo -e "Available scripts:\n" for index in "${!scripts[@]}"; do echo "$((index+1)): ${scripts[$index]}" done echo "" read -p "Enter the number of scripts to run (or 'q' to quit): " user_input if [[ "$user_input" = "q" ]]; then break fi local temp_file=$(mktemp) for index in "${!scripts[@]}"; do if (( $user_input >= $((index+1)) )); then cat "scripts/${scripts[$index]}" >> "$temp_file" echo >> "$temp_file" fi done clear echo -e "\nPython Output:\n\n---" python "$temp_file" local exit_code=$? echo -e "---\n" echo -e "Exit code: $exit_code\n" rm "$temp_file" done } ``` ## Check commands Add the following statements to check if the required commands is available: ```python if ! (command -v grep &> /dev/null && grep --version | grep -q GNU) && [[ "$OS_RELEASE" =~ ^(Darwin)$ ]]; then echo 'Error: On Darwin systems, GNU grep must be installed.' exit 1 fi if ! command -v envsubst &> /dev/null; then echo 'Error: '\''envsubst'\'' is not available. Install '\''gettext'\'' package.' exit 1 fi ```