{"id":20134581,"url":"https://github.com/gnebbia/bash_scripting_tutorial","last_synced_at":"2025-03-02T22:28:07.752Z","repository":{"id":129498266,"uuid":"86557579","full_name":"gnebbia/bash_scripting_tutorial","owner":"gnebbia","description":"A personal tutorial for Bash scripting","archived":false,"fork":false,"pushed_at":"2020-03-27T20:56:06.000Z","size":53,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-01-13T09:27:31.263Z","etag":null,"topics":["bash","notes","scripting","tutorial"],"latest_commit_sha":null,"homepage":"","language":"HTML","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/gnebbia.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":"2017-03-29T08:33:15.000Z","updated_at":"2022-12-19T11:11:48.000Z","dependencies_parsed_at":"2023-04-05T03:15:33.655Z","dependency_job_id":null,"html_url":"https://github.com/gnebbia/bash_scripting_tutorial","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gnebbia%2Fbash_scripting_tutorial","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gnebbia%2Fbash_scripting_tutorial/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gnebbia%2Fbash_scripting_tutorial/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gnebbia%2Fbash_scripting_tutorial/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gnebbia","download_url":"https://codeload.github.com/gnebbia/bash_scripting_tutorial/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241581178,"owners_count":19985710,"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","notes","scripting","tutorial"],"created_at":"2024-11-13T21:10:06.189Z","updated_at":"2025-03-02T22:28:07.743Z","avatar_url":"https://github.com/gnebbia.png","language":"HTML","readme":"# Bash Scripting\n\nIn order to start a script in bash we do:\n```sh\n#!/usr/bin/env  bash\nprintf \"%s\" \"Hello World\"\n```\nFirst rule, even if online we see plenty of examples with `echo`, we should  always use `printf` instead of the famous `echo` command.\n\n## Good Rules for Bash Scripting\n\nHere some good rules when writing Bash:\n\n* Always prefer printf to echo\n* Quote every variable, always, except when we want to compress spaces\n* Don't expect speed in Bash scripts, but if you want to optimize prefer builtins instead of external prograsm\n* Don't use cat but do this: `content=$(\u003c fileName)`\n* Always use the new test command [[ instead of the old test [\n* Use \"set +x\" and \"set -x\" to enable/disable debugging mode\n* Use \"set -r\" to set restricted mode, this can be useful for security reasons\n\n## Variable Types\n\nAlthough BASH is not a typed language, it does have a few different types of variables.\nThese types define the kind of content they have. They are stored in the variable's\nattributes.\nAttributes are settings for a variable. They define the way the variable will behave. Here\nare the attributes you can assign to a variable:\n\n* Array: declare -a variable: The variable is an array of strings.\n* Associative array: declare -A variable: The variable is an associative array of strings (bash 4.0 or higher).\n* Integer: declare -i variable: The variable holds an integer. Assigning values to this variable automatically triggers Arithmetic Evaluation.\n* Read Only: declare -r variable: The variable can no longer be modified or unset.\n* Export: declare -x variable: The variable is marked for export which means it will be inherited by any subshell or child process\n\necho \"$BASH_VERSION\" : Contains a string describing the version of BASH\n\n\n## Control Structures\n\nThere are different control structures as in general programming languages:\n\n### If Conrol Structure\n```sh\nif [[ condition ]]; then\n    ...\nfi\n```\nor:\n```sh\nif ((a\u003cb)); then\n    ...\nfi\n```\nExample of more complex if statement:\n```sh\nif [[ condition ]]; then\n    ...\nelif [[ condition ]]; then\n    ...\nelse\n    ...\nfi\n```\n### For Conrol Structure\n\n```sh\nfor [[ condition ]]; do\n    ...\ndone\n```\n```sh\nfor ((i=0;i\u003c10;i++)); do\n    ...\ndone\n```\n```sh\nfor my_var in {1..100}; do\n    ...\ndone\n```\n\nThe for loop can be useful to loop on files, but notice that we should not make use of the ls command (as seen in many scripts online), let's make an example:\n```sh\nfor my_file in *.tar.gz; do\n    tar -xvzf \"$my_file\";\ndone\n```\nTo loop over the fields of an array we do:\nmy_array=(mam dad example another string)\n```sh\nfor x in \"${my_array[@]}\"; do\n    printf \"$x\";\ndone\n```\n\n### Ternary Operator\n\nBash supports ternary operators for variable assignments.\n\n```sh\n${varname:-word} # if varname exists and isn't null, return its value; otherwise return word\n${varname:=word} # if varname exists and isn't null, return its value; otherwise set it word and then return its value\n${varname:+word} # if varname exists and isn't null, return word; otherwise return null\n${varname:offset:length} # performs substring expansion. It returns the substring of $varname starting at offset and up to length characters\n```\n\n### Case Control Structure\n```sh\ncase $LANG in\n    en*) echo 'Hello!' ;;\n    fr*) echo 'Salut!' ;;\n    de*) echo 'Guten Tag!' ;;\n    nl*) echo 'Hallo!' ;;\n    it*) echo 'Ciao!' ;;\n    es*) echo 'Hola!' ;;\n    C|POSIX) echo 'hello world' ;;\n    *) echo 'I do not speak your language.' ;;\nesac\n```\n\nlet's see another example:\n```sh\nread ans\n\ncase \"$ans\" in\n#List patterns for the conditions you want to meet\n    [nN]) echo \"NO\";;\n    [yY]) echo \"YES\";;\n    *) echo \"Wrong input\";;\nesac\n```\n\n\n### Bash Program General Structure\n\n```sh\n#!/usr/bin/env  bash\n#\n# AUTHORS, LICENSE AND DOCUMENTATION\n#\nset -e -o pipefail\n# READONLY VARIABLES HERE\n# GLOBAL VARIABLES HERE\n\n# IMPORT EXTERNAL SOURCE CODE (DOTTING FILES)\n# FUNCTIONS (USE LOCAL VARIABLES AND RETURN EITHER A CODE OR A STRING)\n\n# MAIN FUNCTION:\n    # OPTION PARSING\n    # LOG FILE AND SYSLOG HANDLING\n    # TEMPORARY FILES AND NAMED PIPE HANDLING\n    # SIGNAL TRAPS\nmain \"$@\"\n```\n\n## Conditionals and Expressions\n\n```sh\nstatement1 \u0026\u0026 statement2  # both statements are true\nstatement1 || statement2  # at least one of the statements is true\n\nstr1=str2       # str1 matches str2\nstr1!=str2      # str1 does not match str2\nstr1\u003cstr2       # str1 is less than str2\nstr1\u003estr2       # str1 is greater than str2\n-n str1         # str1 is not null (has length greater than 0)\n-z str1         # str1 is null (has length 0)\n\n-a file         # file exists\n-d file         # file exists and is a directory\n-e file         # file exists; same -a\n-f file         # file exists and is a regular file (i.e., not a directory or other special type of file)\n-r file         # you have read permission\n-s file         # file exists and is not empty\n-w file         # you have write permission\n-x file         # you have execute permission on file, or directory search permission if it is a directory\n-N file         # file was modified since it was last read\n-O file         # you own file\n-G file         # file's group ID matches yours (or one of yours, if you are in multiple groups)\n\nfile1 -nt file2     # file1 is newer than file2\nfile1 -ot file2     # file1 is older than file2\n\n-lt     # less than\n-le     # less than or equal\n-eq     # equal\n-ge     # greater than or equal\n-gt     # greater than\n-ne     # not equal\n```\n\n\n## Debugging with bash\n\nLet's see the meaning of some debugging techniques with bash.\n\nIn Bash scripts, use set -x (or the variant set -v, which logs raw input,\nincluding unexpanded variables and comments) for debugging output. Use\nstrict modes unless you have a good reason not to: Use set -e to abort on\nerrors (nonzero exit code). Use set -u to detect unset variable usages.\nConsider set -o pipefail too, to on errors within pipes, too (though read up\non it more if you do, as this topic is a bit subtle). For more involved\nscripts, also use trap on EXIT or ERR. A useful habit is to start a script\nlike this, which will make it detect and abort on common errors and print a\nmessage:\n\n```sh\nset -eo pipefail \ntrap \"echo 'error: Script failed: see failed command above'\" ERR\n```\n\nWe can also use some flags with bash to debug a script.\n\nIndeed we can easily debug the bash script by passing different options to bash\ncommand. For example -n will not run commands and check for syntax errors only.\n-v echo commands before running them. -x echo commands after command-line\nprocessing.\n\n```sh\nbash -n scriptname\nbash -v scriptname\nbash -x scriptname\n```\n\n## Executing Commands in Shell Scripts\n\nIn order to execute a command and save its stdout we should always use $() and not backticks \\`\\`, \nthis for two reasons mainly:\n    - $() allows for nested commands, while backticks do not without escaping\n    - $() is POSIX portable and compliant, while backticks do not\n\n## Functions \nIt is a good idea for scripts larger than a screen page to build functions, \nand make our program more modular as possible.There are no return values in bash, \nso we can return values by printing a string like:\n\n```sh\nmy_function_name() {\n    ...\n    printf \"%s\" \"$return_value\"\n}\n```\nAlso consider that when we write modules our function name should be like:\n```sh\nmy_module_name::my_function_name() {\n    ...\n    printf \"%s\" \"$return_value\"\n}\n```\n\nall the functions related to a module should be contained inside the relative module file, in the last case the function would be contained in \"my_module_name.sh\".\n\nTo refer to all arguments passed to a function we can use \"$@\", and to remove the first argument we can use \"shift\", this is similar to Perl.\n\n\n## Advanced Redirection\n\nThere are more advanced redirection operators from newer versions of bash, the operators are:\n    - `\u003c\u003c`\n    - `\u003c\u003c\u003c`\n    - `\u003c()`\n    - `\u003e()`\nlet's see some use cases in order to explain these:\n```sh\ncat fileName | tr ' ' '\\n'\n```\nan alternative to this notation is a simpler:\n```sh\ntr ' ' '\\n' \u003c fileName\n```\nso we can use `\u003c` to redirect the stdin to an actual file.\nWhat if the content on which we want to apply our tr is not in a file but in a variable ? Well, this is the case for `\u003c\u003c\u003c`, let's see an example:\n```sh\ncontent=\"this is an example string\"\ntr ' ' '\\n' \u003c\u003c\u003c \"$content\"\n```\nAnother example can be:\n\n```sh\nwc -w \u003c\u003c\u003c \"$( sed -n '1p' words )\"\n```\n\nnow let's consider another scenario, let's say we want to get a diff between two files, we would normally do:\n```sh\ndiff -u file1 file2\n```\nhow we could apply a diff on the output of another command instead of a file ?\nWell we could first save the command output to a file for both command and then use those files, but there is a syntactic faster solution:\n\n```sh\ndiff -u \u003c(ls) \u003c(ls anotherDir/)\n```\n\nNow let's see the `\u003c\u003c` HEREDOC or HERESTRING (a heredoc with one string) example,\nthis is useful when we want to create strings,\nthe most common use of heredocs is dumping documentation, for example:\n\n```sh\nusage() {\n    cat \u003c\u003cEOF\nusage: foobar [-x] [-v] [-z] [file ...]\nA short explanation of the operation goes here.\nIt might be a few lines long, but shouldn't be excessive.\nEOF\n}\n```\n\nWe can have better indentation with `\u003c\u003c` accompanied by `-` format, for example:\n\n```sh\nusage() {\n    cat \u003c\u003c-EOF\n    usage: foobar [-x] [-v] [-z] [file ...]\n    A short explanation of the operation goes here.\n    It might be a few lines long, but shouldn't be excessive.\n    EOF\n}\n```\nAnother example with herestring but using `\u003c\u003c\u003c` is by using:\n\n```sh\ngrep proud \u003c\u003c\u003c\"I am a proud sentence\"\n```\n\n## Pattern Matching\n\nIn Bash we have basically three way for pattern matching\n  - Globs (used for files), e.g., * to match a range of files\n  - Extended Globs (used for files), this has to be enabled with shopt -s extglob\n  - Regex (used for strings), used with the =~ operator\n\n## Globs\n\nLet's see some example, where we can use globs to select multiple files:\n\n```sh\nfiles=(*) #selects all the files in the current directory\nfiles=(../*.tar.gz) #selects all the .tar.gz files in the previous directory\n```\n\n\n## Extended Globs Examples\n\nOnce we have enabled extended globs with:\n\n```sh\nshopt -s extglob\n```\n\nwe can tell things such as \"do this for all files except this\" or \n\"do this for this file or this file\" and so on, to a certain degree \nextended globs and regex are interchangeable in terms of capabilities. \nLet's see some example:\n\n```sh\n#remember that the * glob doesn't match hidden files, but .* matches only hidden files...\nshopt -s extglob\nrm !(fileName) #this removes all files in current directory except fileName\nrm !(file1|file2) #this removes all files in current directory except the two mentioned files\nrm !([bc]) #removes all but b and c\nrm !(*.txt) #removes all files except those ending with .txt\nls !(*jpg|*bmp)\n```\n\n\n## Regex\n\nLet's see an example of regex:\n\n```sh\n# Regex check is done within\nmyregex='co[ao]l'\nif [[ \"cool\" =~ $myregex ]]; do\n\techo \"matched\"\nelse\n\techo \"not matched\"\nfi\n```\n\n\n## Temporary Files\n\nOccasionally we need to create temporary files to store data, in order to do this we can do:\n\n```sh\nmy_file=$(mktemp)\nprintf \"%s\" \"ciao\" \u003e \"$my_file\"\nrm \"$my_fil\"ed\n```\n\n\n## Looping and Parsing Data\nWhen we loop over data (either from a file or a variable) it is good practice to use while and use the IFS field separato variable, let's see some examples:\n\n```sh\n# loop through a file delimited by newlines\nwhile IFS=$'\\n' read -r line; do\n\techo \"$line\"\ndone \u003c $file_name\n```\n\n```sh\n# loop through a variable delimited by newlines:\nwhile IFS=$'\\n' read -r line; do\n\techo \"$line\"\ndone \u003c\u003c\u003c $variable_name\n```\n```sh\n# loop through fields of an array:\nfor field in \"${myarray[@]}\"; do\n\techo \"$field\"\n\techo \"\\n\"\ndone\n```\n```sh\n# Insert data separated by blanks in an array and loop through them\ndata=\"gatto cane mamma papa lol\"\nread -a myarray \u003c\u003c\u003c $data; \nfor field in \"${myarray[@]}\"; do\n\techo \"$field\"\n\techo \"\\n\"\ndone\n```\n```sh\n# Loop through fileds separate by another character such as comma:\nlol=\"ciao,mamma,papa,gatto,cane\"; \nIFS=, \nread -a myarray \u003c\u003c\u003c \"$lol\"\n```\nNotice that if we have some strange combinations of characters as separators, \nit is generally a good idea to convert this combo with string substitution with another character such as comma, and then separate with IFS.\n\n```sh\n# loop through lines and put the first column into first_name, the second column into last_name and third column into phone variables.\nwhile read -r first_name last_name phone; do\n  # Only print the last name (second column)\n  printf '%s\\n' \"$last_name\"\ndone \u003c \"$file\"\n```\nRemember that when dealing with arrays, @ expands multiple arguments, while * concatenates them, so never do for field in `\"${array[*]}\"` !\n\nWe can also read an array from the stdin where each element is separated\nby a newline by doing:\n```sh\ni=0\nwhile read line\ndo\n    array[$i]=$line\n    ((i+=1))\ndone\n\necho ${array[@]}\n```\n\n## String and Arrays Manipulations\nLet's see some example of string manipulation which will allow us to use builtins instead of awk, sed or perl, in order to gain some speed and not create subprocesses.\n\n\n```sh\n#Let's see how to create an array\nnames=(\"Bob\" \"Peter\" \"$USER\" \"Big Bad John\")\n\n#Now we create a normal string\nexample=\" this is an example how do you do ?\"\n\nmyarr=($example) #this creates an array from a string\n\necho \"${myarr[@]:0:4}\" #prints only from element 0 and gets 4 elements from that\necho \"${myarr[@]:2:4}\" #prints only from element 2 and gets 4 elements from that\n\n\necho \"${#Unix[@]}\" #prints the number of elements\n\necho \"${#Unix[3]}\" #prints the length of the 4th element of the array\n\necho \"${example/d/D}\" #substitutes the first occurrence of d with D\n\necho \"${example//d/D}\" #substitutes all the occurrences of d with D \n\necho \"${example#*d}\"  #deletes from left until the first occurrence of d included\necho \"${example##*d}\" #deletes from left until the last occurrence of d included\n\necho \"${example%d*}\" #deletes from right until the first occurrence of d included\necho \"${example%%d*}\" #deletes from right until the last occurrence of d included\n\n# example, given the string:\nexample=\"this is an example, string with racecars and dragons\"\n#let's see what the following commands will output\n\necho \"${example#*c}\"   #ecars and dragons\necho \"${example##*c}\"  #ars and dragons\necho \"${example%c*}\"   #this is an example, string with race\necho \"${example%%c*}\"  #this is an example, string with ra\n\necho \"${example:3}\"   # Remove the first three chars (leaving 4..end)\necho \"${example::3}\"  # Return the first three characters\n\n#Let's see still some other example:\nVAR=foofoobar\n${VAR/foo/bar} # barfoobar\n${VAR//foo/bar} # barbarbar\n${VAR//foo} # bar\n```\nLet's see an example where we have a one line separator \nand put it in array the separated fields into an array\n\n```sh\nIFS=. read -a ip_elements \u003c\u003c\u003c \"127.0.0.1\"\n```\n\nnow i can print the various elements with\n```sh\nprint \"${ip_elements[*]}\"\n```\nor we can even print them singularly with:\n```sh\nprintf \"%s\\n\" \"${ip_elements[3]}\"\nprintf \"%s\\n\" \"${ip_elements[2]}\"\nprintf \"%s\\n\" \"${ip_elements[1]}\"\nprintf \"%s\\n\" \"${ip_elements[0]}\"\n```\n\n\n### String Subbstitution Examples\n\n`${variable#pattern}` # if the pattern matches the beginning of the variable's value, delete the shortest part that matches and return the rest\n`${variable##pattern}` # if the pattern matches the beginning of the variable's value, delete the longest part that matches and return the rest\n`${variable%pattern}` # if the pattern matches the end of the variable's value, delete the shortest part that matches and return the rest\n`${variable%%pattern}` # if the pattern matches the end of the variable's value, delete the longest part that matches and return the rest\n`${variable/pattern/string}`  # the longest match to pattern in variable is replaced by string. Only the first match is replaced\n`${variable//pattern/string}` # the longest match to pattern in variable is replaced by string. All matches are replaced\n`${#varname}` # returns the length of the value of the variable as a character string\n\n\n## Associative Arrays\n\nWe can create an associative array and create some key-value pair with the following code:\n\n```sh\ndeclare -A my_assoc\nmy_assoc[name]=\"giuseppe\"\nmy_assoc[surname]=\"nebbione\"\n```\n\nnotice that as with normal arrays we can append an element to the associative array with the\nnotation \"+=\":\n\n\n```sh\nvar=\"random string\"\naa=([hello]=\"world\")\naa+=([b]=\"$var\")           # aa now contains 2 items\n```\n\nThe -A option declares aa to be an associative array. Assignments are then made \nby putting the \"key\" inside the square brackets rather than an array index. \nWe can also assign multiple items at once:\n\n```sh \ndeclare -A my_assoc\nmy_assoc=([name]=\"giuseppe\" [surname]=\"nebbione\" [address]=\"Italy, Napoli\")\n```\n\nLet's see some use case:\n```sh\nif [[ ${my_assoc[name]} == \"giuseppe\" ]]; then\n    printf \"%s\" \"They are equal\"\nelse\n\tprintf \"%s\" \"They are not equal\"\nfi\n```\n\nwe can even use variables as keys, for example, this is possible and valid:\n\n```sh\nkey=\"surname\"\nbb=${my_assoc[\"$key\"]}\n```\n\nin order to loop over the keys of an associative array we do:\n\n```sh\nfor key in \"${!my_assoc[@]}\"; do\n\tprintf \"%s\" \"$key\"\ndone\n```\n\n\n## Bash Math\nBash alone isn't capable of doing floating point math, in order to do that either\nuse awk or bc, let's see now some example of integer math with pure bash:\n```sh\ncount=$(( $count + 1 ))\n```\n\nAnother example:\n```sh\nread x\nread y\n\necho \"$((x + y))\"\necho \"$((x - y))\"\necho \"$((x * y))\"\necho \"$((x / y))\" # returns the integer part of the division\necho \"$((x % y))\" # returns the module of division\n\n# notice that it is equivalent to write $x or x inside the double parenthesis\n```\n\nWe can do also floating point math by using bc, let's be sure to call\nbc with the `-l` flag which allows for long precision math and other\nmath library functions. Let's see an example:\n```sh\n# read an arbitrary math expression\nread expression\n\n# perform the operation with bc -l\nres=$(bc -l \u003c\u003c\u003c\"$expression\")\n\n# round the result to three digits\nprintf \"%.3f\" \"$res\"\n```\n\nWe can also round a result a priori (this is also a good choice)\nby instructing the desired precision to bc, for example:\n```sh\n# read an arbitrary math expression\nread expression\n\n# perform the operation with bc -l and specifying the precision\nres=$(bc -l \u003c\u003c\u003c\" scale=3; \"$expression\" \")\n\n# print the rounded result to three digits\nprintf \"%s\" \"$res\"\n```\n\nAnother example, is X greater than Y?\n```sh\nread x\nread y\n\nz=\"$((x - y))\"\n\nif  ((z == 0)); then\n    echo \"X is equal to Y\"\nelif ((z \u003e 0 )); then\n    echo \"X is greater than Y\"\nelse\n    echo \"X is less than Y\"\nfi\n```\n\nWe can also build more complex conditions by doing:\n```sh\nread x\nread y\nread z\n\nif (( (x==y) \u0026\u0026 (x==z) ));then\n    echo \"EQUILATERAL\"\nelif (( (x!=y) \u0026\u0026 (y!=z) \u0026\u0026 (x!=z)  ));then\n    echo \"SCALENE\"\nelse\n    echo \"ISOSCELES\"\nfi\n```\n\n\n\n## Command groups\n\nCommand groups can be used to run multiple commands and have a single redirection\naffect all of them:\n\n```sh\n{ echo \"Starting at $(date)\"; rsync -av . /backup; echo\n\"Finishing at $(date)\"; } \u003e backup.log 2\u003e\u00261\n```\n\nCommand groups are also useful to shorten certain common tasks:\n\n```sh\n[[ -f $CONFIGFILE ]] || { echo \"Config file $CONFIGFILE not found\" \u003e\u00262; exit 1; }\n```\n\n## File Redirection and Process Substitution\n\nLet's see how to use the powerful find command to make an array of files\n```sh\nfiles=()\nwhile read -r -d $'\\0' line; do\n\tfiles+=(\"$line\")\ndone \u003c \u003c(find /foo -print0)\n\n# notice that is is essential to use print0 in this find command\n# in order to be able to produce a list which can be read by `read -r`\n```\n\nWe can also exploit the full capabilities of find, for example let's say\nwe want to search recursively all .py files but exclude certain directories\nfrom our search, we can do that with this:\n\n```python\nfiles=()\nwhile read -r -d $'\\0' file; do\n    printf \"%s\\n\" \"$file\"\n    files+=(\"$file\")\ndone \u003c \u003c(find -name \"*.py\" -not -path \"./docs/*\" -and -not -path \"./env/*\" -print0)\n\nprintf \"%s\\n\" \"done\"\n```\n\n## Heredocs and Herestrings\n\nWhy they exist: Sometimes storing data in a file is overkill. We might only have a tiny bit of it -- enough\nto fit conveniently in the script itself. Or we might want to redirect the contents of a\nvariable into a command, without having to write it to a file first.\n\n#### interpolate output of commands \n\n printf 'The date is: %s\\n' \"$(date)\"\n\n\n## Pipelines\nPipes are a very attractive means of post-processing application output. You\nshould, however, be careful not to over-use pipes. If you end up making a\npipeline that consists of three or more applications, it is time to ask yourself\nwhether you're doing things a smart way. You might be able to use more\napplication features of one of the post-processing applications you've used\nearlier in the pipe. Each new command in a pipeline causes a new subshell\nand a new application to be loaded. It also makes it very hard to follow the\nlogic in your script!\n\n\n## Bash Shortcuts\nA list of common shortcuts when dealing with bash command line:\n\n\n* `CTRL+a`  # move to beginning of line\n* `CTRL+b`  # moves backward one character\n* `CTRL+c`  # halts the current command\n* `CTRL+d`  # deletes one character backward or logs out of current session, it is similar to exit\n* `CTRL+e`  # moves to end of line\n* `CTRL+f`  # moves forward one character\n* `CTRL+g`  # aborts the current editing command or search mode and rings the terminal bell\n* `CTRL+j`  # same as RETURN\n* `CTRL+k`  # deletes from the cursor to end of line\n* `CTRL+l`  # clears screen \n* `CTRL+m`  # same as RETURN\n* `CTRL+n`  # next line in command history\n* `CTRL+o`  # same as RETURN, then displays next line in history file\n* `CTRL+p`  # previous line in command history\n* `CTRL+r`  # searches backward\n* `CTRL+s`  # searches forward\n* `CTRL+t`  # swaps two characters\n* `CTRL+u`  # removes backward from cursor to the beginning of line\n* `CTRL+v`  # makes the next character typed verbatim\n* `CTRL+w`  # removes the word behind the cursor\n* `CTRL+x`  # lists the possible filename completions of the current word\n* `CTRL+y`  # retrieves (yank) last item killed\n* `CTRL+z`  # stops the current command, resume with fg in the foreground or bg in the background\n* `CTRL+xx` # Toggle between the start of line and current cursor position\n* `CTRL+_`  # undo\n* `ALT+. `  # cycles through previous arguments\n* `ALT+#`   # useful whenever we want to save the command in history, makes the current command a comment\n* `ALT+f`   # moves forward one word\n* `ALT+b`   # moves backward one word\n* `ALT+d`   # deletes a word\n* `ALT+r`   # cancel the changes and put back the line as it was in the history (revert)\n* `ALT+t`   # swaps two words\n* `^ab^de`  # run previous command, replacing ab with de\n* `!str`    # repeat the last command which was starting with \"str\"\n* `!str:p`  # prints the last command starting with \"str\"\n* `!str:2`  # second argument to last command starting with \"str\"\n* `!!`      # repeat last command\n* `!*`      # all arguments of previous command\n* `!$`      # last argument of previous command\n* `!n:p`    # print last command starting with n\n\n\n\n### References\n* [Bash_Reference] - Bash Reference!\n\n\n## TODO\n* Integrate from here [Bash Tutorial](https://github.com/denysdovhan/bash-handbook)\n* Insert more snippets of code which accomplish specific functionalities\n\nLicense\n----\n\nGPLv3\n\n[Bash_Reference]: \u003chttp://mywiki.wooledge.org/\u003e\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgnebbia%2Fbash_scripting_tutorial","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgnebbia%2Fbash_scripting_tutorial","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgnebbia%2Fbash_scripting_tutorial/lists"}