Context-Aware Shell Aliases: One Shortcut, Many Projects

Aliases turn long, forgettable commands into muscle-memory shortcuts. They’re especially helpful when you’re juggling multiple projects or jumping back into a codebase you haven’t touched in months. They save keystrokes, reduce context switching, and keep you in the flow. The problem with simple shell aliases is that common tasks — running tests, building docs, or starting a dev server — often require different commands depending on your project or tech stack.

Instead of learning one command per task, you’re stuck remembering a different one for each project. That causes a lot of mental overhead. The solution is to define shortcuts that adapt to your context.

The Main Tradeoff: Local vs Global

Before building your time-saving automations, ask yourself this question:

Should this command be shared with the team, or just for me?

The answer reveals two different approaches:

Local (Project-Centric) Global (Developer-Centric)
Lives in repo Lives in your dotfiles
Shared with teammates Private and portable
Versioned with the code Stays the same across projects
Great for onboarding Great for speed and customization

Once you know your intent, the implementation follows naturally.

Local Commands: Shared, Discoverable, Versioned

Local commands are defined inside the repo and checked into version control. This makes them ideal for:

  • Team-wide consistency
  • Onboarding new devs
  • CI or automated scripts

Common approaches for project-local commands include executable scripts and Makefiles:

# projectA/bin/test
npm test --silent "$@"
# projectA/Makefile
test:
	npm test --silent

Pros

  • Discoverable – New devs can browse commands and have visibility when new ones are added.
  • Versioned – Updates are tracked in Git.
  • Usable in CI – Works the same in automation and local environments.

Cons

  • Duplication – May require the same code across multiple projects.
  • Extra ceremony – Requires touching repo files even for one-offs.

Global Commands: Fast, Flexible, Private

Global commands live in your shell config or personal tools. They’re useful when you want speed and flexibility, without involving the rest of the team.

Example: Context‑Aware Shell Function

# ~/.zshrc
t() {
  case "$(basename "$PWD")" in
    projectA)
      npm test "$@"
      ;;
    projectB)
      pytest "$@"
      ;;
    *)
      echo "No test command configured for this project"
      ;;
  esac
}

Now t runs tests for all of the projects you’ve configured it to work with.

Pros

  • Fast to iterate – No PR needed to tweak behavior.
  • Highly personalized – Tailor logic to your habits.
  • Portable – Works across all your projects.

Cons

  • Not versioned per project – Can drift from current project needs.
  • Can grow messy – A few smart functions can turn into a monolith.

Blended Strategy: The Best of Both Worlds

Some workflows benefit from a mix of local and global automations.

  • Local for commands everyone should use (e.g. make test).
  • Global for glue code and personal wrappers (e.g. a smart t() function).

For example, you might have make test defined in some repos while others use stack-specific commands. You can define a global function that finds and runs the correct command based on the context:

# ~/.zshrc
t() {
  if [ -f Makefile ]; then
    make test "$@"
  elif [ -f package.json ]; then
    npm test "$@"
  elif [ -f pytest.ini ] || [ -d tests ]; then
    pytest "$@"
  else
    echo "No recognizable test command"
  fi
}

This gives you muscle memory (t) with team consistency under the hood.

Conclusion

Project-local commands are great for team alignment and shared tooling. Global commands shine when you want fast iteration and personal efficiency. When combined, they let you balance consistency with convenience.

Start simple: pick one repetitive task, decide where it belongs, and give it a single memorable shortcut. That small step can clear a lot of mental clutter and make jumping between projects that much easier.

Conversation
  • Brian Oxley says:

    I find https://direnv.net/ a helpful tool for setting up the shell per-directory.

    An example trick is to bring a `.env` file, for example in an NPM project, into the shell with a `.envrc` file such as:
    “`
    set -o allexport
    source .env
    set +allexport
    “`

  • Join the conversation

    Your email address will not be published. Required fields are marked *