mcpField
W
which

which

github.com/petr-korobeinikovDevelopment171 installsUpdated 3d ago
Repository

About which

which is a shell-focused agent skill that recommends using POSIX `command -v` instead of `which` for command checks and resolution. It explains portability differences, substitutions, and cases that need explicit path handling.

Key features

  1. 01Replaces command checks with `command -v`
  2. 02Explains shell portability differences
  3. 03Provides silent and error-reporting checks
  4. 04Distinguishes command names from file paths
  5. 05Covers listing executable matches in `$PATH`

Use cases

  1. 01Checking whether a command is available
  2. 02Resolving an external executable path
  3. 03Reviewing shell snippets and build instructions

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/petr-korobeinikov/skills --skill which

After installing, type/whichin Claude Code to confirm it loaded.

Details

From the project README

which

Never use which to test for a command or to print its path. Use command -v instead.

Why

  • command -v is a POSIX-mandated shell builtin (bash, zsh, dash, ash, ksh, busybox sh). which is an external program and may be missing, aliased, or replaced by a shell function.
  • which behavior varies across implementations: BSD, GNU, Debian's shell-script which, zsh's builtin, and busybox's applet differ in flags, output format, and exit codes.
  • command -v reports shell builtins, functions, and aliases — not just files on $PATH — which is what callers usually actually want.
  • command -v returns a reliable non-zero exit code when the name is not found, with no output on either stdout or stderr.

Direct substitutions

Don'tDo
which foocommand -v foo
which foo >/dev/null 2>&1command -v foo >/dev/null
if which foo; then …if command -v foo >/dev/null; then …

The 2>&1 is dropped because command -v writes nothing to stderr when the name is not found.

Two cases have no one-liner replacement and need the idioms below:

  • path=$(which foo) — command -v may return a builtin name or alias string, not a filesystem path.
  • which -a foo — command -v resolves only the first hit.

Idioms

Existence check (silent):

if command -v foo >/dev/null; then
  # ...
fi

Existence check with a clear failure message:

command -v foo >/dev/null || {
  echo "foo is required but not installed" >&2
  exit 1
}

Resolve a filesystem path:

foo_path=$(command -v foo) || {
  echo "foo not found" >&2
  exit 1
}
case "$foo_path" in
  /*) ;;
  *)  echo "foo is not an external binary (got: $foo_path)" >&2
      exit 1 ;;
esac

command -v may output:

  • /usr/bin/foo — external file on $PATH;
  • foo — shell builtin or function;
  • alias foo='…' — alias.

The /* guard accepts only the first form.

Edge cases

  • Listing every match on $PATH (which -a) has no single-call POSIX equivalent. Walk $PATH explicitly:

    ( IFS=:
      for dir in $PATH; do
        candidate=${dir:-.}/foo
        [ -f "$candidate" ] && [ -x "$candidate" ] && printf '%s\n' "$candidate"
      done )
    

    [ -f ] together with [ -x ] filters to regular executable files — matching what which -a reports; [ -x ] alone would also accept directories with the execute bit.

    In bash and zsh, type -a foo is shorter but not portable.

  • Makefiles. Recipes run through $(SHELL), default /bin/sh. If a project sets SHELL to a non-POSIX shell, pin recipes back to a POSIX shell — e.g. SHELL := /bin/sh — before relying on command -v.

Scope

This rule applies to anything written or run as POSIX shell: Bash tool invocations, snippets in SKILL.md / README.md, scripts under scripts/, Makefiles, CI configs, Dockerfiles, install instructions, suggested one-liners.

Out of scope: fish, and tooling that wraps which internally (CMake's find_program, npm bin, etc.).

If you encounter a which call while editing a file, replace it with the equivalent command -v form as part of the same edit.

Comments

Markdown supported