{"id":33180947,"url":"https://github.com/shuckster/statebot-sh","last_synced_at":"2026-01-17T14:21:16.214Z","repository":{"id":43750052,"uuid":"257148156","full_name":"shuckster/statebot-sh","owner":"shuckster","description":"Statebot for shell-scripts. Describe the states and allowed transitions of a program using a flowchart-like syntax.","archived":false,"fork":false,"pushed_at":"2022-11-01T12:13:47.000Z","size":301,"stargazers_count":17,"open_issues_count":0,"forks_count":1,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-06-09T13:40:52.168Z","etag":null,"topics":["code-organization","emitting-events","fsm","state-machine","state-management"],"latest_commit_sha":null,"homepage":"https://shuckster.github.io/statebot/","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/shuckster.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-04-20T02:13:21.000Z","updated_at":"2024-11-04T23:57:07.000Z","dependencies_parsed_at":"2023-01-20T18:00:14.722Z","dependency_job_id":null,"html_url":"https://github.com/shuckster/statebot-sh","commit_stats":null,"previous_names":[],"tags_count":23,"template":false,"template_full_name":null,"purl":"pkg:github/shuckster/statebot-sh","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shuckster%2Fstatebot-sh","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shuckster%2Fstatebot-sh/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shuckster%2Fstatebot-sh/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shuckster%2Fstatebot-sh/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/shuckster","download_url":"https://codeload.github.com/shuckster/statebot-sh/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shuckster%2Fstatebot-sh/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":285512086,"owners_count":27184300,"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-11-20T02:00:05.334Z","response_time":54,"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":["code-organization","emitting-events","fsm","state-machine","state-management"],"created_at":"2025-11-16T04:00:23.294Z","updated_at":"2025-11-20T21:04:42.335Z","avatar_url":"https://github.com/shuckster.png","language":"Shell","readme":"\u003cimg alt=\"Statebot-sh\" src=\"./sh-logo-full.png\" width=\"300\" /\u003e\n\nStatebot for shell-scripts. Describe the states and allowed transitions of a program using a flowchart-like syntax.\n\nStatebot-sh is an [FSM](https://en.wikipedia.org/wiki/Finite-state_machine). A minimal, but useful shell port of the version that runs in [Node and the browser](https://shuckster.github.io/statebot/). It employs a simple caching mechanism with `/tmp/statebots.csv` in order to persist the states of your machines across runs, and optionally the events too.\n\n- [Quick Start](#quick-start)\n  - [A small example](#a-small-example)\n- [The API](#the-api)\n  - [Handling events and transitions](#handling-events-and-transitions)\n  - [Limitations](#limitations)\n- [Why?](#why)\n- [Credits](#credits)\n- [License](#license)\n\nDocumentation is in this README, and examples can be found in `/examples`. It's not required reading, but the original JavaScript version has [extensive documentation](https://shuckster.github.io/statebot/) that may be helpful with getting-to-grips with this port. In particular, familiarity with the [chart syntax](https://zansh.in/statebot/types/index.TStatebotChart.html) would be helpful to know (it's not very complicated.)\n\n# Quick Start\n\n`install.sh` will install Statebot-sh and its examples into `/opt/statebot`:\n\n```sh\n# Run install.sh using curl\nsh -c \"$(curl -fsSL https://raw.githubusercontent.com/shuckster/statebot-sh/master/install.sh)\"\n```\n```sh\n# Run install.sh using wget\nsh -c \"$(wget -qO- https://raw.githubusercontent.com/shuckster/statebot-sh/master/install.sh)\"\n```\n\n## Manual installation\n\nOf course, you can also just download whatever you like directly from [the repository](https://github.com/shuckster/statebot-sh). Only `statebot.sh` is required:\n\n```sh\ncurl https://raw.githubusercontent.com/shuckster/statebot-sh/master/statebot.sh \u003e statebot.sh\n```\n\nYou don't need to make it executable, but if you do, running it will display an example and the API documentation:\n\n```sh\nchmod +x statebot.sh\n./statebot.sh\n```\n\n# A small example:\n\n```sh\n#!/bin/sh\nSTATEBOT_LOG_LEVEL=4\n# 0 for silence, 4 for everything\n\nSTATEBOT_USE_LOGGER=0\n# 1 to use the `logger` command instead of `echo`\n\n#\n# Define the states and allowed transitions:\n#\nPROMISE_CHART='\n\n  idle -\u003e\n    // Behaves a bit like a JS Promise\n    pending -\u003e\n      (rejected | resolved) -\u003e\n    idle\n\n'\n#\n# Implement \"perform_transitions\" to act on events:\n#\nperform_transitions ()\n{\n  local ON THEN\n  ON=\"\"\n  THEN=\"\"\n\n  case $1 in\n    'idle-\u003epending')\n      ON=\"start\"\n      THEN=\"hello_world\"\n    ;;\n    'pending-\u003eresolved')\n      ON=\"okay\"\n      THEN=\"statebot_emit done\"\n    ;;\n    'rejected-\u003eidle'|'resolved-\u003eidle')\n      ON=\"done\"\n      THEN=\"all_finished\"\n    ;;\n  esac\n\n  echo $ON \"$THEN\"\n\n  # The job of this function is to \"echo\" the event-\n  # name that will cause the transition to happen.\n  #\n  # Optionally, it can also \"echo\" a command to run\n  # after the transition happens.\n  #\n  # Following the convention set in the JS version\n  # of Statebot, this is called a \"THEN\" command.\n  # It can be anything you like, including a Statebot\n  # API call.\n  #\n  # It's important to just echo the name of an event\n  # (and optional command, too) rather than execute\n  # something directly! Anything that is echo'ed by\n  # this function that is not an event or command-\n  # name might result in some wild behaviour.\n}\n\n#\n# THEN callbacks:\n#\nhello_world ()\n{\n  echo \"Hello, World!\"\n  statebot_emit \"okay\"\n}\n\nall_finished ()\n{\n  echo \"Done and done!\"\n}\n\n#\n# Entry point\n#\nmain ()\n{\n  statebot_init \"demo\" \"idle\" \"start\" \"$PROMISE_CHART\"\n  #   machine name -^     ^      ^           ^\n  #  1st-run state -------+      |           |\n  #  1st-run event --------------+           |\n  # statebot chart --------------------------+\n\n  echo  \"Current state: $CURRENT_STATE\"\n  echo \"Previous state: $PREVIOUS_STATE\"\n\n  if [ \"$1\" = \"\" ]\n  then\n    exit\n  fi\n\n  # Send events/reset signal from the command-line:\n  if [ \"$1\" = \"reset\" ]\n  then\n    statebot_reset\n  else\n    statebot_emit \"$1\"\n  fi\n}\n\ncd \"${0%/*}\" || exit\n# (^- change the directory to where this script is)\n\n# Import Statebot-sh\n# shellcheck disable=SC1091\n. ./statebot.sh\n\nmain \"$1\"\n```\n\nThis example, along with some more complex ones, is available in the `/examples` folder.\n\n# The API\n\nThis port implements a subset of the API in the JavaScript version of Statebot.\n\nYou can see API documentation by running `./statebot.sh --api`\n\nHere's the output:\n\n```sh\nstatebot_inspect \"$YOUR_CHART\"\n  #\n  # When developing your charts, it is useful\n  # to see the transitions they represent so\n  # you can copy-paste into your perform/\n  # on_transitions() functions.\n  #\n  # Use statebot_inspect() to give you this\n  # information, and the states too.\n\nstatebot_init \"example\" \"idle\" \"start\" \"idle -\u003e done\"\n  #                 ^      ^     ^         ^\n  #   machine name -|      |     |         |\n  #  1st-run state --------+     |         |\n  #  1st-run event --------------+         |\n  # statebot chart ------------------------+\n  #\n  # If your machine does not yet have an\n  # entry in the CSV database, this will\n  # initialise it with the values passed-in.\n  #\n  # If the machine already exists, then the\n  # values in the DB will be used from this\n  # point onwards.\n  #\n  # Only one machine is allowed per script,\n  # so do not call this more than once in\n  # order to try and have multiple state-\n  # machines in the same script! It is easy\n  # to use Statebot in many different and\n  # independent scripts.\n  #\n  # (Charts are not stored in the DB, and\n  # are specified as the last argument\n  # in order to enforce setting defaults\n  # for the initial state/event too.)\n\nstatebot_emit \"start\" persist\n  #              ^       ^\n  # event name --+       |\n  #                      |\n  # Store this event ----+ (optional)\n  # for a future run instead of calling it\n  # immediately.\n  #\n  # When you run your script again later, the\n  # event will be picked-up by the call to\n  # statebot_init().\n\nstatebot_enter \"pending\"\n  #                ^\n  #   state name --+\n  #\n  # Changes to the specified state, if allowed\n  # by the rules in the state-chart.\n\nstatebot_reset\n  #\n  # Reset the machine to its 1st-run state\n  # \u0026 event. No events will be emitted.\n\nstatebot_states_available_from_here\n  #\n  # List the states available from the\n  # current-state.\n\nstatebot_current_state_of \"statebot_name\"\nstatebot_persisted_event_of \"statebot_name\"\n  #                                ^\n  #  machine name -----------------+\n  #\n  # Get the current-state / persisted-event\n  # of the machine called \"statebot_name\".\n\nstatebot_delete \"statebot_name\"\n  #                    ^\n  #  machine name -----+\n  #\n  # Delete the record of a \"statebot_name\"\n  # from the database.\n\n# Details about the current machine:\necho \"     Current state: $CURRENT_STATE\"\necho \"    Previous state: $PREVIOUS_STATE\"\necho \"Last emitted event: $PREVIOUS_EVENT\"\n```\n\n## Handling events and transitions\n\nIf you want Statebot to do something when you run `statebot_emit()` and `statebot_enter()` other than just changing the `CURRENT_STATE` variable, then you need to define at least one of these functions:\n\n- `perform_transitions()`\n- `on_transitions()`\n\nThese should be available before calling `statebot_init()`.\n\n`perform_transitions()` has the following signature:\n\n```sh\nperform_transitions ()\n{\n  local ON THEN\n  ON=\"\"\n  THEN=\"\"\n\n  # A string in the form `from-\u003eto` will be passed-in\n  # as the only argument ($1) to this function, and it\n  # represents a state-transition.\n  case $1 in\n    'idle-\u003epending')\n      ON=\"start\"\n      THEN=\"statebot_emit okay persist\"\n    ;;\n    'pending-\u003eresolved')\n      ON=\"okay\"\n      THEN=\"statebot_emit done\"\n    ;;\n    'rejected-\u003eidle'|'resolved-\u003eidle')\n      ON=\"done\"\n    ;;\n  esac\n\n  echo $ON $THEN\n\n  # The job of this function is to \"echo\" the event-\n  # name that will cause the transition to happen.\n  #\n  # Optionally, it can also \"echo\" a command to run\n  # after the transition happens.\n  #\n  # Following the convention set in the JS version\n  # of Statebot, this is called a \"THEN\" command.\n  # It can be anything you like, including a Statebot\n  # API call.\n  #\n  # It's important to just echo the name of an event\n  # (and optional command, too) rather than execute\n  # something directly! Anything that is echo'ed by\n  # this function that is not an event or command-\n  # name might result in some wild behaviour.\n}\n```\n\n`on_transitions()` is similar:\n\n```sh\non_transitions ()\n{\n  local THEN\n  THEN=\"\"\n\n  # A string in the form `from-\u003eto` will be passed-in\n  # as the only argument ($1) to this function, and it\n  # represents the state-transition that just happened.\n  case $1 in\n    'idle-\u003epending')\n      THEN=\"echo Hello, World!\"\n    ;;\n    'rejected-\u003eidle'|'resolved-\u003eidle')\n      THEN=\"all_finished\"\n    ;;\n  esac\n\n  echo $THEN\n\n  # The job of this function is to \"echo\" the name of\n  # a command you want to run.\n  #\n  # Again, it's important to just echo the name of\n  # a command rather than executing it directly!\n}\n```\n\nNote that `statebot_emit()` will also call `on_transitions()` if an event causes a transition to happen, so long as the function has been defined.\n\n## Limitations\n\nThe JavaScript version of Statebot obviously has a bigger API, but it also allows more complex transition-names in its `performTransitions()` and `onTransitions()` hitchers, for example:\n\n```js\nmachine.onTransitions({\n  'resolved | rejected -\u003e done': () =\u003e {\n    console.log('All finished')\n  }\n})\n```\n\nTo emulate this in Statebot-sh, the function `case_statebot` is offered:\n\n```sh\non_transitions ()\n{\n  local THEN\n  THEN=\"\"\n\n  if case_statebot $1 '\n    resolved | rejected -\u003e done\n  '\n  then\n    THEN=\"echo 'All finished'\"\n  fi\n\n  echo $THEN\n}\n```\n\nIt takes a transition as the first-argument, and a statebot-chart as the second. If the transition exists in the chart, an exit-code of `0` is returned.\n\nAbusing this will manifest as a performance-hit on slow devices, so I recommend using it only in a wildcard `*)` at the end of a regular case-statement.\n\nFor example:\n\n```sh\nperform_transitions ()\n{\n  local ON THEN\n  ON=\"\"\n  THEN=\"\"\n\n  case $1 in\n    # Handle your \"simple\" transitions first:\n    'idle-\u003epending')\n      ON=\"start\"\n      THEN=\"statebot_emit okay\"\n    ;;\n\n    *)\n      # Now in the wildcard section, use\n      # case_statebot() for your complex\n      # rules:\n      if case_statebot $1 '\n        rejected | resolved -\u003e idle\n      '\n      then\n        ON=\"done\"\n      fi\n    ;;\n  esac\n\n  echo $ON $THEN\n}\n```\n\n# Why?\n\nAfter writing the [JavaScript version of Statebot](https://shuckster.github.io/statebot/) I really wanted the same kind of API to help me write more predictable scripts for my little hobby devices.\n\nI know **Raspberry Pi** is all the rage, so I could have just installed Node on one of those and used the JS version of Statebot. But I also have some considerably less beefy embedded devices that can't run Node, so I really liked the idea of getting Statebot running as a shell-script.\n\nNeedless to say, it now satisfies my own needs enough that I'm happy to share it. :)\n\nI hope you find it useful!\n\n# Contributing\n\nMy main goal with this version is to make it as portable as possible! If you find an issue with it working in your particular version of `sh` or `bash`, please file a bug!\n\nIf you feel it has saved you a little pain while trying to grok your own old projects, please consider [buying me a coffee](https://www.buymeacoffee.com/shuckster). :)\n\n## Credits\n\nStatebot was inspired by a trawl through Wikipedia and Google, which in turn was inspired by [XState](https://github.com/davidkpiano/xstate) by David Khourshid. You should check it out.\n\nThe Statebot logo uses the \"You're Gone\" font from [Typodermic Fonts](https://typodermicfonts.com/youre-gone/). The logo was made with [Acorn](https://flyingmeat.com/acorn/). The JS documentation is written in [JSDoc](https://jsdoc.app/) and is built with [Typedoc](https://typedoc.org/).\n\nStatebot-sh was written by [Conan Theobald](https://github.com/shuckster/).\n\n\u003cimg src=\"./logo-small.png\" width=\"75\" /\u003e\n\n## License\n\nStatebot-sh is [MIT licensed](./LICENSE).\n","funding_links":["https://www.buymeacoffee.com/shuckster"],"categories":["Libraries"],"sub_categories":["Shell"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshuckster%2Fstatebot-sh","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fshuckster%2Fstatebot-sh","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshuckster%2Fstatebot-sh/lists"}