Amending PATH (something arcane that exists in “system variables” on windows that I had always been a bit afraid of) allows you to call scripts in a particular directory from anywhere on your system. All the programs/functionaly that normally work in the terminal (cd, ls, mkdir, python etc.) have to be placed somewhere that the terminal can find them. Otherwise you’re just shouting into the void and computer says no, I don’t know what that is.

On MacOS and Linux (and probably windows too), the PATH variable is actually incredibly simple, and can be easily and safely edited. In this example, I edit the PATH to add my crypto-prices directory, so when I call the bash function crypto.sh, the shell knows what I’m talking about.

Step 1. Editing the PATH Variable

Edit the PATH in ~/.zshrc:

open ~/.zshrc

Add the line export PATH="PATH:$HOME/Documents/Code/scratch/cryptoprices somewhere in .zshrc. This defines a new PATH, but to stop the previous PATH from being overwritten you must prepend the path to your new directory with PATH. Alternatively you can add your new directory to the end of a long PATH string, deliminated by :, with $PATH at the end, e.g.:

export PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/homebrew/bin:/opt/homebrew/opt/python@3.12/libexec/bin:$HOME/code/scratch/crypto-prices:$PATH"

which lists in order all the directories the shell will look for programs/commands entered into the terminal.

Bash and Python Files of Interest

I have three scripts and one venv/ directory in $HOME/code/scratch/crypto-prices directory of interest here ($HOME expands to the user’s home directory):

  1. crypto.sh - bash script that activates the venv, checks the script arguments, determines which python file to run and runs it with user arguments
  2. simple.py - python script that prints the current crypto prices for BTC, ETH, LINK, DOT, ADA using the Binance API
  3. complete.py - does the same as simple.py but with added functionality for calculating wallet amounts and printing the all-time-highs
  4. venv/ - directory storing the virtual environment in which the python library requests is installed.

The full, heavily commented, crypto.sh script is as follows:

#!/bin/bash

# The first line, the "shebang," tells the system which interpreter to use for the script.

# Set the path to the virtual environment directory.
# "VENV_PATH" is a variable storing the full path to the virtual environment.
VENV_PATH="$HOME/code/scratch/crypto-prices/venv"

# Check if the virtual environment exists by testing if the "activate" script is present.
# "-f" checks if a file exists and is a regular file (not a directory).
if [ -f "$VENV_PATH/bin/activate" ]; then
    # "source" runs the file in the current shell, activating the virtual environment
    source "$VENV_PATH/bin/activate"
else
    # If the file doesn't exist, print an error message and exit the script with a status of 1 (indicating failure).
    echo "Error: Virtual environment not found at $VENV_PATH"
    exit 1
fi

# Check if at least one argument is provided when running the script.
# "$#" represents the number of arguments passed to the script. "-lt 1" means "less than 1".
if [ "$#" -lt 1 ]; then
    # If no arguments are provided, print usage instructions to help the user.
    # "$0" refers to the name of the script itself (e.g., ./crypto.sh).
    echo "Usage: $0 <script_name> [script_args]"
    echo "Available scripts: simple, complete"
    exit 1
fi

# Store the first argument (the script name) in the variable "SCRIPT_NAME".
# "$1" is the first positional argument passed to the script.
SCRIPT_NAME=$1

# "shift" removes the first argument from the list of positional arguments ($1), shifting all others down.
shift

# Here "$@" (all arguments as a single space-separated string) will only additional script arguments (if any).
# Store the remaining arguments (after removing the first) in the variable "SCRIPT_ARGS".
SCRIPT_ARGS="$@"

# Use a "case" statement to map the script name provided by the user to the actual Python script file.
# The "case" construct is like a switch statement in other languages.
case $SCRIPT_NAME in
    # If the user specifies "simple" as the script name, set "SCRIPT_PATH" to the corresponding file path.
    simple)
        SCRIPT_PATH="$HOME/code/scratch/crypto-prices/simple.py"
        ;;
    # If the user specifies "complete," set "SCRIPT_PATH" to a different file path.
    complete)
        SCRIPT_PATH="$HOME/code/scratch/crypto-prices/complete.py"
        ;;
    # If the script name doesn't match any of the predefined options, handle it as an error.
    *)
        # Print an error message and remind the user of the available options.
        echo "Unknown script name: $SCRIPT_NAME"
        echo "Available scripts: simple, complete"
        exit 1
        ;;
esac

# Run the selected Python script using the Python interpreter from the virtual environment.
# "$SCRIPT_PATH" is the full path to the Python script determined earlier.
# "$SCRIPT_ARGS" contains any additional arguments passed by the user.
python "$SCRIPT_PATH" $SCRIPT_ARGS

# Deactivate the virtual environment to restore the shell's environment to its previous state.
deactivate

I can now run the simple and complete scripts globally:

$ crypto.sh complete -ath

--- All-Time Highs ---
Symbol              ATH  Current Price Change from ATH (%)
----------------------------------------------------------
BTC          $108353.00     $104199.99              -3.83%
ETH            $4868.00       $3272.49             -32.78%
LINK             $53.00         $23.71             -55.26%
DOT              $55.09          $6.92             -87.44%
ADA               $3.10          $1.07             -65.62%