{"id":43598680,"url":"https://github.com/mattieb/dye","last_synced_at":"2026-04-03T02:02:15.022Z","repository":{"id":336347231,"uuid":"1051402779","full_name":"mattieb/dye","owner":"mattieb","description":"a portable and respectful color library for shell scripts","archived":false,"fork":false,"pushed_at":"2026-02-04T03:34:08.000Z","size":2583,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-04T13:28:33.289Z","etag":null,"topics":["ansi-color","ansi-colors","shell","shell-script","shell-scripting","shell-scripts","tput"],"latest_commit_sha":null,"homepage":"https://mattiebee.dev/dye","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mattieb.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","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-09-05T23:43:53.000Z","updated_at":"2026-02-04T03:32:12.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/mattieb/dye","commit_stats":null,"previous_names":["mattieb/dye"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/mattieb/dye","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mattieb%2Fdye","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mattieb%2Fdye/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mattieb%2Fdye/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mattieb%2Fdye/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mattieb","download_url":"https://codeload.github.com/mattieb/dye/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mattieb%2Fdye/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31326851,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-03T01:42:14.489Z","status":"online","status_checked_at":"2026-04-03T02:00:06.642Z","response_time":107,"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":["ansi-color","ansi-colors","shell","shell-script","shell-scripting","shell-scripts","tput"],"created_at":"2026-02-04T05:10:54.970Z","updated_at":"2026-04-03T02:02:15.017Z","avatar_url":"https://github.com/mattieb.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# [dye](https://mattiebee.dev/dye)\n\n-   [About](#about)\n-   [Usage](#usage)\n-   [Development](#development)\n\n## Demo\n\n![A screenshot of the \"demo\" script in action](./doc/demo.png)\n\n## About\n\ndye is a **portable** and **respectful** library for adding color and emphasis to the output of shell scripts.\n\nIt's portable because\n\n-   it works on many Unix systems, including macOS, Linux, and OpenBSD;\n\n-   it is written to [the POSIX shell standard](https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19), so it works in many shells that are POSIX-compatible, such as [ash and Dash](https://en.wikipedia.org/wiki/Almquist_shell), [Bash](https://www.gnu.org/software/bash/), [ksh](https://en.wikipedia.org/wiki/KornShell), and [Zsh](https://zsh.sourceforge.io);\n\n-   it uses [tput(1)](https://man.openbsd.org/tput) instead of hard-wired ANSI sequences, so it will work wherever the appropriate [terminal capabilities](https://man.openbsd.org/terminfo.5) are available, and gracefully degrade where they are not; and\n\n-   it additionally degrades gracefully if tput(1) is not available (e.g. in [a Docker alpine image](https://hub.docker.com/_/alpine) where ncurses is not installed).\n\nIt's respectful because:\n\n-   it will disable if [\"NO_COLOR\"](https://no-color.org) is set;\n\n-   it will enable if [\"CLICOLOR\"](https://bixense.com/clicolors/) is set, but only if stdout is a tty;\n\n-   it will unconditionally enable (e.g. if stdout is not a [tty](https://en.wikipedia.org/wiki/Tty_(Unix))) if \"CLICOLOR_FORCE\" is set;\n\n-   it allows script developers to choose to default to color off unless the user has opted in with environment variables; and\n\n-   it is written to only put functions and environment variables prefixed with \"dye\" or \"DYE\" into the shell's global namespace, and carefully avoids clobbering any existing shell variables during operation.\n\ndye does call [tput](https://pubs.opengroup.org/onlinepubs/9799919799/utilities/tput.html) for every terminal sequence it needs to output, so it's not screamingly fast. However, in practice, it's more than fast enough for the job I need it for: making the output of shell scripts colorful to make them easier to read and scan. If you're working with lots of color (for example, creating [ANSI art](https://en.wikipedia.org/wiki/ANSI_art)), it's probably best to stick to a solution that caches ANSI sequences and forgo the portability.\n\nI wrote dye to replace my previous project, [portable-color](https://mattiebee.dev/portable-color). portable-color was fine, but would load lots of functions into the shell's global namespace. dye has a better API, with more capabilities and conveniences.\n\n## Usage\n\n### Embedding\n\nYou can use dye in your script by copying the contents of [dye.sh](./dye.sh) into your script so that the functions are defined before you use them.\n\nThis the method I recommend. Shell scripts that I write are generally made to be self-contained, and embedding makes them very easy to download and use.\n\nA couple notes on this strategy:\n\n-   If you don't want dye's over 100 lines of code at the top of your script, wrap your main code in a function, then insert all of dye's code, and finally call your main function at the end.\n\n-   If you end up changing dye's code, consider changing the names of the dye functions and variables that end up in the shell's global namespace, to avoid conflicts with the standard dye code that may be depended on elsewhere—particularly if you're removing functionality you do not use.\n\n-   If you distribute your script with dye's code inline, you must include a copy of [the license](./LICENSE.md) in some way with your script. This specifically so others understand their rights in regard to the use of this software.\n\n### Sourcing\n\nIf you have [dye.sh](./dye.sh) available [on your PATH](https://mattiebee.io/44251/a-proposal-for-shell-libraries) (executable bit not necessary), you can load it very simply:\n\n```shell\n. dye.sh || exit 1\n```\n\nIf you include a copy alongside your script, you can also load it from a specific directory:\n\n```shell\n. ./dye.sh || exit 1\n```\n\n### Initialization\n\ndye must be initialized once before use, or no text changes will happen when you use the color or emphasis routines:\n\n```shell\ndye setup\n```\n\n\"setup\" is where dye will check things like whether stdout is a tty, whether environment variables like \"NO_COLOR\" and \"CLICOLOR\" are set, and make a decision whether or not to set the variable \"DYE_COLORS\" to the number of available colors, which the other routines will use.\n\nAn alternate mode for \"setup\" will not enable color by default, but will enable it if the user has \"CLICOLOR\" set:\n\n```shell\ndye setup default-off\n```\n\n### Templates\n\nThe best way to use dye is with its built-in templates.\n\nTemplates embed other dye commands inside a single string.\n\n```shell\ndye print \"{{green}}It's not easy being... well, you know.{{reset}}\"\n```\n\nLike [\"echo\"](https://pubs.opengroup.org/onlinepubs/9799919799/utilities/echo.html), \"dye print\" ends its output with a newline.\n(\"dye p\" is a shorthand for \"dye print\" and highly recommended.)\n\nNote that _unlike_ \"echo\" on many systems, control characters are not automatically parsed. Whatever the shell passes to \"dye print\" is only processed according to [two simple syntax rules](#syntax).\n\n```shell\ndye write \"So {{bold}}bold{{reset}}, it's not recommended \"\ndye write \"for human consumption!\"\n```\n\n\"dye write\" is the same, except it does not end its output with a newline.\n\n#### Syntax\n\nTemplates have two basic syntax rules:\n\n1.  Text between double curly braces (\"{{\" and \"}}\") is interpreted as a dye command. The curly braces are not printed.\n\n2.  A backslash (\"\\\") escapes the next character if it is either a backlash or left curly brace. The backslash itself is not printed. Useful if you want to print two left curly braces.\n\n#### Multiline strings\n\ndye commands between double curly braces, and as such can have trailing spaces added to enable multiline strings:\n\n```shell\ndye print \"We have an {{bold\n}}awful{{reset}} lot to say across {{bold\n}}many{{reset}} lines.\"\n```\n\n#### Escaping backslashes\n\nThere is one caveat to the syntax rules. If you're using double quotes, the backslash behavior can be a bit unintuitive in one case.\n\n```shell\ndye print \"a\\\\{{red}}/b\"\n```\n\nThis actually prints \"a{{red}}/b\", because the double backlash is interpreted as a single backslash by the shell before dye even sees it. dye then interprets the resulting \"\\\\{\" as escaping \"{\".\n\nEscaping both backslashes passes through \"\\\\\\\\\" to dye's engine, which in turn interprets it as an escaped single-backslash. This prints `a\\/b` with a red color change in the middle:\n\n```shell\ndye print \"a\\\\\\\\{{red}}/b\"\n```\n\nUsing single-quoted strings works around this issue, but of course sacrifices the variable interpolation you get with double-quoted strings. \n\n### Wrapping text\n\n\u003e [!NOTE]\n\u003e Wrapping text is deprecated due to the awkwardness of dealing with [resets](#resets) and the extra weirdness of [unquoted wrapping](#unquoted-wrapping), and is likely to be removed in a future major version.\n\nFor simple [color](#colors) and [emphasis](#emphases), using dye to wrap quoted text is the most convenient method.\n\n```shell\necho \"$(dye green \"It's not easy being... well, you know.\")\"\n```\n\n```shell\necho \"So $(dye bold \"bold\"), it's not recommended for human consumption\\!\"\n```\n\n#### Unquoted wrapping\n\nQuoting text is also not *strictly* necessary, but can result in the need to use many more escapes (just like it would if using \"echo\" straight up). It also means whitespace gets collapsed, so beware!\n\n```shell\necho Quotes\\? Quotes\\? $(dye italic We don\\'t need no stinking quotes\\!)\n```\n\n#### Resets\n\nWhen wrapping text, one key caveat applies: all colors and several emphases do not have a matching ending terminal sequence—they can only be turned off by sending an \"sgr0\" terminal capability to reset *all* color and emphasis.\n\ndye will send this reset sequence at the end of wrapped text for colors and select emphases, so it's best not to stack wrappers. It gets unreadable really fast, anyway, so it's better to use [manual control](#manual-control).\n\n### Manual control\n\nYou can generally use [command substitution](https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_06_03) to capture the output from manual control commands so it can all be strung together:\n\n```shell\necho \"$(dye cyan)$(dye bold)Cyan$(dye reset), $(dye magenta)$(dye bold\n    )magenta$(dye reset), and $(dye bold)white$(dye reset\n    ) ought to be enough for anybody.\"\n```\n\nUsing lots of manual control can make lines pretty long, but as you can see, you can also leverage the fact that line breaks are valid inside command substitution to break them up.\n\nIt's better (and shorter!) to use [templates](#templates), though. Capturing the output of dye works in most cases, but could break in some obscure situations.\n\n### Colors\n\nMany colors are available for use, subject to terminal support.\n\nThere's the basic ANSI color set:\n\n-   `black` (or `0`)\n-   `red` (or `1`)\n-   `green` (or `2`)\n-   `yellow` (or `3`)\n-   `blue` (or `4`)\n-   `magenta` (or `5`)\n-   `cyan` (or `6`)\n-   `white` (or `7`, or `brightgray`)\n-   `gray` (or `8`)\n-   `brightred` (or `9`)\n-   `brightgreen` (or `10`)\n-   `brightyellow` (or `11`)\n-   `brightblue` (or `12`)\n-   `brightmagenta` (or `13`)\n-   `brightcyan` (or `14`)\n-   `brightwhite` (or `15`)\n\n\"dye yellow\" will set the foreground color to yellow, for example. There are also \"fg\" and \"bg\" commands that will explicitly set the foreground or background color, respectively:\n\n```shell\ndye print '{{bg blue}}{{fg yellow}}In Ann Arbor, everything is this color.{{reset}}'\n```\n\nTurning colors off requires replacing them with a different color, or a [reset](#resets).\n\n#### High colors\n\nSome terminal definitions, like \"ansi\" and \"xterm\", don't recognize colors higher than 7. If only colors 0-7 are supported, indicated by \"DYE_COLORS\" being set to 8 (via [setup](#initialization)), dye will synthesize \"bright\" colors by turning on \"bold\" and setting the non-bright equivalent color. (Some terminals will, in turn, render this with a bright color!)\n\nNote that this also means that \"bold\" may be turned on unexpectedly if you're using \"bright\" colors—so keep this situation in mind:\n\n-   If you're using templates or manual control, be sure to reset at the appropriate time if the possiblity that \"bold\" might be on.\n\n-   If you're nesting wrapped text, make sure that nested text deals with the fact that \"bold\" might be on if you're using a \"bright\" color.\n\nYou can test your code in this situation by setting \"TERM\" to \"ansi\" or \"xterm\" on many systems.\n\n### Emphases\n\nSeveral emphases are available as well.\n\n#### Resettable emphases\n\nThe first group, like colors, must be [reset](#resets) to turn them off:\n\n-   `dim` (makes things darker)\n-   `bold`\n-   `reverse` (see also \"standout\" below)\n\nThey can be used just like colors, and wrapping text with them will automatically send a reset at the end.\n\n#### Endable emphases\n\nThe second group have \"end\" terminal sequences that can turn them off explicitly, with all other settings remaining in play:\n\n-   `italic` (or `i`)\n-   `standout` (or `so`, often displayed as reversed foreground and background)\n-   `underline` (or `ul`, or `u`)\n\nWhen using one of these, you don't have to re-enable other modes—just \"end\" them:\n\n```shell\ndye print \"{{magenta}}Mary {{italic}}had{{end italic}} a little lamb.{{reset}}\"\ndye print \"{{magenta}}Mary had a {{italic}}little{{end italic}} lamb.{{reset}}\"\n```\n\nThey also support old-school wrapping:\n\n```shell\necho \"Visit $(dye ul \"https://mattiebee.dev/dye\") to get the code.\"\n```\n\nTo match \"end\", \"begin\" is also available (and works with all emphases). It behaves the same way as just using the emphasis, e.g. \"dye begin italic\" is equivalent to \"dye italic\".\n\n## Development\n\n### Testing\n\nUnit tests are exhaustively written in [shellspec](https://shellspec.info). The [specs](./tools/specs) script will look for shells on your system that are expected to be compatible, and run the suite for each, stopping on the first failures.\n\n[coverage](./tools/coverage) pairs shellspec with [kcov](http://simonkagstrom.github.io/kcov/) in [Docker](https://www.docker.com) to gather coverage while running against [Bash](https://www.gnu.org/software/bash/). (I'm using Docker here because I can't get shellspec with kcov to work on macOS.) 100% is impossible to reach due to kcov thinking some syntax isn't covered. But all meaningful lines of [dye.sh](./dye.sh) are covered.\n\n### Standards\n\nCode should all be written to [the POSIX shell spec](https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html). Deviations are probably bugs. 🐜\n\nThis is particularly important because dye should run everywhere it can, including in very limited systems. If it starts getting loaded up with [Bashisms](https://www.bowmanjd.com/bash-not-bash-posix/), it won't work in some places.\n\n### Notes\n\n#### tput\n\nDuring the development of dye, I did explore things like caching the output of \"tput\" so it didn't have to be invoked quite so much.\n\nThe added complexity was really not worth it, since \"tput\" is still fast enough (i.e. not at all noticeably slow) for most purposes where a shell script is doing work for at least a small amount of time. The cache would also need to be filled, and most scripts just don't switch colors enough to make it worthwhile.\n\nThe sequences dye generally uses are simple and unconcerned with this, but there are also interesting details with certain terminal control sequences on certain systems that \"tput\" can handle if invoked directly, such as embedded delays. So, the practice also encourages maximum compatibility—especially if using the new template engine, which passes control back and forth between \"tput\" and \"printf\".\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmattieb%2Fdye","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmattieb%2Fdye","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmattieb%2Fdye/lists"}