{"id":20217589,"url":"https://github.com/pforret/learnbashquickly","last_synced_at":"2025-10-09T23:36:45.131Z","repository":{"id":65033866,"uuid":"327109569","full_name":"pforret/LearnBashQuickly","owner":"pforret","description":"Learn Bash scripting in 27 minutes","archived":false,"fork":false,"pushed_at":"2023-12-01T08:15:00.000Z","size":2386,"stargazers_count":36,"open_issues_count":0,"forks_count":3,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-10T16:07:01.072Z","etag":null,"topics":["bash","bash-script","learn","learn-to-code","learning","tutorial"],"latest_commit_sha":null,"homepage":"","language":null,"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/pforret.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}},"created_at":"2021-01-05T20:19:05.000Z","updated_at":"2025-02-18T14:46:16.000Z","dependencies_parsed_at":"2024-11-14T12:34:18.639Z","dependency_job_id":null,"html_url":"https://github.com/pforret/LearnBashQuickly","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/pforret/LearnBashQuickly","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pforret%2FLearnBashQuickly","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pforret%2FLearnBashQuickly/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pforret%2FLearnBashQuickly/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pforret%2FLearnBashQuickly/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pforret","download_url":"https://codeload.github.com/pforret/LearnBashQuickly/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pforret%2FLearnBashQuickly/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279002310,"owners_count":26083340,"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-09T02:00:07.460Z","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","bash-script","learn","learn-to-code","learning","tutorial"],"created_at":"2024-11-14T06:34:45.326Z","updated_at":"2025-10-09T23:36:45.085Z","avatar_url":"https://github.com/pforret.png","language":null,"readme":"![version](https://img.shields.io/github/v/release/pforret/LearnBashQuickly)\n[part of ![Bashful Scripting](https://img.shields.io/badge/bashful-scripting-orange) network](https://blog.forret.com/portfolio/bashful/)\n\n# Learn Bash in 27 minutes\nLearn Bash scripting in 27 minutes\n\n![Bash logo](learnbash.jpg)\nThis is inspired by \n[Learn Go in ~5mins](https://gist.github.com/prologic/5f6afe9c1b98016ca278f4d507e65510),\n[A half-hour to learn Rust](https://fasterthanli.me/articles/a-half-hour-to-learn-rust)\nand [Zig in 30 minutes](https://gist.github.com/ityonemo/769532c2017ed9143f3571e5ac104e50).\n\n## Why bash scripting?\n`bash` (Bourne Again Shell) was developed in 1989, which makes it younger than C \nbut probably older than anything you're used to developing in.\nThis does not make it old-fashioned or obsolete. \nBash runs practically everywhere: on **Unix/Linux**, on **MacOS** and **Windows** (WSL),\nit is used in all kinds of 'modern' software (Docker, deployment/build scripts, CI/CD) \nand the chances that you will have a rich developer career\nwithout ever using `bash` are quasi zero. So why not get good at it?\n\n## Your first script\n\nWhenever you are typing in your Terminal/Console, you are in an interactive `bash` shell \n(or its more sophisticated cousins `zsh` or `fish`). \nAny command you can type here, like `ls`, `whoami`, `echo \"hello\"` qualifies as a bash command, \nand can be used in a script.\n\nWhile in your terminal, enter the following command (don't copy the \u003e, it's there to indicate the start of the command line):\n```bash\n\u003e touch myscript.sh       # create the file 'myscript.sh' as an empty file\n```\n\nGo edit this new file with your favorite text editor (Sublime/Vcode/JetBrains/...) and add the following 2 lines:\n\n```bash\n#!/usr/bin/env bash\necho \"Hello world!\"\n```\n\nNow go back to your Terminal and type\n```bash\n\u003e chmod +x myscript.sh  # make the file executable, so you can call it directly as ./myscript.sh\n\u003e ./myscript.sh         # execute your new script\nHello world!\n```\n\n## Variables\n\nBash variables are untyped.\nThe value and/or context of a variable determines if it will be interpreted as an integer, a string or an array.\n\n```bash\nwidth=800               # variables are assigned with '='\nname=\"James Bond\"       # strings with spaces should be delimited by \" or '\nuser_name1=jbond        # variable names can contain letters, digits and '_', but cannot start with a digit\n\necho \"Welcome $name!\"   # variable are referenced with a $ prefix\nfile=\"${user}_${uniq}\"  # ${var} can be used to clearly delimit the name of the variable\necho \"${width:-400}\"    # if variable $width is not set, the default 400 will be used\necho \"${text/etc/\u0026}\"    # replace the 1st text 'etc' by '\u0026' in the variable before printing it\necho \"${text//etc/\u0026}\"   # replace all texts 'etc' by '\u0026' in the variable before printing it\n\nw=$((width + 80))       # $(()) allows arithmetic: + - / * % \n\ninput=$1                # $1, $2 ... are the 1st, 2nd ... parameters specified on the command line\ninput=\"$1\"              # put quotes around any variable that might contain \" \" (space), \"\\t\" (tab), \"\\n\" (new line) \nscript=$0               # $0 refers to the actual script's name, as called (so /full/path/script or ../src/script)\ntemp=\"/tmp/$$.txt\"      # $$ is the id of this process and will be different each time the script runs\n\necho \"$SECONDS secs\"    # there are preset variables that your shell sets automatically: $SECONDS, $HOME, $HOSTNAME, $PWD\n\nLANG=C do_something     # start the subcommand do_something but first set LANG to \"C\" only for that subcommand\n\nscript=$(basename \"$0\") # execute what is between $( ) and use the output as the value\n```\n\n## Test or [[ ]]\nBash has an essential '[test](https://ss64.com/bash/test.html)' program (e.g. `test -f output.txt`) \nthat is most common used as `[[ -f output.txt ]]`. \nThere is also an older syntax of `[ -f output.txt ]`, but the double square brackets are preferred. \nThis program tests for a certain condition and returns with 0 ('ok') if the condition was met. \nThe purpose of this will become clear in the next chapter.\n```bash\n[[ -f file.txt ]]       # file exists\n[[ ! -f file.txt ]]     # file does not exist -- ! means 'not'\n[[ -f a.txt \u0026\u0026 -f b.txt ]] # both files exist -- \u0026\u0026 means AND , || means OR\n[[ -d ~/.home ]]        # folder exists\n[[ -x program ]]        # program exists and is executable\n[[ -s file.txt ]]       # file exists and has a size \u003e 0\n[[ -n \"$text\" ]]        # variable $text is not empty\n[[ -z \"$text\" ]]        # variable $text is empty\n[[ \"$text\" == \"yes\" ]]  # variable $text == \"yes\"\n[[ $width -eq 800 ]]    # $width is the number 800\n[[ $width -gt 800 ]]    # $width is greater than 800\n[[ file1 -nt file2 ]]   # file1 is newer (more recently modified) than file2\n```\n\n## Flow control structures\n\n```bash\nif [[ ! -f \"out.txt\" ]] ; then                    # if ... then ... else ... fi\n  echo \"OK\"\nelse\n  echo \"STOP\"\nfi\n\n[[ -f \"out.txt\" ]] \u0026\u0026 echo \"OK\" || echo \"STOP\"    # can also be written as 1 line if the 'then' part is 1 line only\n\nwhile [[ ! -f output.txt ]] ; do                  # while ... do ... done\n  (...)     # wait for output.txt\n  continue  # return and do next iteration\ndone\n\nfor file in *.txt ; do                            # for ... in ... do ... done\n  echo \"Process file $file\"\n  (...)\ndone\n\nfor (( i = 0; i \u003c n; i++ )); do                   # for (...) do ... done\n    (...)\ndone\n\ncase $option in                                   # case ... in ...) ;; esac\nexport) do_export \"$1\" \"$2\"                       # you might know this as a 'switch' statement\n  ;;\nrefresh) do_refresh \"$2\"\n  ;;\n*)      echo \"Unknown option [$option]\"\nesac\n```\n\n\n## Functions\n```bash\nmyfunction(){           # bash functions are defined with \u003cname\u003e(){...} and have to be defined before they are used\n  # $1 = input          # functions can be called with their own parameters, and they are also referenced as $1 $2\n  # $2 = output         # this means that the main program's $1,$2...parameters are no longer available inside the function\n  local error           # a variable can be defined with local scope. if not, variables are always global\n  (...)\n  return 0              # this explicitly exits the function with a status code (0 = OK, \u003e 0 = with an error)\n}\n```\n\n## Arrays\n```bash\nnumbers=(1 2 3)           # define array with all values\nnumbers[0]=\"one\"          # replace 1st element (indexes start at 0) by \"one\"\necho ${numbers[@]}        # [@] represents the whole array\nnumbers=(${numbers[@]} 4) # add new element to array\n${#numbers[@]}            # nb of elements in the array\n\nfor element in ${numbers[@]} ; do\n  ...\ndone\n```\n\n## Stdin, Stdout, Stderr\neach script, function, program has 3 default streams (file descriptors): stdin, stdout and stderr. \nE.g. 'sort' is a program that reads lines of text on stdin and outputs them sorted on stdout, \nand shows any errors it encounters in stderr.\n```bash\n# by default, `stdin` is your interactive terminal, `stdout` and `stderr` are both your terminal\nsort                      # stdin = read from terminal (just type and end with CTRL-D), stdout = terminal\n\u003c input.txt sort          # stdin = read from input.txt, stdout = terminal\n\u003c input.txt sort \u003e sorted.txt  # stdin = read from input.txt, stdout = written to sorted.txt\n\u003c input.txt sort \u003e\u003e sorted.txt # stdin = read from input.txt, stdout = append to sorted.txt\n\u003c input.txt sort \u003e /dev/null   # stdin = read from input.txt, stdout = just ignore it, throw it away\n\necho \"Output\"             # writes \"Output\" to stdout = terminal\necho \"Output\" \u003e\u00262         # writes \"Output\" to stderr = terminal\nprogram 2\u003e /dev/null \u003e output.txt # write stdout to output.txt, and ignore stderr\nprogram \u0026\u003e /output.txt    # redirect both stdout and stderr to output.txt\n\nfind . -name \"*.txt\" \\\n| while read -r file ; do # while read is a good way to run some code for each line\n      output=\"$(basename \"$file\" .txt).out\"\n      [[ ! -f \"$output\" ]] \u0026\u0026 \u003c \"$file\" sort \u003e \"$output\"\n  done\n```\n## Pipes\nThe `|` (pipe) character is bash's superpower. It allows you to build sophisticated chains of programs, \nwhere each passes its `stdout` to the next program's `stdin`. \nIf the [Unix philosophy](https://en.wikipedia.org/wiki/Unix_philosophy) prescribes \n\"_Write programs that do one thing and do it well_\", \nthen bash is the perfect tool to glue all those specialised programs together.\nTo sort, use `sort`, to search, use `grep`, to replace characters, use `tr`; \nto chain all these together, use bash and its pipes.\n\n```bash\nls | sort | head -1       # ls lists filenames to its stdout, which is 'piped' (connected) to sort's stdin. \n                          # sort sends the sorted names to its stdout, which is piped to stdin of 'head -1'.\n                          # head will just copy the first line from stdin to stdout and then stop\n        \n# the following chain will return the 5 most occurring non-comment lines in all .txt files              \ncat *.txt | grep -v '^#' | sort | uniq -c | sort -nr | head -5\n# this line gives a lowercase name for the current script (MyScript.sh -\u003e myscript)\nscript_name=$(basename \"$0\" .sh | tr \"[:upper:]\" \"[:lower:]\")\n```\n## Processes\n```bash\n(                         # ( ... ) starts a new subshell with its own stdin/stdout\ncat *.txt\ncurl -s https://host/archive.txt\n) | sort\n\nstart_in_background \u0026     # start the program and return immediately, don't wait for it to end\n\ngit commit -a \u0026\u0026 git push   # 'git push' will only execute if 'git commit -a' finished without errors\n```\n\n## Error handling\n* `set -uo pipefail`: stop the script when errors happen\n* install [shellcheck](https://github.com/koalaman/shellcheck), ideally in your IDE\n\n## Go and script!\n- use a strong bash boilerplate template like [pforret/bashew](https://github.com/pforret/basher)\n- discover all the [SS64 Linux tools for Bash](https://ss64.com/bash/)\n- [Learn bash in Y minutes](https://learnxinyminutes.com/docs/bash/) \n\n## Advanced Bash\n- [Awesome Bash](https://github.com/awesome-lists/awesome-bash)\n- [Bash pitfalls](https://mywiki.wooledge.org/BashPitfalls)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpforret%2Flearnbashquickly","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpforret%2Flearnbashquickly","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpforret%2Flearnbashquickly/lists"}