{"id":31650460,"url":"https://github.com/open-technology-foundation/whichx","last_synced_at":"2026-04-19T15:03:45.018Z","repository":{"id":317962311,"uuid":"1069519380","full_name":"Open-Technology-Foundation/whichx","owner":"Open-Technology-Foundation","description":"A robust bash implementation of the classic Unix which command with enhanced features and comprehensive error handling.","archived":false,"fork":false,"pushed_at":"2025-10-04T05:20:43.000Z","size":16,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-04T07:16:47.356Z","etag":null,"topics":["bash","which"],"latest_commit_sha":null,"homepage":"https://yatti.id/","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Open-Technology-Foundation.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-04T04:58:40.000Z","updated_at":"2025-10-04T05:23:31.000Z","dependencies_parsed_at":"2025-10-04T07:16:54.678Z","dependency_job_id":"db455c95-afb7-4bcc-bcb1-7d9a19facf45","html_url":"https://github.com/Open-Technology-Foundation/whichx","commit_stats":null,"previous_names":["open-technology-foundation/whichx"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/Open-Technology-Foundation/whichx","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Open-Technology-Foundation%2Fwhichx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Open-Technology-Foundation%2Fwhichx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Open-Technology-Foundation%2Fwhichx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Open-Technology-Foundation%2Fwhichx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Open-Technology-Foundation","download_url":"https://codeload.github.com/Open-Technology-Foundation/whichx/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Open-Technology-Foundation%2Fwhichx/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278742889,"owners_count":26037915,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-07T02:00:06.786Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bash","which"],"created_at":"2025-10-07T08:29:48.699Z","updated_at":"2026-04-19T15:03:44.977Z","avatar_url":"https://github.com/Open-Technology-Foundation.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# which\n\nA robust, POSIX-compliant `which` replacement for Bash.\n\n[![License: GPL-3.0](https://img.shields.io/badge/License-GPL%203.0-blue.svg)](LICENSE)\n[![Bash 4.4+](https://img.shields.io/badge/Bash-4.4%2B-green.svg)](https://www.gnu.org/software/bash/)\n[![Tests: 69 passing](https://img.shields.io/badge/Tests-69%20passing-brightgreen.svg)](tests/)\n\n**Requires Bash 4.4+** — runs on any OS with a compatible shell.\n\n## TL;DR\n\n```bash\ngit clone https://github.com/Open-Technology-Foundation/whichx.git\ncd whichx \u0026\u0026 sudo make install\nwhich -a python3\n```\n\n## Why Replace which?\n\nThe standard `which` command varies significantly across Unix systems:\n\n| Issue | Debian | macOS | Busybox |\n|-------|--------|-------|---------|\n| Exit code (no args) | 1 | 0 | 0 |\n| Exit code (bad option) | 2 | 1 | 1 |\n| `-s` silent mode | No | Yes | No |\n| Long options | No | No | No |\n\nThis implementation provides:\n\n- **Consistent exit codes**: 0 (found), 1 (not found), 2 (no args), 22 (EINVAL)\n- **POSIX PATH compliance**: Correct handling of empty PATH elements\n- **Dual-mode execution**: Run as script OR source as function (12x faster)\n- **Canonical resolution**: Follow symlinks to actual executables\n\n## Installation\n\n### Quick Install\n\n```bash\ngit clone https://github.com/Open-Technology-Foundation/whichx.git\ncd whichx \u0026\u0026 sudo make install\n```\n\nInstalls to `/usr/local/bin/which` with man page.\n\n### Custom Prefix\n\n```bash\nsudo make install PREFIX=/usr/bin\n```\n\n### Sourceable Install (Recommended for Interactive Use)\n\n```bash\nsudo make install-sourceable\n```\n\nThis copies the script to `/etc/profile.d/which.sh`. New shells will have `which()` as a shell function instead of calling an external process.\n\n**Why is this faster?** Each external command invocation requires fork() + exec() + bash interpreter startup (~1.6ms). A shell function runs in-process (~0.13ms). That's **12x faster**.\n\n**Note:** `/etc/profile.d/` is sourced by login shells via `/etc/profile`. Most terminal emulators start non-login shells, which source `~/.bashrc` instead. If `which` isn't available in new terminals, either:\n- Add `source /etc/profile.d/which.sh` to your `~/.bashrc`, or\n- Configure your terminal to start login shells\n\n### Uninstall\n\n```bash\nsudo make uninstall\nsudo make uninstall-sourceable\n```\n\n## Usage\n\n```\nwhich [OPTIONS] [--] command ...\n```\n\n### Options\n\n| Option | Long | Description |\n|--------|------|-------------|\n| `-a` | `--all` | Print all matches in PATH, not just first |\n| `-c` | `--canonical` | Resolve symlinks via realpath/readlink |\n| `-q` | `--quiet` | No output, exit code only |\n| `-s` | `--silent` | Alias for `-q` |\n| `-V` | `--version` | Print version and exit |\n| `-h` | `--help` | Print help and exit |\n\nOptions can be combined: `-ac` equals `-a -c`\n\n### Exit Codes\n\n| Code | Constant | Meaning |\n|------|----------|---------|\n| 0 | `EXIT_SUCCESS` | All commands found |\n| 1 | `EXIT_FAILURE` | One or more not found |\n| 2 | `EXIT_USAGE` | No arguments provided |\n| 22 | `EINVAL` | Invalid option |\n\n### Examples\n\n```bash\nwhich ls                      # /usr/bin/ls\nwhich -a python3              # All python3 in PATH\nwhich -c /usr/bin/python3     # Resolves to /usr/bin/python3.12\nwhich -q docker \u0026\u0026 echo \"ok\"  # Silent check\nwhich ls cat grep             # Multiple commands\nwhich -- -weird-name          # Command starting with hyphen\n```\n\n## Architecture\n\n### Dual-Mode Design\n\nThe script works both as an executable and as a sourceable function:\n\n```bash\n# As executable (subprocess)\n./which ls\n\n# As sourced function (in-process)\nsource ./which\nwhich ls\n```\n\nThis is achieved with the `BASH_SOURCE` guard:\n\n```bash\nwhich() {\n  # ... function body ...\n}\ndeclare -fx which\n\n[[ \"${BASH_SOURCE[0]}\" == \"$0\" ]] || return 0\n\n# --- Script mode (direct execution only) ---\nset -euo pipefail\nshopt -s inherit_errexit\n\nwhich_help() { ... }\nwhich \"$@\"\n```\n\nWhen sourced, `BASH_SOURCE[0]` differs from `$0`, so `return 0` exits early after defining the function. When executed, they match, so the script continues to run `which \"$@\"`.\n\n### Strict Mode Without Pollution\n\nTraditional bash scripts use strict mode at the top, but this would pollute the sourcing shell's environment. This script solves that by placing strict mode **after** the BASH_SOURCE guard:\n\n- **Sourced**: Returns before reaching `set -euo pipefail` — caller's shell unaffected\n- **Executed**: Strict mode applies only to the subprocess\n\n### Function Structure\n\nAll logic lives in a single `which()` function with:\n\n- All variables declared `local` (no namespace pollution)\n- `return` instead of `exit` (function-safe)\n- Inline PATH parsing (no helper functions to leak)\n- Conditional help: brief when sourced, full when executed\n\n### PATH Parsing\n\n```bash\npath_str=${PATH:-}\n[[ $path_str == *: ]] \u0026\u0026 path_str+='.'  # Trailing colon = cwd\nIFS=':' read -ra path_dirs \u003c\u003c\u003c \"$path_str\"\n\nfor path in \"${path_dirs[@]}\"; do\n  [[ -n $path ]] || path='.'  # Empty element = cwd\n  # ...\ndone\n```\n\nThe `read -ra` with herestring is a common idiom, but it drops trailing empty elements. The `*:` check handles trailing colons explicitly.\n\n## POSIX Compliance\n\nPer POSIX, an empty element in PATH means the current directory. Many `which` implementations get this wrong.\n\n```bash\n# Leading colon = cwd searched first\nPATH=\":/usr/bin\" which ./script\n\n# Trailing colon = cwd searched last\nPATH=\"/usr/bin:\" which ./script\n\n# Double colon = cwd searched in middle\nPATH=\"/usr/bin::/usr/local/bin\" which ./script\n```\n\nThis matters for security audits and understanding command resolution.\n\n## Performance\n\n### Methodology\n\nBenchmarks run each command 1000 times, measuring wall-clock time with nanosecond precision.\n\n### Results\n\n| Test | which (subprocess) | which (sourced) | old.which (dash) |\n|------|-------------------|-----------------|------------------|\n| Single lookup | ~600 ops/s | ~7,500 ops/s | ~1,200 ops/s |\n| Large PATH (50 dirs) | ~500 ops/s | ~6,000 ops/s | ~1,200 ops/s |\n| Not found | ~600 ops/s | ~7,500 ops/s | ~1,200 ops/s |\n\n### Analysis\n\n**Why is subprocess mode 2x slower than dash-based which?**\n\nBash has more startup overhead than dash. The actual PATH searching is nearly identical, but bash's interpreter initialization dominates.\n\n**Why is sourced mode 12x faster?**\n\nNo fork(), no exec(), no interpreter startup. The function runs directly in the current shell's process space.\n\n### Run Benchmarks\n\n```bash\nmake benchmark\n```\n\n## Testing\n\n### Run Tests\n\n```bash\nmake test         # shellcheck + functional tests\nmake shellcheck   # Static analysis only\nmake functional   # 69 functional tests only\n```\n\n### Test Coverage\n\n- Basic operations (find, not found, multiple targets)\n- All options (`-a`, `-c`, `-q`, `-s`, `-V`, `-h`, `--long`)\n- Combined options (`-ac`, `-qa`, `-aqs`)\n- Exit codes (0, 1, 2, 22)\n- PATH edge cases (leading/trailing/double colon, empty, nonexistent dirs)\n- Input handling (absolute path, relative path, `--` separator, hyphen commands)\n- Edge cases (non-executable, directories, symlinks, broken symlinks)\n\n### Adding Tests\n\nTests use TAP-style output. Add to `tests/test_which.sh`:\n\n```bash\nout=$(\"$WHICH\" -a python3 2\u003e\u00261); rc=$?\nassert_exit 0 $rc \"description\"\nassert_contains \"python\" \"$out\" \"description\"\n```\n\n## Contributing\n\n### Code Style\n\n- All variables `local` (sourceable requirement)\n- Integer variables: `local -i count=0`\n- Arrays: `local -a items=()`\n- Conditionals: `[[ ]]` never `[ ]`\n- Arithmetic: `(( ))` only\n- 2-space indentation\n- Quote all variable expansions\n- Errors to stderr: `printf \u003e\u00262`\n\n### Requirements\n\n- Must pass `shellcheck`\n- Must pass all 69 tests\n- No new dependencies\n\n### Pull Requests\n\n1. Fork the repository\n2. Create a feature branch\n3. Make changes\n4. Run `make test`\n5. Submit PR\n\n## License\n\nGPL-3.0-or-later — see [LICENSE](LICENSE)\n\n**Indonesian Open Technology Foundation**\nadmin@yatti.id\n\n## See Also\n\n- `man which` — installed man page\n- `type(1)` — bash builtin, shows aliases/functions too\n- `command -v` — POSIX way to find commands\n- `whereis(1)` — also searches man pages and source\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopen-technology-foundation%2Fwhichx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fopen-technology-foundation%2Fwhichx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopen-technology-foundation%2Fwhichx/lists"}