{"id":13566407,"url":"https://github.com/icy/bash-coding-style","last_synced_at":"2025-04-07T12:06:46.819Z","repository":{"id":29108190,"uuid":"32637703","full_name":"icy/bash-coding-style","owner":"icy","description":"A Bash coding style","archived":false,"fork":false,"pushed_at":"2023-02-16T19:42:56.000Z","size":204,"stargazers_count":288,"open_issues_count":1,"forks_count":37,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-03-31T10:08:40.302Z","etag":null,"topics":["bash","disaster","personality","styling"],"latest_commit_sha":null,"homepage":"https://github.com/icy/bash-coding-style","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/icy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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}},"created_at":"2015-03-21T15:35:37.000Z","updated_at":"2025-03-29T20:43:17.000Z","dependencies_parsed_at":"2024-04-07T22:45:24.538Z","dependency_job_id":null,"html_url":"https://github.com/icy/bash-coding-style","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icy%2Fbash-coding-style","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icy%2Fbash-coding-style/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icy%2Fbash-coding-style/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icy%2Fbash-coding-style/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/icy","download_url":"https://codeload.github.com/icy/bash-coding-style/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247648977,"owners_count":20972945,"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","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","disaster","personality","styling"],"created_at":"2024-08-01T13:02:09.027Z","updated_at":"2025-04-07T12:06:46.788Z","avatar_url":"https://github.com/icy.png","language":null,"funding_links":[],"categories":["Others"],"sub_categories":[],"readme":"## Some `Bash` coding conventions and good practices.\n\nCoding conventions are... just conventions.\nThey help to have a little fun with scripting,\nnot to create new war/bias conversations.\n\nFeel free to break the rules any time you can; it's important\nthat you will always love what you would have written\nbecause scripts can be too fragile, too hard to maintain,\nor so many people hate them...\nAnd it's also important to have a consistent way in your scripts.\n\n* [Deprecated conventions](#deprecation)\n  * [`variable name started with an underscore` (`_foo_bar`)](#variable-name-started-with-an-underscore-_foo_bar)\n* [Naming and styles](#naming-and-styles)\n  * [Tabs and Spaces](#tabs-and-spaces)\n  * [Pipe](#pipe)\n  * [Variable names](#variable-names)\n  * [Function names](#function-names)\n* [Error handling](#error-handling)\n  * [Sending instructions](#sending-instructions)\n  * [Pipe error handling](#pipe-error-handling)\n  * [Catch up with $?](#catch-up-with-)\n  * [Automatic error handling](#automatic-error-handling)\n    * [Set -u](#set--u)\n    * [Set -e](#set--e)\n* [Techniques](#techniques)\n  * [Keep that in mind](#keep-that-in-mind)\n  * [A little tracing](#a-little-tracing)\n  * [Making your script a library](#making-your-script-a-library)\n  * [Quick self-doc](#quick-self-doc)\n  * [No excuse](#no-excuse)\n  * [Meta programming](#meta-programming)\n  * [Removing with care](#removing-with-care)\n  * [Shell or Python/Ruby/etc](#shell-or-pythonrubyetc)\n* [Contributions](#contributions)\n  * [Variable names for arrays](#variable-names-for-arrays)\n* [Good lessons](#good-lessons)\n* [Resources](#resources)\n* [Author. License](#author-license)\n\n## Naming and Styles\n\n### Tabs and Spaces\n\nDon't use `(smart-)`tabs. Replace a tab by two spaces.\nDo not accept any trailing spaces.\n\nMany editors can't and/or aren't configured to display the differences\nbetween tabs and spaces. Another person's editor is just not your editor.\nHaving spaces does virtually help a strange reader of your script.\n\n### Pipe\n\nThere are `inline` pipe and `display` pipe.  Unless your pipe is \nshort, please use `display` pipe to make things clear. For example,\n\n\n```bash\n\n # This is an inline pipe: \"$(ls -la /foo/ | grep /bar/)\"\n\n # The following pipe is of display form: every command is on\n # its own line.\n\nfoobar=\"$( \\\n  ls -la /foo/ \\\n  | grep /bar/ \\\n  | awk '{print $NF}')\"\n\n_generate_long_lists \\\n| while IFS= read -r  line; do\n    _do_something_fun\n  done\n```\n\nWhen using `display` form, put pipe symbol (`|`) at the beginning of\nof its statement. Don't put `|` at the end of a line, because it's the\njob of the line end (`EOL`) character and line continuation (`\\`).\n\nHere is another example\n\n```bash\n\n # List all public images found in k8s manifest files\n # ignore some in-house image.\nlist_public_images() {\n  find . -type f -iname \"*.yaml\" -exec grep 'image: ' {} \\; \\\n  | grep -v ecr. \\\n  | grep -v '#' \\\n  | sed -e \"s#['\\\"]##g\" \\\n  | awk '{print $NF}' \\\n  | sort -u \\\n  | grep -Eve '^(coredns|bflux|kube-proxy|logstash)$' \\\n}\n```\n\n### Variable names\n\nIf you are going to have meanful variable name, please use them\nfor the right purpose. The variable name `country_name` should\nnot be used to indicate a city name or a person, should they?\nSo this is bad\n\n```bash\n\ncountries=\"australia germany berlin\"\nfor city in $countries; do\n  echo \"city or country is: $city\ndone\n```\n\nThat's very bad example but that is to emphasize the idea.\n(FIXME: Add better examples)\n\nA variable is named according to its scope.\n\n* If a variable can be changed from its parent environment,\n  it should be in uppercase; e.g, `THIS_IS_A_USER_VARIABLE`.\n* Other variables are in lowercase\n* Any local variables inside a function definition should be\n  declared with a `local` statement.\n\nExample\n\n```bash\n\n # The following variable can be provided by user at run time.\nD_ROOT=\"${D_ROOT:-}\"\n\n # All variables inside `my_def` are declared with `local` statement.\nmy_def() {\n  local d_tmp=\"/tmp/\"\n  local f_a=\n  local f_b=\n\n  # This is good, but it's quite a mess\n  local f_x= f_y=\n}\n```\n\nThough `local` statement can declare multiple variables, that way\nmakes your code unreadable. Put each `local` statement on its own line.\n\n`FIXME`: Add flexibility support.\n\n### Function names\n\nName of internal functions should be started by an underscore (`_`).\nUse underscore (`_`) to glue verbs and nouns. Don't use camel form\n(`ThisIsNotMyStyle`; use `this_is_my_style` instead.)\n\nUse two underscores (`__`) to indicate some very internal methods aka\nthe ones should be used by other internal functions.\n\n## Error handling\n\n### Sending instructions\n\nAll errors should be sent to `STDERR`. Never send any error/warning message\nto a`STDOUT` device. Never use `echo` directly to print your message;\nuse a wrapper instead (`warn`, `err`, `die`,...). For example,\n\n```bash\n\n_warn() {\n  echo \u003e\u00262 \":: $*\"\n}\n\n_die() {\n  echo \u003e\u00262 \":: $*\"\n  exit 1\n}\n```\n\nDo not handle error of another function. Each function should handle\nerror and/or error message by their own implementation, inside its own\ndefinition.\n\n```bash\n\n_my_def() {\n  _foobar_call\n\n  if [[ $? -ge 1 ]]; then\n    echo \u003e\u00262 \"_foobar_call has some error\"\n    _error \"_foobar_call has some error\"\n    return 1\n  fi\n}\n```\n\nIn the above example, `_my_def` is trying to handle error for `_foobar_call`.\nThat's not a good idea. Use the following code instead\n\n```bash\n\n_foobar_call() {\n  # do something\n\n  if [[ $? -ge 1 ]]; then\n    _error \"${FUNCNAME[0]} has some internal error\"\n  fi\n}\n\n_my_def() {\n  _foobar_call || return 1\n}\n```\n\n### Catch up with $?\n\n`$?` is used to get the return code of the *last statement*.\nTo use it, please make sure you are not too late. The best way is to\nsave the last return code thanks to some local variable. For example,\n\n```bash\n\n_do_something_critical\nlocal _ret=\"$?\"\n\n # from now on, $? is zero, because the latest statement (assignment)\n # (always) returns zero.\n\n_do_something_terrible\necho \"done\"\nif [[ $? -ge 1 ]]; then\n  # Bash will never reach here. Because \"echo\" has returned zero.\nfi\n```\n\n`$?` is very useful. But don't trust it.\n\nPlease don't use `$?` with `set -e` ;)\n\n### Pipe error handling\n\nPipe stores its components' return codes in the `PIPESTATUS` array.\nThis variable can be used only *ONCE* in the sub-`{shell,process}`\nfollowed the pipe. Be sure you catch it up!\n\n```bash\n\n  echo test | fail_command | something_else\n  local _ret_pipe=( \"${PIPESTATUS[@]}\" )\n  # from here, `PIPESTATUS` is not available anymore\n```\n\nWhen this `_ret_pipe` array contains something other than zero,\nyou should check if some pipe component has failed. For example,\n\n```bash\n\n # Note:\n #   This function only works when it is invoked\n #   immediately after a pipe statement.\n_is_good_pipe() {\n  echo \"${PIPESTATUS[@]}\" | grep -qE \"^[0 ]+$\"\n}\n\n_do_something | _do_something_else | _do_anything\n_is_good_pipe \\\n|| {\n  echo \u003e\u00262 \":: Unable to do something\"\n}\n```\n\n### Automatic error handling\n\n#### Set -u\n\nAlways use `set -u` to make sure you won't use any undeclared variable.\nThis saves you from a lot of headaches and critical bugs.\n\nBecause `set -u` can't help when a variable is declared and set to empty\nvalue, don't trust it twice.\n\nIt's recommended to emphasize the needs of your variables before your\nscript actually starts. In the following example, the script just stops\nwhen `SOME_VARIABLE` or `OTHER_VARIABLE` is not defined; these checks\nare done just before any execution of the main routine(s).\n\n```bash\n\n: a lot of method definitions\n\nset -u\n\n: \"${SOME_VARIABLE}\"\n: \"${OTHER_VARIABLE}\"\n\n: your main routine\n```\n\n#### Set -e\n\nUse `set -e` if your script is being used for your own business.\n\nBe **careful** when shipping `set -e` script to the world. It can simply\nbreak a lot of games. And sometimes you will shoot yourself in the foot.\nIf possible please have an option for user choice.\n\nLet's see\n\n```bash\n\nset -e\n_do_some_critical_check\n\nif [[ $? -ge 1 ]]; then\n  echo \"Oh, you will never see this line.\"\nfi\n```\n\nIf `_do_some_critical_check` fails, the script just exits and the following\ncode is just skipped without any notice. Too bad, right? The code above\ncan be refactored as below\n\n```bash\n\nset +e\nif _do_some_critical_check; then\n  echo \"Something has gone very well.\"\nfi\necho \"You will see this line.\"\n```\n\nNow, if you expect to stop the script when `_do_some_critical_check` fails\n(it's the purpose of `set -e`, right?), these lines don't help. Why?\nBecause `set -e` doesn't work when being used with `if`. Confused?\nOkay, these lines are the correct one\n\n```bash\n\nset +e\nif _do_some_critical_check; then\n  echo \"All check passed.\"\nelse\n  echo \"Something wrong we have to stop here\"\n  exit 1 # or return 1\nfi\n```\n\n`set -e` doesn't help to improve your code: it just forces you to work hard,\ndoesn't it?\n\nAnother example, in effect of `set -e`:\n\n```bash\n\n(false \u0026\u0026 true); echo not here\n```\n\nprints nothing, while:\n\n```bash\n\n    { false \u0026\u0026 true; }; echo here\n```\n\nprints `here`.\n\nThe result is varied with different shells or even different versions of the same shell.\n\nIn general, don't rely on `set -e` and do proper error handling instead.\n\nFor more details about `set -e`, please read\n\n\u003e The correct answer to every exercise is actually \"because set -e is crap\".\n\n* http://mywiki.wooledge.org/BashFAQ/105/Answers\n* [When Bash scripts bite](https://news.ycombinator.com/item?id=14321213)\n\n## Techniques\n\n### Keep that in mind\n\nThere are lot of shell scripts that don't come with (unit)tests.\nIt's just not very easy to write tests. Please keep that in mind:\nWriting shell scripts is more about dealing with runtime and side effects.\n\nIt's very hard to refactor shell scripts.\nBe prepared, and don't hate bash/shell scripts too much ;)\n\n### A little tracing\n\nIt would be very helpful if you can show in your script logs some tracing\ninformation of the being-invoked function/method.\n\n`Bash` has two jiffy variables `LINENO` and `FUNCNAME` that can help.\nWhile it's easy to understand `LINENO`, `FUNCNAME` is a little complex.\nIt is an array of `chained` functions. Let's look at the following example\n\n```bash\n\nfuncA() {\n  log \"This is A\"\n}\n\nfuncB() {\n  log \"This is B\"\n  funcA\n}\n\nfuncC() {\n  log \"This is C\"\n  funcB\n}\n\n: Now, we call funcC\n\nfuncC\n```\n\nIn this example, we have a chain: `funcC -\u003e funcB -\u003e funcA`.\nInside `funcA`, the runtime expands `FUNCNAME` to\n\n```bash\n\nFUNCNAME=(funcA funcB funcC)\n```\n\nThe first item of the array is the method per-se (`funcA`),\nand the next one is the one who instructs `funcA` (it is `funcB`).\n\nSo, how can this help? Let's define a powerful `log` function\n\n```bash\n\nlog() {\n  echo \"(LOGGING) ${FUNCNAME[1]:-unknown}: *\"\n}\n```\n\nYou can use this little `log` method everywhere, for example, when `funcB`\nis invoked, it will print\n\n```bash\n\nLOGGING funcB: This is B\n```\n\n### Making your script a library\n\nFirst thing first: Use `function` if possible. Instead of writting\nsome direct instructions in your script, you have a wrapper for them.\nThis is not good\n\n```bash\n\n: do something cool\n: do something great\n```\n\nHaving them in a function is better\n\n```bash\n\n_default_tasks() {\n  : do something cool\n  : do something great\n}\n```\n\nNow in the very last lines of you script, you can execute them\n\n```bash\n\ncase \"${@:-}\" in\n\":\")  echo \"File included.\" ;;\n\"\")   _default_tasks        ;;\nesac\n```\n\nFrom other script you can include the script easily without executing\nany code:\n\n```bash\n\n # from other script\nsource \"/path/to_the_previous_script.sh\" \":\"\n```\n\n(When being invoked without any argument the `_default_tasks` is called.)\n\nBy advancing this simple technique, you have more options to debug\nyour script and/or change your script behavior.\n\n### Quick self-doc\n\nIt's possible to generate beautiful self documentation by using `grep`,\nas in the following example. You define a strict format and `grep` them:\n\n```bash\n\n_func_1() { #public: Some quick introduction\n  :\n}\n\n_func_2() { #public: Some other tasks\n  :\n}\n\n_quick_help() {\n  LANG=en_US.UTF_8\n  grep -E '^_.+ #public' \"$0\" \\\n  | sed -e 's|() { #public: |☠|g' \\\n  | column -s\"☠\" -t \\\n  | sort\n}\n```\n\nWhen you execute `_quick_help`, the output is as below\n\n```bash\n\n_func_1    Some quick introduction\n_func_2    Some other tasks\n```\n\n### No excuse\n\nWhen someone tells you to do something, you may blindly do as said,\nor you would think twice then raise your white flag.\n\nSimilarly, you should give your script a white flag. A backup script\ncan't be executed on any workstation. A clean up job can't silently\nsend `rm` commands in any directory. Critical mission script should\n\n* exit immediately without doing anything if argument list is empty;\n* exit if basic constraints are not established.\n\nKeep this in mind. Always.\n\n### Meta programming\n\n`Bash` has a very powerful feature that you may have known:\nIt's very trivial to get definition of a defined method. For example,\n\n```bash\n\nmy_func() {\n  echo \"This is my function`\"\n}\n\necho \"The definition of my_func\"\ndeclare -f my_func\n\n # \u003csnip\u003e\n```\n\nWhy is this important? Your program manipulates them. It's up to your\nimagination.\n\nFor example, send a local function to remote and excute them via `ssh`\n\n```bash\n\n{\n  declare -f my_func    # send function definition\n  echo \"my_func\"        # execution instruction\n} \\\n| ssh some_server\n```\n\nThis will help your program and script readable especially when you\nhave to send a lot of instructions via `ssh`. Please note `ssh` session\nwill miss interactive input stream though.\n\n### Removing with care\n\nIt's hard to remove files and directories **correctly**.\nPlease consider to use `rm` with `backup` options. If you use some\nvariables in your `rm` arguments, you may want to make them immutable.\n\n```bash\n\nexport temporary_file=/path/to/some/file/\nreadonly temporary_file\n # \u003csnip\u003e\nrm -fv \"$temporary_file\"\n```\n\n### Shell or Python/Ruby/etc\n\nIn many situations you may have to answer to yourself whether you have\nto use `Bash` and/or `Ruby/Python/Go/etc`.\n\nOne significant factor is that `Bash` doesn't have a good memory.\nThat means if you have a bunch of data (in any format) you probably\nreload them every time you want to extract some portion from them.\nThis really makes your script slow and buggy. When your script\nneeds to interpret any kind of data, it's a good idea to move forward\nand rewrite the script in another language, `Ruby/Python/Golang/...`.\n\nAnyway, probably you can't deny to ignore `Bash`:\nit's still very popular and many services are woken up by some shell things.\nKeep learning some basic things and you will never have to say sorry.\nBefore thinking of switching to Python/Ruby/Golang, please consider\nto write better Bash scripts first ;)\n\n## Contributions\n\n### Variable names for arrays\n\nIn #7, Cristofer Fuentes suggests to use special names for arrays.\nPersonally I don't follow this way, because I always try to avoid\nto use Bash array (and/or associative arrays), and in Bash\nper-se there are quite a lot of confusion (e.g, `LINENO` is a string,\n`FUNCNAME` is array, `BASH_VERSION` is ... another array.)\n\nHowever, if your script has to use some array, it's also possible to\nhave special name for them. E.g,\n\n```bash\n\ndeclare -A DEPLOYMENTS\nDEPLOYMENTS[\"the1st\"]=\"foo\"\nDEPLOYMENTS[\"the2nd\"]=\"bar\"\n```\n\nAs there are two types of arrays, you may need to enforce a better name\n\n```bash\n\ndeclare -A MAP_DEPLOYMENTS\n```\n\nWell, it's just a reflection of some idea from another language;)\n\n## Good lessons\n\nSee also in `LESSONS.md` (https://github.com/icy/bash-coding-style/blob/master/LESSONS.md).\n\n## Deprecation\n\n### `variable name started with an underscore` (`_foo_bar`)\n\nDeprecated on July 7th 2021 (cf.: https://github.com/icy/bash-coding-style/issues/10).\n\nTo migrate existing code, you may need to list all variables that\nfollowed the deprecated convention. Here is an simple `grep` command:\n\n```\n$ grep -RhEoe '(\\$_\\w+)|(\\$\\{_[^}]+\\})' . | sort -u\n\n  # -R    find in all files in the current directory\n  # -h    don't show file name in the command output\n  # -E    enable regular expression\n  # -o    only print variable name that matches our pattern\n  # -e    to specify the pattern (as seen above)\n```\n\n## Resources\n\n* [Anybody can write good bash with a little effort](https://blog.yossarian.net/2020/01/23/Anybody-can-write-good-bash-with-a-little-effort)\n* [Google - Shell Style Guide](https://github.com/google/styleguide/blob/gh-pages/shellguide.md)\n* [Defensive Bash programming](https://news.ycombinator.com/item?id=7815190)\n* [Shellcheck](https://github.com/koalaman/shellcheck)\n* [What exactly was the point of [ “x$var” = “xval” ]?](https://www.vidarholen.net/contents/blog/?p=1035) TLDR; You needed the trick during the mid-to-late 1990s and some times before 2010. Now you can forget that trick.\n* [Don't copy paste from a website to a terminal (thejh.net)](http://thejh.net/misc/website-terminal-copy-paste): https://news.ycombinator.com/item?id=10554679\n* [Homebrew installation \"isssue\"](https://github.com/withfig/fig/discussions/300): https://news.ycombinator.com/item?id=27901496\n\n## Authors. License\n\nThe original author is Anh K. Huynh and the original work was part of\n[`TheSLinux`](http://theslinux.org/doc/bash/coding_style/).\n\nA few contributors have been helped to fix errors and improve the style.\nThey are also the authors.\n\nThe work is released under a MIT license.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ficy%2Fbash-coding-style","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ficy%2Fbash-coding-style","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ficy%2Fbash-coding-style/lists"}