-
Notifications
You must be signed in to change notification settings - Fork 0
bugsbugsbux/bash-notes
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
#!/bin/bash --norc # TODO: line-continuation # TODO: exporting # TODO: job control: <https://www.gnu.org/software/bash/manual/html_node/Job-Control-Basics.html> # TODO: shell settings, `set` `shopt` # TODO: getopts # TODO: expanding indirect array # TODO: FIX: simple branching only works if the exit code of the first branch is guaranteed # TODO: patterns: inverse character groups can be written with `!` instead of `^` # TODO: note the difference between regular and extglob patterns # TODO: FIX: patterns: char-range only works with globasciiranges on or while LC_ALL=C; # TODO: patterns: how to specify `[` `-` in char-groups # TODO: patterns: how to specify more than a single named class in char-group # TODO: patterns in [[ are (always) extglob patterns # TODO: missing parameter expansion operators: ^ ^^ , ,, @ # `#` as first character of a word (see below) comments rest of line. # The first line of this file was a comment too, but is also a shebang, a unix # convention, that specifies how to run the file, when executed as # an executable file (`chmod u+x ./file; ./file`). # based on the talk by James Panacciulli available at: # <https://www.youtube.com/watch?v=BJ0uHhBkzOQ> # Rule of bash: Better quote than be sorry! # Terminology: # - word: char-sequence considered a single unit # - list: 1+ commands or pipelines # - name: words matching ^[[:alpha:]_]+[[:alnum:]_]*$ but not just a single underscore # - parameter: stores a value; see: variable # - variable: named parameter, as opposed to numbered or special-char-addressed # - attribute: meta info about a parameter influencing its behaviour, can be set with `declare` # Basic syntax: echo optional arguments # `echo` prints the arguments and a final newline to the screen echo # calls `echo` without arguments; this only prints a newline printf '%s\n' a b c d e # printf's first arg specifies how to combine the rest: each on own line printf '%s\n' "a b" c 'd e' # quotes mark argument boundaries when surrounded by whitespace printf '%s\n' not" 3"' args' name= # set variable name to null (which is a valid value!) name="my value" # do NOT put spaces around `=` printf '%s\n' $name # expand=use variable; equivalent to `printf '%s\n' my value` printf '%s\n' "$name" # expansion in string; equivalent to `printf '%s\n' "my value"` printf '%s\n' '$name' # prevents expansion; equivalent to `printf '%s\n' '$name'` # Advanced topics on variables: # - see also: arrays, associative arrays, scopes/namespaces, arithmetic expressions # - builtins `declare` and `local` create new variables with given attributes in respective scope # - `printf -v name ...` assigns to the given name instead of printing to stdout # - `declare -n pointer=target` creates a new pointer variable (has attribute `n`) which redirects # all actions to the target, thus assigning to a pointer modifies the target # - `unset` removes the given variables, and their targets if they are pointers (has attribute `n`) # - `unset -n pointer` removes a pointer but not it's target # - `local -I name` creates a local copy of name from the outer scope unset name pointer target # declare name=orig target=name # not a real pointer because it doesn't have attribute n printf -v "$target" '%s' "hello world" # write to variable echo "$target;$name;" unset target # as this isn't a real pointer, only unsets `pointer` echo "$target;$name;" # name=orig; declare -n pointer=name # creates real pointer which behaves like the target echo "$pointer;$name;" pointer=new echo "$pointer;$name;" unset pointer # removes both: pointer and its target `name` echo "$pointer;$name;" # name=orig; declare -n pointer=name unset -n pointer # only removes pointer but not it's target echo "$pointer;$name;" # Types of commands (check with command `type`): # - file # - keyword # - function # - builtin # - alias type ls type function function greet { echo hello; } # or `greet() { echo hello;}` type greet type alias alias hi=greet type hi # Getting help: # - type: show type of command # - help: info about *bash* builtins and keywords # - apropos: search man-pages # - man: open man-page of command; number as first arg selects man-section # - info: open help pages in texinfo format; mainly used by gnu programs type kill # prints type and sometimes the definition help kill # only short info about builtins; see `man bash` for more apropos kill man kill # type q to exit man 2 kill # type q to exit info kill # type q to exit declare -p name # print definition of a variable (not function) declare -f name # print definition of a function # Using local definitions on remote: # - this prepends "code" with the definitions of given variables and functions: # `ssh remote "$(declare -p vars; declare -f funcs;) code"` # Accessing arguments (of current function or, when not in a function, of the script): # - `set --` fakes which arguments the current script was called with set -- a b c d e f "g h i" j k l # echo $# # get number of args: 2 echo "$1, $3" # uses first and third arg echo $10 ${10} # nth arg >9 must be specified in braces echo ">${100}<" # missing args default to empty printf '%s\n' "$*" # `*` gives elements as a single string printf '%s\n' "$@" # `@` gives each element, preserving their boundaries when quoted... printf '%s\n' $@ # ...but not when unquoted printf '%s\n' '$@' # obviously single quotes prevent the expansion entirely # Exit/return status/code: # Every command returns an exit code between (incl) 0 and 255 where: # - 0: should indicate success # - 1-255: should indicate different types of failures # Last command's status is available as `$?`. # The builtins `true` and `false` reutrn 0 and 1 respectively, # builtin `:` does nothing and returns 0 true ; echo $? false; echo $? : ; echo $? # Combining commands echo hello ; echo world # same as newline: one after another echo hello & echo world # execute simultaneously; only last stays in foreground echo hello & # run command in background false && echo not happening # only execute last if previous succeeded (status 0) true || echo not happening # only execute last if previous failed (status not 0) # - simple branching: false && echo True || echo False # Note the order: `condition && when-true || when-false` false || echo False && echo True # WRONG ORDER -> executes both "branches" # - `wait` after backgrounded commands avoids receiving outputs at unexpected times: (sleep 5; echo awaken)& echo hello # quickly type: sleep 5; echo interrupted # now, compare to this: (sleep 5; echo awaken)& echo hello ;wait # quickly try to type: echo last # Pipes and Redirections: # - stdout, stderr, stdin are so called "file descriptors" # - commands can write output to stdout and stderr # - outputs of commands can be redirected to files or other file descriptors # - to "pipe" one command into another means to connect the outputs of a command to another's stdin # - some commands detect whether they run in a pipe and then behave differently # - the rhs of a pipe is always a subshell (see below); thus it does not affect the environment # - commands can take stdin from a file # - to ignore an output it is the convention to redirect it to /dev/null # - these examples require `cat` and `rev` which should be widely available: echo hello > /tmp/demo # redirects stdout to file (overrides it) echo world >>/tmp/demo # redirects stdout to file (appends to it) cat < /tmp/demo # read stdin from file error 2>/tmp/demo # redirects stderr to file (use `2>>` to append) cat /tmp/demo echo hello &>/tmp/demo # redirects stdout and stderr to file (overrides it) error &>>/tmp/demo # redirects stdout and stderr to file (appends to it) cat /tmp/demo error 2>&1 # redirects stderr to stdout echo hello | rev # connects stdout of echo to stdin of rev error | rev # stderr still goes to screen and is not received by rev echo hi 2>/tmp/demo >&2 # multiple redirections; stderr>file, stdout>stderr, somehow order matters cat /tmp/demo error 2>&1 | rev # connects stdout to stdin and redirects stderr of `error` to its stdout error |& rev # equivalent error 2>/dev/null # convention for suppressing stderr; redirections don't change return status echo $? name=(); echo -e 'one\ntwo' | while read; do name+=("$REPLY"); done printf '%s\n' "${name[@]}" # unchanged due to subshell not influencing env # instead do this: while read; do name+=("$REPLY"); done < <(echo -e 'one\ntwo') printf '%s\n' "${name[@]}" # If-statements: # - semicolons may be replaced by newlines # - keywords may be place on their own lines # - syntax: # `if COMMANDS; then COMMANDS; [ elif COMMANDS; then COMMANDS; ]... [ else COMMANDS; ] fi` if false then echo "if branch is required" elif false then echo "elif branch is optional" else echo "else branch is optional" fi # Tests: use `help test` and `help [[` # - `test expr` is `[ expr ]` and a builtin thus safe to use even when concerned about portability # - `/bin/test` does the same as `test` but runs as system process # - `[[ expr ]]` are keywords thus process expr differently: # + no word splitting during parameter expansion # + combine conditions with && and || which do not evaluate rhs if lhs suffices # + rhs of == and != is literal when quoted # + rhs of == and != is pattern when unquoted # + rhs of =~ is regex test -n "not nothing"; echo $? test -z "zero length"; echo $? [[ bar == "baz" ]]; echo $? # Pattern matching: # - pattern matching works in # + path-expansions # + unquoted rhs of == and != in [[ # + unquoted cases of case-statements # + some parameter-expansions # - `*` matches any string including null # - `?` matches any single char # - patterns with alternatives: # + `[xyz]` matches x, y or z # + `[^xyz]` matches everything except x, y or z # + `[0-3a-c]` matches 0, 1, 2, 3, a, b or c # + `[[:digit:]]` matches the digits, for allowed classes see: `man 7 regex` echo /* [[ bar == ba[zr] ]]; echo $? # Case-statements: # cases are specified as # - string or pattern # - optionally more strings or patterns each separated with `|` # - followed by `)` # - the case-body, which may start immediately after `)` # case-bodies end with # - `;;` continues execution after the case-statement # - `;&` executes next case-body regardless of whether it's case matches # - `;;&` continues executing the next *matching* case # the entire case statement can be written in one line without the need for a `;` after `in` case word in 'wor[dt]') echo literally 'word[dt]' ;; wor[dt]) echo either german or english for '"word"' ;;& word | wort) echo same but only executes if above match didn\'t terminate with ';;' ;& foobar) echo this case does not match ;; *) echo this case always matches -- but it it executed\? ;; esac # Brace expansions: # - generate combinations and sequences # - useful in for-loops (see below) echo a{X,Y,Z}b echo a{X,Y,Z} {X,Y,Z}b echo a{X,}b a{,X}b echo a{1..3}b a{X..Z}b a{x..z}b echo a{0..10..2} echo {a..z..2} echo a{{1..3},{X..Z}}b echo {1..5}{0,5}% echo twice{,} # Parameter expansion: `${` + name (+ operator + argument) + `}` # - also work with array elements by appending index in brackets to name: `name[0]` # - operators for handling unset (null is not unset) and empty parameters: # + `${name-fallback}`: if unset return "fallback" # + `${name=fallback}`: if unset return "fallback" and assign it to name # + `${name?errormsg}`: if unset throw error with message "errormsg" # + `${name+override}`: if name is set (including empty) return "override" value # - these do the same but treat empty as unset: # + `${name:-fallback}`: if unset or empty return "fallback" # + `${name:=fallback}`: if unset or empty return "fallback" and assign it to name # + `${name:?errormsg}`: if unset or empty throw error with message "errormsg" # + `${name:+override}`: if name is set (unless empty) return "override" value # - operators for string manipulation # + `${name:n}`: rm first n characters # + `${name:n:m}`: rm first n characters, then take next m # + `${name#pattern}`: rm non-greedy pattern from start # + `${name##pattern}`: rm greedy pattern from start # + `${name%pattern}`: rm non-greedy pattern from end # + `${name%%pattern}`: rm greedy pattern from end # + `${name/pattern/str}`: replace first match of greedy pattern with given str # + `${name//pattern/str}`: replace all matches of greedy pattern with given str # + `${name/#pattern/str}`: replace greedy pattern which must be at start with given string # + `${name/%pattern/str}`: replace greedy pattern which must be at end with given string foo=bar ; echo "${foo-fallback}" unset foo; echo "${foo-fallback}" echo "${foo?not set}" # prevents execution of echo name=abcAbc; echo "${name:1}" "${name:0:2}" echo "${name#*b}" "${name##*b}" echo "${name%b*}" "${name%%b*}" echo "${name/*b/.}" "${name/?b/.}" "${name//?b/.}" echo "${name/#?b?/.}" "${name/%?b?/.}" # - operations on variable names: # + `${!name}`: gives value of name contained by variable `name` # + `${!pre*}`: all names starting with "pre" as single string # + `${!pre@}`: all names starting with "pre" as array foo=bar; bar=baz; echo "${!foo}" # does not work with longer redirection chains bar=; baz=; printf '%s\n' "${!ba*}" bar=; baz=; printf '%s\n' "${!ba@}" # Arrays, Parameter expansion continued: # - there are two types of arrays: # + "indexed arrays" # + "associative arrays" # - every variable can be considered an array # - not specifying an index is the same as specifying index 0 # - indices are specified in brackets following the array's name: `name[index]` # - parameter expansions allow specifying an index after the name: `${name[1]?missing element}` # - as `unset name` removes a variable, `unset name[i]` removes an element i from an array; # this does not shift the remaining elements up or down, the index is simply missing # "Indexed arrays", Arrays continued: # - one of the 2 types of arrays # - a sequence of elements, accessible via indices starting at 0 # - assigning to negative indices is invalid # - negative indices access elements counting from the back # - indices are specified in brackets and interpreted as arithmetic expressions # - using array like normal variables accesses element at index 0 # - `declare -a name`: explicitly creates *indexed* array name # - `name[0]=first`: implicitly makes name an indexed array with element at index 0 "first" # - `name=(first second last)`: creates indexed array with 3 elements # - `name=(zero [2]=two three [5]=five six)` is equivalent to # `name=([0]=zero [2]=two [3]=three [5]=five [6]=six)` # - `+=` operator with string on rhs appends to the original value # - `+=` operator with a literal array on rhs modifies the original array like so: # elements before the first use of an explicit index are appended # after the highest used index; then the explicitly and implicitly named elements # are modified which may override elements which were just appended # - `${name[i]}`: (i-1)th element # - `${name[*]}` or `${name[@]}`: all elements disregarding their boundaries # - `"${name[*]}"`: all values in one string separated by "${IFS[0]}" # - `"${name[@]}"`: all values as seperate elements # - `"${!name[@]}"`: all keys as seperate elements # - `"${#name[@]}"`: length # - `"${name[@]:n:m}"`: (try to) get m elements starting from index n # n specifies an index; m is the amount of elements to get not index n+m which is relevant when # there are elements missing unset name name=(first 'second element' last) printf '%s\n' "${name[*]}" printf '%s\n' "${name[@]}" printf '%s\n' ${name[@]} echo keys: "${!name[*]}" echo length: "${#name[@]}" echo length of element 0: "${#name[0]}" or simply "${#name}" unset name name[0]=first name[0+1]=last # arithmetic when assigning echo "${name[1]}" echo "${name[2-1]}" # arithmetic when referencing echo "${name[-1]}" # count from back unset name name=(zero [2]=two three [5]=five six) echo keys: "${!name[*]}" printf '%s\n' "${name[@]}" # when retrieving all, missing elements do not show up *at all* name+=(7 8 [5]=5 6 seven) # first appends elements 7 and 8 then modifies indices 5-7 echo values: "${name[*]}" echo "${name[@]:3:3}" # gets elements at indices 3,5,6 echo "${name[@]:8:2}" # gets <2 elements because there are not enough starting at index 8 # "Associative arrays": # - one of the 2 types of arrays # - a collection of key-value pairs # - the order is not guaranteed # - indices are specified in brackets and *not* interpreted as arithmetic expressions # - `declare -A name`: (capital A!) explicitly creates an *associative* array # - `name=(k1 v1 k2 v2)` is equivalent to # `name=([k1]=v1 [k2]=v2)` # - `+=` operator with string on rhs appends to the original value # - `+=` operator with a literal array on rhs modifies the original array unset name declare -A name=([foo]=fizz [bar]=buzz) echo keys: "${!name[*]}" echo foo is: "${name[foo]}" name+=([bar]=baz [foobar]=fizzbuzz) printf '%s\n' "${name[@]}" declare -A name=(a x b y c z) echo "keys: ${!name[*]}; values: ${name[*]}" # Arithmetic expressions (hereafter abbreviated to aexpr): # - indices of indexed arrays are interpreted as aexprs; see above # - for-loops of the form `for (( aexpr ; aexpr ; aexpr )) ; do ...` use aexprs; see below # - `$((` + aexp + `))`: gives result # - `((` + aexp + `))`: gives truthiness, determined like so: # + true if comparison, (in)equality is true # + *true if* calculated number is *not 0* # - `let 'aexpr'`: evaluates the given aexpr # - of multiple aexpr (separated by `,` in `((` or passed as individual args to `let`) # only the last determines the output and overall result # - parameter expansion is allowed inside aexprs # - bare names can be used instead of dollar notation to expand variables # + the difference: if the variable is empty or unset dollar notation yields # empty which may cause an syntax error, while the bare name yields 0 # - variables are themselves interpreted as aexpr, leading to them being expanded # recursively until their value is an aexpr which evaluates to a number # - follows arithmetic operator precedence (actually the one of C) # - floating point numbers are not allowed, factional results are floored! # - aexpr do not have their own scope, assignments affect the respective/outer scope # - assignemnts can be combined with an operator: `x-=1` is `x=x-1` # - `name++` or `name--`: inside aexpr inc-/decrements the value of name after evaluating the aexpr # - `++name` or `--name`: like `name++` or `name--` but reassigns name *before* evaluating the aexpr declare name=foo foo=2 bar echo $((name * 1)) # 2 echo $(("$name" * 1)) # 2 echo $((bar * 1)) # 0 echo $(("$bar" * 1)) # error: $(( * 1)) is missing left arg of * name=bar echo $((name * 1)) # 0 echo $(("$name" * 1)) # 0 # redirection to unset/empty var yields 0 # echo $(( 2+3*4 )) # mathematical precedence echo $(( (2+3)*4 )) # use parens to influence order echo $((11/0)) # division by zero error echo $((11/3)) # no floating point values allowed! results are floored echo $((2**3)) # power echo $((5%2)) # modulo name=10; echo $(($name/3)) if ((1>2 || 0**(10%2) )); then echo True; else echo False; fi name=0; echo $((name++)); echo $name name=0; echo $((--name)); echo $name # Loops: # - semicolons may be replaced by newlines # - keywords may be place on their own lines # - keyword `break` can be used to exit loops early # - keyword `continue` can be used to abort the current iteration and continue with the next # - syntax: # + `while COMMANDS; do COMMANDS-2; done` # + `until COMMANDS; do COMMANDS-2; done` # + `for NAME [in WORDS ... ] ; do COMMANDS; done` # + `for (( AEXPR ; AEXPR ; AEXPR )); do COMMANDS; done` i=0; while read -p"in: " do echo "out: $REPLY" if ((++i>=2)); then break; fi done i=-1; while ((++i < 5)); do echo $i; done i=-1; until ((++i >= 5)); do echo $i; done for i in {0..4}; do echo $i; done for (( i=0; i<5; i++)); do echo $i; done # + `select NAME [in WORDS ... ;] do COMMANDS; done` # prints each word with it's index to stderr; then waits for choice on stdin # the given variable NAME is set to the corresponding word of a valid choice # the variable `REPLY` is set to the line read from stdin # after executing the body it prompts for new input unless `break` was encountered # user can exit the loop by hitting ctrl+c or ctrl+d # *try the example once with input `2` and once with `2 3`* select choice in foo bar baz; do echo "$choice; $REPLY"; break; done select choice in foo bar baz; do echo "$choice; $REPLY"; done # Command groups: # - commands may be grouped to redirect them at once # - there are two types of such groups: # + subshells, denoted by `( list )`, create their own environment derived from the outside one. # This means they do not influence the surrounding environment. # + brace surrounded groups, which do not create their own environment and thus influence the # surrounding one. To differentiate them from brace expansions (see above), they must start # with whitespace after the opening `{` and end with a semicolon before the `}` if they do # not contain multiple commands and are one-liners. name=outside ( echo inherits outside environment: $name name=inside echo redefined name: $name ) echo "but does not influence the surrounding environment: $name" { name=inside; } echo "brace surrounded groups do influence the environment: $name" { echo "${name?undefined}" echo "${foobar?undefined}" } 2>/dev/null # throw away stderr output # Subshell substitution, Process substitution: # - to replace a subshell expression with it's output, prepending it with `$` # quote the substitution to receive a single argument, dont to split it into words # - the subshell expression of a quoted subshell substitution does not need to escape quotes # - to treat a subshell expression as a file, prepend it with `<` or `>` # - `<()` is used when one wants to read stdout of the subshell as a file # - `>()` is used when one wants to write to a file which shall be used as stdin of the subshell echo today is "$(echo "now")" # not the unescaped quotes in the quoted substitution cat <(echo hello; echo world) # `cat` expects a filename as argument echo hello world > >(rev) # `tee` writes stdin to the given filename and outputs it to stdout echo hello | tee >(rev >/tmp/demo) | cat cat /tmp/demo # Functions, Compound expressions, Scopes/Namespaces: # - compound expressions are: subshells, brace surrounded expressions, arithmetic expressions, # [[ expressions, loops and conditional # - syntax: # + `function NAME COMPOUND` the name may be followed by `()` # + OR: `NAME () COMPOUND` # + redirections directly following a function definition become part of the definition # - only subshells create a new scope/namespace, all other compound expressions use the surrounding # namespace except for: # + the argument-variables ($1, $2, ..., $*, $@, $#) which always reference the arguments of the # current function # Note: $0 does not hold the name of the current function! # + variables after being declared as local using the `local` builtin instead of `declare` # Note: `local` allows the same options as `declare` # - `return` exits the current function with the given (or 0) exit code # - a variable and a function with the same name can exist at the same time declare name=outside error123 () { echo not the function name: $0 echo first arg: $1 name=inside # modifies outside name because {}-body shares surrounding namespace local name="lol" # using `local` shadows the outside name echo $name return 123 # set return status and exit function } >&2 # redirect stdout to stderr declare -f error123 # shows that the redirection is part of the definition error123 message >/tmp/demo_stdout 2>/tmp/demo_stderr echo $? # successfully failed with code 123 cat /tmp/demo_stdout # empty since all output went to stderr cat /tmp/demo_stderr # contains even what originally went to stdout echo $foo # not available anymore unset error123 # remove function # using a subshell as body: unset name; function f ( name="contained to subshell"; ); f; echo ${name-name not set} # function names are not variable names: declare name=variable; function name () { echo "i'm not a $1"; } declare -p name declare -f name name $name
About
No description, website, or topics provided.
Resources
Stars
Watchers
Forks
Releases
No releases published
Packages 0
No packages published