diff --git a/bash.txt b/bash.txt new file mode 100644 index 0000000000000000000000000000000000000000..d9a3335f682992a09b0a414ce41776f6d051b418 --- /dev/null +++ b/bash.txt @@ -0,0 +1,348 @@ +Bash Quick-Reference +==================== + +The bash man page is very long and detailed, with the result that +it can be difficult to find what you're looking for in it. + +Variables +--------- + +There are many shell variables that can come in handy, both +interactively and in scripts. We typically write them with a dollar +sign in front, because that is how they are referenced (but not +set!). + +| *Variable* | *Contents* | +| ---------- | ---------- | +| $$ | Current process (shell) PID | +| $! | PID of last subprocess started in the background | +| $? | Return value of the last completed subprocess | +| $0 | Name with which the shell/script was invoked | +| $1, $2, ... | Positional parameters to the shell/script | +| $# | Number of positional parameters to shell/script | +| $@ | Positional parameters, expanded as separate words | +| $* | Positional parameters, expanded as a single word | +| $HOME | The current user's home directory | +| $OLDPWD | The previous working directory (see "cd -") | +| $PATH | The directories searched for commands | +| $PPID | The PID of the shell/script's parent process | +| $PWD | The current working directory | +| $RANDOM | A random int in the range [0,32767] | +| $EUID | The current effective user numeric ID | +| $UID | The current user numeric ID | +| $USER | The current username | + +You set a variable with + + my_var=123 + +Referencing variables is done with a dollar sign, but there is more +than one way to do this. These are equivalent: + + $HOME + ${HOME} + +Why would we use the longer version? Try the following: + + for f in *; do echo $f0; done + +Now try: + + for f in *; do echo ${f}0; done + +The braces give us considerable more control, and some extra features, +as we'll see. + + +Parameter Expansion +------------------- + +See the "Parameter Expansion" section of the bash manpage for more +details and other expansion options. + +| *Expansion* | *Effect* | +| ----------- | -------- | +| ${#var} | The length of *var* | +| ${var:-def} | *var*, if set, otherwise *def* | +| ${var:=def} | As above, but *var* will be set to *def* if not set | +| ${var:off} | Substring of *var*, beginning with character *off* | +| ${var:o:l} | As above, but at most *l* characters | +| ${var/p/s} | Expand *var*, replace pattern *p* with string *s* | +| | If *s* not provided, remove the pattern | +| | # and % perform prefix and suffix matches | + + +Quoting +------- + +How a language handles single- vs double-quotes varies quite a bit. +Python treats them equivalently, while C only allows a single +character between single-quotes. Bash works a bit differently: +single-quoted strings do not have parameters expanded, while +double-quoted strings do. For example, compare: + + echo '${HOME}' + echo "${HOME}" + +You will most often want double-quotes, but single-quotes are very +useful when preparing input to another program. For example: + + find . -name '*.txt' + +which is equivalent to + + find . -name \*.txt + + +Command Execution +----------------- + +There are multiple ways to do this. Consider an executable `foo`: + +| *Invocation* | *Effect* | +| ------------ | -------- | +| `foo` | foo is run as a subprocess normally | +| `foo &` | foo is run in the background, as execution continues | +| `` `foo` `` | foo is run as a subprocess, and its STDOUT is returned | +| `$(foo)` | Same as the above | + +The last two are equivalent, but the $() form is preferable, because +it is clearer and can be nested. + + +Working with Positional Parameters +---------------------------------- + +Sometimes, $* or $@ are good enough: + + foo $* # Pass all parameters to this other command + for a in $*; do ... # Loop over the parameters + +If the parameters have different meanings, we can do the following: + + a=$1 + b=$2 + +We can make this more robust, with defaults: + + a=${1:-foo} + b=${2:-bar} + +We can also use `shift`, which pops the first positional parameter: + + a=$1 + shift + b=$1 + shift + +or, more compactly: + + a=$1; shift + b=$1; shift + +The advantage of the `$1; shift` form is that we can add more +positional parameters without having to keep count. We'll see other +uses later. + + +Mathematical Expressions +------------------------ + +Many mathematical operations can be put in $(( )). This will only +perform integer math, however. Here's an example: + + total=0 + for thing in $* + do + total=$(( ${total} + ${#thing} )) + done + echo ${total} + +This will sum the lengths of the positional parameters, and print +the result to STDOUT. + +For floating-point math, we have to use other options (such as awk). + + +File Descriptors +---------------- + +There are three automatic file descriptors (in addition to any files +your program opens): + +| *File* | *Descriptor* | *Meaning* | +| ------ | ------------ | --------- | +| STDIN | 0 | Standard input, reading from the terminal | +| STDOUT | 1 | Standard output, writing to the terminal | +| STDERR | 2 | Standard error, writing to the terminal | + +The normal thing for a program to do is read from STDIN and write to STDOUT. +Many programs will also write to STDERR, but if all you have is the terminal, +it's hard to tell STDOUT from STDERR. However, the shell still knows, and +lets us treat these differently. Here's an example. First, try: + + grep bash /etc/* + +Now, try this: + + grep bash /etc/* 2>/dev/null + +The second form told the shell that STDERR (2) should be redirected +to (>) the special file /dev/null. We could also do: + + grep bash /etc/* 2>/dev/null >bash_in_etc + +You shouldn't see any output now, but take a look at the new file +`bash_in_etc`. If unspecified, output redirection applies to STDOUT. + +We can also merge STDOUT and STDERR: + + grep bash /etc/* 2>&1 + +Here, we've specified the redirection target as `&1`, which means +"whatever file descriptor 1 points to". + +To append, instead of overwriting, we can use ">>" instead of ">". + +We can also redirect STDIN, by using "<": + + wc -l <bash_in_etc # This counts the number of lines in the file + + +Here Documents +-------------- + +There's a special form of input redirection, using "<<": + + wc <<EOFWC + this + is + a + test + EOFWC + +The string "EOFWC" is arbitrary, but "EOF<command>" is fairly common. We +can also pass a single string as STDIN with "<<<": + + wc <<<"this is a test" + + +Pipelines +--------- + +The Unix philosophy is that a given tool should do one thing, and +if you have to do multiple things, you should compose different +tools. Pipelines are the shell's way to do this. In short: + + foo | bar | baz + +takes STDOUT from foo, redirects that to the STDIN of bar, and +redirects bar's STDOUT to baz's STDIN. + +You should become comfortable with this pattern, because it is one +of the keys to creating powerful scripts. We can also combine with +other things we've seen: + + echo "grep produced $(grep bash /etc/* 2>&1 >/dev/null | wc -l) errors" + + +Control Flow +------------ + +Bash has an if/then/elif/else/fi construction. The minimal version is +if/then/fi, as in: + + if $foo + then + echo "foo" + fi + +The full form would be: + + if $foo + then + echo "foo" + elif $bar + then + echo "bar" + else + echo "baz" + fi + +The if statement uses command return codes, so you can put a command +in the test, or use the `test` command (usually written `[`): + + grep foo /etc/hosts + have_foo=$? + grep localhost /etc/hosts + have_local=$? + if [ 0 -eq ${have_foo} ] + then + echo "We have foo" + elif [ 0 -eq ${have_local} ] + then + echo "We have localhost" + fi + +See the `test` manpage for details; there are many tests you can +perform, and the manpage is fairly compact. + +We can also construct loops in bash, as we've already seen briefly. +There are "for" loops and "while" loops, and they behave as you'd +expect. Both have the format: + + <for or while> + do + # ... + done + +A "for" statement looks like + + for loop_var in <sequence> + +Sequence can be something like "$*", or "$a $b $c", or $(ls /etc). +If you want to iterate over numbers, you can do something like + + for loop_var in $(seq 10) + do + echo "foo${loop_var}" + done + +The `while` statement takes a conditional, much like `if`. We can loop +indefinitely with it: + + while true + do + # ... + if $condition + then + break + fi + done + +Finally, bash has a `case` statement: + + case ${switch_var} in + foo) echo "foo";; + bar|baz) echo "bar"; echo "baz";; + *) echo "default";; + esac + +Let's combine this for command-line argument parsing: + + a="foo" + b="bar" + c="" + while [ $# -gt 0 ] + do + case $1 in + -a) shift; a=$1; shift;; + -b) shift; b=$1; shift;; + *) c="$c $1"; shift;; + esac + done + + +Functions +--------- +