15222
Comment: exit codes, tests, patterns.
|
33272
|
Deletions are marked like this. | Additions are marked like this. |
Line 2: | Line 2: |
[[TableOfContents]] |
<<TableOfContents>> |
Line 8: | Line 5: |
Line 10: | Line 6: |
Compound commands are statements that can execute several commands but are considdered as a sort of command group by Bash. |
Compound commands are statements that can execute several commands but are considered as a sort of command group by Bash. |
Line 19: | Line 14: |
That means, strings are considdered names of integer variables, all operators are considdered arithmetic operators (such as `++`, `==`, `>`, `<=`, etc..) You should always use this for performing tests on numbers! |
. That means, strings are considered names of integer variables, all operators are considered arithmetic operators (such as `++`, `==`, `>`, `<=`, etc..) You should always use this for performing tests on numbers! |
Line 22: | Line 16: |
All `test` operators are supported but you can also perform ''Glob pattern matching'' and several other more advanced tests. It is good to note that word splitting will '''not''' take place on unquoted parameter expansions here. You should always use this for performing tests on strings and filenames! |
. All `test` operators are supported but you can also perform ''Glob pattern matching'' and several other more advanced tests. It is good to note that word splitting will '''not''' take place on unquoted parameter expansions here. You should always use this for performing tests on strings and filenames! |
Line 26: | Line 19: |
* `do` ''[command list]''`; done`: '''This constitutes the actual loop that is used by the next few commands'''. [[BR]]The list of commands between the `do` and `done` are the commands that will be executed in every iteration of the loop. * `for` ''[name]'' `in` ''[words]'': '''The next loop will iterate over each ''WORD'' after the `in` keyword'''. [[BR]]The loop's commands will be executed with the value of the variable denoted by `name` set to the word. * `for ((` ''[arithmetic expression]''`;` ''[arithmetic expression]''`;` ''[arithmetic expression]'' `))`: '''The next loop will run as long as the second ''arithmetic expression'' remains ''true'''''. [[BR]]The first ''arithmetic expression'' will be ran before the loop starts. The third ''arithmetic expression'' will be ran after the last command in each iteration has been executed. * `while` ''[command list]'': '''The next loop will be repeated for as long as the last command ran in the ''command list'' exits successfully'''. * `until` ''[command list]'': '''The next loop will be repeated for as long as the last command ran in the ''command list'' exits unsuccessfully ("fails")'''. * `select` ''[name]'' `in` ''[words]'': '''The next loop will repeat forever, letting the user choose between the given words'''. [[BR]]The iteration's commands are executed with the variable denoted by `name`'s value set to the word chosen by the user. Naturally, you can use `break` to end this loop. |
* `do` ''[command list]''`; done` . '''This constitutes the actual loop that is used by the next few commands'''. <<BR>>The list of commands between the `do` and `done` are the commands that will be executed in every iteration of the loop. * `for` ''[name]'' `in` ''[words]'' . '''The next loop will iterate over each ''WORD'' after the `in` keyword'''. <<BR>>The loop's commands will be executed with the value of the variable denoted by `name` set to the word. * `for ((` ''[arithmetic expression]''`;` ''[arithmetic expression]''`;` ''[arithmetic expression]'' `))` . '''The next loop will run as long as the second ''arithmetic expression'' remains ''true'''''. <<BR>>The first ''arithmetic expression'' will be ran before the loop starts. The third ''arithmetic expression'' will be ran after the last command in each iteration has been executed. * `while` ''[command list]'' . '''The next loop will be repeated for as long as the last command ran in the ''command list'' exits successfully'''. * `until` ''[command list]'' . '''The next loop will be repeated for as long as the last command ran in the ''command list'' exits unsuccessfully ("fails")'''. * `select` ''[name]'' `in` ''[words]'' . '''The next loop will repeat forever, letting the user choose between the given words'''. . <<BR>>The iteration's commands are executed with the variable denoted by `name`'s value set to the word chosen by the user. Naturally, you can use `break` to end this loop. |
Line 45: | Line 34: |
Line 50: | Line 38: |
They are ''NOP''s that always return successfully. | . They are ''NOP''s that always return successfully. |
Line 52: | Line 40: |
It returns an exit code of `1` indicating failure. | . It returns an exit code of `1` indicating failure. |
Line 56: | Line 44: |
Aliasses replace a word in the beginning of a command by something else. They only work in interactive shells (not scripts). | . Aliasses replace a word in the beginning of a command by something else. They only work in interactive shells (not scripts). |
Line 58: | Line 46: |
Each argument is a new variable assignment. Each argument's part before the equal sign is the name of the variable, and after comes the data of the variable. Options to declare can be used to toggle special variable flags (like __r__ead-only/e__x__port/__i__nteger/__a__rray). | . Each argument is a new variable assignment. Each argument's part before the equal sign is the name of the variable, and after comes the data of the variable. Options to declare can be used to toggle special variable flags (like __r__ead-only/e__x__port/__i__nteger/__a__rray). |
Line 60: | Line 48: |
This is the same as `declare -x`. Remember that for the child process, the variable is not the same as the one you exported. It just holds the same data. Which means, you can't change the variable data and expect it to change in the parent process, too. |
. This is the same as `declare -x`. Remember that for the child process, the variable is not the same as the one you exported. It just holds the same data. Which means, you can't change the variable data and expect it to change in the parent process, too. |
Line 63: | Line 50: |
As soon as the function exits, the variable disappears. Assigning to it in a function also doesn't change a global variable with the same name, should one exist. The same options as taken by `declare` can be passed to `local`. | . As soon as the function exits, the variable disappears. Assigning to it in a function also doesn't change a global variable with the same name, should one exist. The same options as taken by `declare` can be passed to `local`. |
Line 65: | Line 52: |
The type can be either: ''alias'', ''keyword'', ''function'', ''builtin'', or ''file''. | . The type can be either: ''alias'', ''keyword'', ''function'', ''builtin'', or ''file''. |
Line 69: | Line 56: |
If more than one variable name is given, split the line up using the characters in `IFS` as delimitors. If less variable names are given than there are split chunks in the line, the last variable gets all data left unsplit. | . If more than one variable name is given, split the line up using the characters in `IFS` as delimiters. If less variable names are given than there are split chunks in the line, the last variable gets all data left unsplit. |
Line 73: | Line 60: |
The first arguments can be options that toggle special behaviour (like __n__o newline at end/evaluate __e__scape sequences). | . The first arguments can be options that toggle special behaviour (like __n__o newline at end/evaluate __e__scape sequences). |
Line 75: | Line 62: |
See `help printf`. | . See `help printf`. |
Line 77: | Line 64: |
You can use the `-P` option to make `pwd` resolve any symlinks in the pathname. | . You can use the `-P` option to make `pwd` resolve any symlinks in the pathname. |
Line 81: | Line 68: |
If the path doesn't start with a slash, it is relative to the current directory. | . If the path doesn't start with a slash, it is relative to the current directory. |
Line 83: | Line 70: |
This tells Bash not to look for an alias, function, builtin or keyword by that name; but skip right ahead to looking through `PATH`. | . This tells Bash not to look for an alias, function, builtin or keyword by that name; but skip right ahead to looking through `PATH`. |
Line 85: | Line 72: |
This is kind of like `include` in other languages. If more arguments are given than just a filename to `source`, those arguments are set as the positional parameters during the execution of the sourced code. If the filename to source has no slash in it, `PATH` is searched for it. |
. This is kind of like `include` in other languages. If more arguments are given than just a filename to `source`, those arguments are set as the positional parameters during the execution of the sourced code. If the filename to source has no slash in it, `PATH` is searched for it. |
Line 88: | Line 74: |
Other arguments are passed to the command as its arguments. If no arguments are given to exec but you do specify ''Redirections'' on the exec command, the redirections will be applied to the current shell. | . Other arguments are passed to the command as its arguments. If no arguments are given to exec but you do specify ''Redirections'' on the exec command, the redirections will be applied to the current shell. |
Line 90: | Line 76: |
If an argument is given, it is the exit status of the current script (an integer between 0 and 255). | . If an argument is given, it is the exit status of the current script (an integer between 0 and 255). |
Line 93: | Line 79: |
An exit status may be specified just like with the `exit` builtin. | . An exit status may be specified just like with the `exit` builtin. |
Line 95: | Line 81: |
These limits are inherited by child processes. | . These limits are inherited by child processes. |
Line 100: | Line 86: |
The shell continues to run while the job is running. The shell's input is handled by itself, not the job. | . The shell continues to run while the job is running. The shell's input is handled by itself, not the job. |
Line 102: | Line 88: |
The shell waits for the job to end and the job can receive the input from the shell. | . The shell waits for the job to end and the job can receive the input from the shell. |
Line 104: | Line 90: |
As argument, give the process ID of the process or the ''jobspec'' of the job you want to send the signal to. | . As argument, give the process ID of the process or the ''jobspec'' of the job you want to send the signal to. |
Line 106: | Line 92: |
The code that is in the first argument is executed whenever a signal is received denoted by any of the other arguments to `trap`. | . The code that is in the first argument is executed whenever a signal is received denoted by any of the other arguments to `trap`. |
Line 108: | Line 94: |
This is much like what happens when the shell receives a ''SIGSTOP'' signal. | . This is much like what happens when the shell receives a ''SIGSTOP'' signal. |
Line 110: | Line 96: |
In arguments, you can specify which jobs (by ''jobspec'') or processes (by ''PID'') to wait for. | . In arguments, you can specify which jobs (by ''jobspec'') or processes (by ''PID'') to wait for. |
Line 114: | Line 100: |
When more than one loop is active, break out the last one declared. When a ''number'' is given as argument to `break`, break out of ''number'' loops, starting with the last one declared. | . When more than one loop is active, break out the last one declared. When a ''number'' is given as argument to `break`, break out of ''number'' loops, starting with the last one declared. |
Line 116: | Line 102: |
Just like with `break`, a ''number'' may be given to skip out more loops. | . Just like with `break`, a ''number'' may be given to skip out more loops. |
Line 120: | Line 106: |
''Shell options'' are options that can be passed to the shell, such as `bash -x` or `bash -e`. `set` toggles shell options like this: `set -x`, `set +x`, `set -e`, ... ''Positional parameters'' are parameters that hold arguments that were passed to the script or shell, such as `bash myscript -foo /bar`. `set` assigns positional parameters like this: `set -- -foo /bar`. |
. ''Shell options'' are options that can be passed to the shell, such as `bash -x` or `bash -e`. `set` toggles shell options like this: `set -x`, `set +x`, `set -e`, ... ''Positional parameters'' are parameters that hold arguments that were passed to the script or shell, such as `bash myscript -foo /bar`. `set` assigns positional parameters like this: `set -- -foo /bar`. |
Line 125: | Line 108: |
This way, values that were in `$1` are discarted, values from `$2` go into `$1`, values from `$3` go into `$2`, and so on. You can specify an argument to shift which is an integer that specifies how many times to repeat this shift. | . This way, values that were in `$1` are discarted, values from `$2` go into `$1`, values from `$3` go into `$2`, and so on. You can specify an argument to shift which is an integer that specifies how many times to repeat this shift. |
Line 127: | Line 110: |
`getopts` Uses the first argument as a specification for which options to look for in the arguments. It then takes the first option in the arguments that is mentioned in this option specification (or next option, if getopts has been ran before), and puts this option in the variable denoted by the name in the second argument to `getopts`. This command is pretty much always used in a '''loop''': {{{ |
. `getopts` Uses the first argument as a specification for which options to look for in the arguments. It then takes the first option in the arguments that is mentioned in this option specification (or next option, if getopts has been ran before), and puts this option in the variable denoted by the name in the second argument to `getopts`. This command is pretty much always used in a '''loop''': {{{ |
Line 137: | Line 119: |
done}}} This way all options in the arguments are parsed and when they are either `-a`, `-b` or `-c`, the respective code in the `case` statement is executed. Following short style is also valid for specifying multiple options in the arguments that `getopts` parses: `-ac`. |
done }}} This way all options in the arguments are parsed and when they are either `-a`, `-b` or `-c`, the respective code in the `case` statement is executed. Following short style is also valid for specifying multiple options in the arguments that `getopts` parses: `-ac`. |
Line 143: | Line 124: |
Line 146: | Line 125: |
Line 151: | Line 129: |
'''The `if` command tests whether the last command in the first ''command list'' had an exit code of `0`'''. [[BR]]If so, it executes the ''command list'' that follows the `then`. If not, the next `elif` is tried in the same manner. If no `elif`s are present, the ''command list'' following `else` is executed, unless there is no `else` statement. To summarize, `if` executes a list of *command*s. It tests the exit code. On success, the `then` commands are executed. `elif` and `else` parts are optional. The `fi` part ends the entire `if` block (don't forget it!). |
. '''The `if` command tests whether the last command in the first ''command list'' had an exit code of `0`'''. <<BR>>If so, it executes the ''command list'' that follows the `then`. If not, the next `elif` is tried in the same manner. If no `elif`s are present, the ''command list'' following `else` is executed, unless there is no `else` statement. To summarize, `if` executes a list of *command*s. It tests the exit code. On success, the `then` commands are executed. `elif` and `else` parts are optional. The `fi` part ends the entire `if` block (don't forget it!). |
Line 155: | Line 131: |
'''Execute the next iteration depending on the exit code of the last command in the ''command list'''''. [[BR]]We've discussed these before, but it's worth repeating them in this section, as they actually do the same thing as the `if` statement; except that they execute a loop for as long as the tested exit code is respectively `0` or `non-0`. |
. '''Execute the next iteration depending on the exit code of the last command in the ''command list'''''. <<BR>>We've discussed these before, but it's worth repeating them in this section, as they actually do the same thing as the `if` statement; except that they execute a loop for as long as the tested exit code is respectively `0` or `non-0`. |
Line 159: | Line 134: |
Bash knows two types of patterns. ''Glob Patterns'' is the most important, most used and best readable one. Later versions of Bash also support the ''"trendy"'' ''Regular Expressions''. It is however ill-adviced to use regular expressions in scripts unless you have absolutely no other choice or its use greatly outweighs the use of globs in advantages. Generally speaking, if you need a regular expression, you'll be using `awk(1)`, `sed(1)`, or `grep(1)` instead of Bash. |
Bash knows two types of patterns. ''Glob Patterns'' is the most important, most used and best readable one. Later versions of Bash also support the ''"trendy"'' ''Regular Expressions''. However, it is ill-advised to use regular expressions in scripts unless you have absolutely no other choice or the advantages of using them are far greater than when using globs. Generally speaking, if you need a regular expression, you'll be using `awk(1)`, `sed(1)`, or `grep(1)` instead of Bash. |
Line 164: | Line 138: |
That is one single character. | . That is one single character. |
Line 166: | Line 140: |
That is zero or more of whatever characters. | . That is zero or more of whatever characters. |
Line 168: | Line 142: |
That is one character that is mentioned inside the braces. * `[abc]`: '''Matches either `a`, `b`, or `c` but not the string `abc`'''. * `[a-c]`: '''The dash tells Bash to use a range'''. Matches any character between (inclusive) `a` and `c`. So this is the same thing as the example just above. * `[!a-c]` or `[^a-c]`: '''The `!` or `^` in the beginning tells Bash to invert the match'''. Matches any character that is *not* `a`, `b` or `c`. That means any other letter, but *also* a number, a period, a comma, or any other character you can think of. * `[[:digit:]]`: '''The `[:`''class''`:]` syntax tells Bash to use a character class'''. Character classes are groups of characters that are predefined and named for convenience. You can use the following classes: `alnum`, `alpha`, `ascii`, `blank`, `cntrl`, `digit`, `graph`, `lower`, `print`, `punct`, `space`, `upper`, `word`, `xdigit` |
. That is one character that is mentioned inside the braces. * `[abc]`: '''Matches either `a`, `b`, or `c` but not the string `abc`'''. * `[a-c]`: '''The dash tells Bash to use a range'''. . Matches any character between (inclusive) `a` and `c`. So this is the same thing as the example just above. * `[!a-c]` or `[^a-c]`: '''The `!` or `^` in the beginning tells Bash to invert the match'''. . Matches any character that is *not* `a`, `b` or `c`. That means any other letter, but *also* a number, a period, a comma, or any other character you can think of. * `[[:digit:]]`: '''The `[:`''class''`:]` syntax tells Bash to use a character class'''. . Character classes are groups of characters that are predefined and named for convenience. You can use the following classes: `alnum`, `alpha`, `ascii`, `blank`, `cntrl`, `digit`, `graph`, `lower`, `print`, `punct`, `space`, `upper`, `word`, `xdigit` |
Line 180: | Line 154: |
'''The `[[` command is an improved version of the commonly used `[` or `test` command'''. [[BR]]`[` and `test` are commands you often see in `sh` scripts to perform tests on files, strings and numbers. `[[` can do all these things (but better) and it also provides you with ''Glob Pattern'' matching. This syntax causes `[[` to return a successful exit code (`0`) when the given ''string'' matches the given ''glob pattern'', or the given ''regular expression'' with the second syntax. |
. '''The `[[` command is an improved version of the commonly used `[` or `test` command'''. <<BR>>`[` and `test` are commands you often see in `sh` scripts to perform tests on files, strings and numbers. `[[` can do all these things (but better) and it also provides you with ''Glob Pattern'' matching. This syntax causes `[[` to return a successful exit code (`0`) when the given ''string'' matches the given ''glob pattern'', or the given ''regular expression'' with the second syntax. |
Line 183: | Line 156: |
'''Using `case` is handy if you want to test a certain string that could match either of several different glob patterns'''. [[BR]]The ''command list'' that follows the *first* ''glob pattern'' that matched your ''string'' will be executed. You can specify as many ''glob pattern'' and ''command lists'' combos as you need. |
. '''Using `case` is handy if you want to test a certain string that could match either of several different glob patterns'''. <<BR>>The ''command list'' that follows the *first* ''glob pattern'' that matched your ''string'' will be executed. You can specify as many ''glob pattern'' and ''command lists'' combos as you need. = Parameters = Parameters are what Bash uses to store your script data in. There are ''Special Parameters'' and ''Variables''. Any parameters you create will be variables, since special parameters are read-only parameters managed by Bash. It is recommended you use lower-case names for your own parameters so as not to confuse them with the all-uppercase variable names used by Bash internal variables and environment variables. It is also recommended you use clear and transparent names for your variables. Avoid `x`, `i`, `t`, `tmp`, `foo`, etc. Instead, use the variable name to describe the kind of data the variable is supposed to hold. It is also important that you understand the need for quoting. Generally speaking, whenever you use a parameter, you should quote it: `echo "The file is in: $filePath"`. If you don't, bash will tear the contents of your parameter to bits, delete all the whitespace from it, and feed the bits as arguments to the command. Yes, Bash mutilates your parameter expansions by default - it's called ''Word Splitting'' - so use quotes to prevent this. <<BR>>The exception is ''keywords'' and ''assignment''. After ''`myvar=`'' and inside ''`[[`'', ''`case`'', etc, you don't ''need'' the quotes, but they won't do any harm either - so if you're unsure: quote! '''Last but not least''': Remember that parameters are the ''data structures'' of bash. They hold your application data. They should '''NOT''' be used to hold your application logic. So while many ill-written scripts out there may use things like `GREP=/usr/bin/grep`, or `command='mplayer -vo x11 -ao alsa'`, you should '''NOT''' do this. The main reason is because you cannot possibly do it completely right ''and'' safe ''and'' readable/maintainable. <<BR>>If you want to avoid retyping the same command multiple times, or make a single place to manage the command's command line, use a ''function'' instead. Not parameters. == Special Parameters == * `1`, `2`, ...: '''Positional Parameters are the arguments that were passed to your script or your function.''' . When your script is started with `./script foo bar`, `"$1"` will become `"foo"` and `"$2"` will become `"bar"`. A script ran as `./script "foo bar" hubble` will expand `"$1"` as `"foo bar"` and `"$2"` as `"hubble"`. * `*`: '''When expanded, it equals the single string that concatenates all positional parameters using the first character of `IFS` to separate them (- by default, that's a space).''' . In short, `"$*"` is the same as `"$1x$2x$3x$4x..."` where x is the first character of `IFS`. <<BR>>With a default `IFS`, that will become a simple `"$1 $2 $3 $4 ..."`. * `@`: '''This will expand into multiple arguments: Each positional parameter that is set will be expanded as a single argument.''' . So basically, `"$@"` is the same as `"$1" "$2" "$3" ...`, all quoted separately. <<BR>>'''NOTE: You should always use `"$@"` before `"$*"`, because `"$@"` preserves the fact that each argument is its separate entity. With `"$*"`, you loose this data! `"$*"` is really only useful if you want to separate your arguments by something that's not a space; for instance, a comma: `(IFS=,; echo "You ran the script with the arguments: $*")` -- output all your arguments, separating them by commas.''' * `#`: '''This parameter expands into a number that represents how many positional parameters are set.''' . A script executed with 5 arguments, will have `"$#"` expand to `5`. This is mostly only useful to test whether any arguments were set: `if (( ! $# )); then echo "No arguments were passed." >&2; exit 1; fi` * `?`: '''Expands into the exit code of the previously completed foreground command.''' . We use `$?` mostly if we want to use the exit code of a command in multiple places; or to test it against many possible values in a `case` statement. * `-`: '''The dash parameter expands into the option flags that are currently set on the Bash process.''' . See ''set'' for an explanation of what option flags are, which exist, and what they mean. * `$`: '''The dollar parameter expands into the ''Process ID'' of the Bash process.''' . Handy mostly for creating a PID file for your bash process (`echo "$$" > /var/run/foo.pid`); so you can easily terminate it from another bash process, for example. * `!`: '''Expands into the ''Process ID'' of the most recently backgrounded command.''' . Use this for managing backgrounded commands from your Bash script: `foo ./bar & pid=$!; sleep 10; kill "$pid"; wait "$pid"` * `_`: '''Expanding the underscore argument gives you the last argument of the last command you executed.''' . This one's used mostly in interactive shells to shorten typing a little: `mkdir -p /foo/bar && mv myfile "$_"`. == Arrays == Arrays are variables that contain multiple strings. Whenever you need to store multiple items in a variable, '''use an array and NOT a string''' variable. Arrays allow you to keep the elements nicely separated and allow you to cleanly expand the elements into separate arguments. This is '''impossible''' to do if you mash your items together in a string! === Creating Arrays === * `myarray=( foo bar quux )` . '''Create an array `myarray` that contains three elements'''. Arrays are created using the `x=(y)` syntax and array elements are separated from each other by whitespace. * `myarray=( "foo bar" quux )` . '''Create an array `myarray` that contains ''two'' elements'''. To put elements in an array that contain whitespace, wrap quotes around them to indicate to bash that the quoted text belongs together in a single array element. * `myfiles=( *.txt )` . '''Create an array `myfiles` that contains all the filenames of the files in the current directory that end with `.txt`'''. We can use any type of expansion inside the array assignment syntax. The example use pathname expansion to replace a glob pattern by all the filenames it matches. Once replaced, array assignment happens like in the first two examples. * `myfiles+=( *.html )` . '''Add all HTML files from the current directory to the `myfiles` array'''. The `x+=(y)` syntax can be used the same way as the normal array assignment syntax, but append elements to the end of the array. * `read -ra myarray` . '''Chop a line into fields and store the fields in an array `myarray`'''. The `read` commands reads a line from ''stdin'' and uses each character in the `IFS` variable as a delimiter to split that line into fields. * `IFS=, read -ra names <<< "John,Lucas,Smith,Yolanda"` . '''Chop a line into fields using `,` as the delimiter and store the fields in the array named `names`'''. We use the `<<<` syntax to feed a string to the `read` command's ''stdin''. `IFS` is set to `,` for the duration of the `read` command, causing it to split the input line into fields separated by a comma. Each field is stored as an element in the `names` array. * `IFS=$'\n' read -d '' -ra lines` . '''Read all lines from ''stdin'' into elements of the array named `lines`'''. We use `read`'s `-d ''` switch to tell it not to stop reading after the first line, causing it to read in all of ''stdin''. We then set `IFS` to a newline character, causing `read` to chop the input up into fields whenever a new line begins. * `files=(); while IFS= read -d '' -r file; do files+=("$file"); done < <(find . -name '*.txt' -print0)` . '''Safely read all TXT files contained recursively in the current directory into the array named `files`'''.<<BR>>We begin by creating an empty array named `files`. We then start a `while` loop which runs a `read` statement to read in a filename from ''stdin'', and then appends that filename (contained in the variable `file`) to the `files` array. For the `read` statement we set `IFS` to empty, avoiding `read`'s behavior of trimming leading whitespace from the input and we set `-d ''` to tell `read` to continue reading until it sees a `NUL` byte (filenames '''CAN''' span multiple lines, so we don't want read to stop reading the filename after one line!). For the input, we attach the `find` command to `while`'s ''stdin''. The `find` command uses `-print0` to output its filenames by separating them with `NUL` bytes (see the `-d ''` on `read`). '''NOTE:''' This is the '''only''' truly safe way of building an array of filenames from a command's output! You '''must''' delimit your filenames with `NUL` bytes, because it is the ''only'' byte that can't actually appear inside a filename! '''NEVER''' use `ls` to enumerate filenames! First try using the glob examples above, they are just as safe (no need to parse an external command), much simpler and faster. * `declare -A homedirs=( ["Peter"]=~pete ["Johan"]=~jo ["Robert"]=~rob )` . '''Create an ''associative'' array, mapping names to user home directories'''. Unlike normal arrays, associative arrays indices are strings (just like the values). Note: you ''must'' use `declare -A` when creating an associative array to indicate to bash that this array's indices are strings and not integers. * `homedirs["John"]=~john` . '''Add an element to an associative array, keyed at `"John"`, mapped to `john`'s home directory'''. === Using Arrays === * `cp "${myfiles[@]}" /destinationdir/` . '''Copy all files referenced by the filenames within the `myfiles` array into `/destinationdir/`'''. Expanding an array happens using the syntax `"${array[@]}"`. It effectively replaces that expansion syntax by a list of all the elements contained within the array, properly quoted as separate arguments. * `rm "./${myfiles[@]}"` . '''Remove all files referenced by the filenames within the `myfiles` array'''. It's generally a ''bad'' idea to attach strings to an array expansion syntax. What happens is: the string is only prefixed to the ''first'' element expanded from the array (or suffixed to the last if you attached the string to the end of the array expansion syntax). If `myfiles` contained the elements `-foo.txt` and `bar-.html`, this command would expand into: `rm "./-foo.txt" "bar-.html"`. Notice only the first element is prefixed with `./`. In this particular instance, this is handy because `rm` fails if the first filename begins with a dash. Now it begins with a dot. * `(IFS=,; echo "${names[*]}")` . '''Expand the array `names` into a ''single string'' containing all elements in the array, merging them by separating them with a comma (`,`)'''. The `"${array[*]}"` syntax is only very rarely useful. Generally, when you see it in scripts, it is a bug. The one use it has is to merge all elements of an array into a single string for displaying to the user. Notice we surrounded the statement with `(`brackets`)`, causing a subshell: This will scope the `IFS` assignment, resetting it after the subshell ends. * `for file in "${myfiles[@]}"; do read -p "Delete $file? " && [[ $REPLY = y ]] && rm "$file"; done` . '''Iterate over all elements of the `myfiles` array after expanding them into the `for` statement'''. Then, for each file, ask the user whether he wants to delete it. * `for index in "${!myfiles[@]}"; do echo "File number $index is ${myfiles[index]}"; done` . '''Iterate over all keys of the `myfiles` array after expanding them into the `for` statement'''. The syntax `"${!array[@]}"` (notice the `!`) gets expanded into a list of array ''keys'', not values. Keys of normal arrays are numbers starting at `0`. The syntax for getting to a particular element within an array is `"${array[index]}"`, where `index` is the key of the element you want to get at. * `names=(John Pete Robert); echo "${names[@]/#/Long }"` . '''Perform a parameter expansion operation on every element of the `names` array'''. When adding a parameter expansion operation to an array expansion, the operation is applied to every single array element as it is expanded. * `printf '%s\n' "${names[@]}"` . '''Output each array element on a new line'''. This `printf` statement is a very handy technique for outputting array elements in a common way (in this case, appending a newline to each). The format string given to `printf` is applied to each element (unless multiple `%s`'s appear in it, of course). * `for name in "${!homedirs[@]}"; do echo "$name lives in ${homedirs[$name]}"; done` . '''Iterate over all keys of the `homedirs` array after expanding them into the `for` statement'''. The syntax for getting to the keys of associative arrays is the same as that for normal arrays. Instead of numbers beginning at `0`, we now get the keys for which we mapped our associative array's values. We can later use these keys to look up values within the array, just like normal arrays. = Examples: Basic Structures = == Compound Commands == === Command Lists === * `[[ $1 ]] || { echo "You need to specify an argument!" >&2; exit 1; }` . '''We use a command group here because the `||` operator takes just one command.''' <<BR>>We want both the `echo` and `exit` commands to run if `$1` is empty. * `(IFS=','; echo "The array contains these elements: ${array[*]}")` . '''We use parenthesis to trigger a subshell here.''' <<BR>>When we set the IFS variable, it will only change in the subshell and not in our main script. That avoids us having to reset it to it's default after the expansion in the echo statement (which otherwise we would have to do in order to avoid unexpected behaviour later on). * `(cd "$1"; tar -cvjpf archive.tbz2 .)` . '''Here we use the subshell to temporarily change the current directory to what's in `$1`.''' <<BR>>After the `tar` operation (when the subshell ends), we're back to where we were before the `cd` command because the current directory of the main script never changed. === Expressions === * `((completion = current * 100 / total))` . '''Note that arithmetic context follows completely different parsing rules than normal bash statements'''. * `[[ $foo = /* ]] && echo "foo contains an absolute pathname."` . '''We can use the `[[` command to perform all tests that `test(1)` can do.''' <<BR>>But as shown in the example it can do far more than `test(1)`; such as glob pattern matching, regular expression matching, test grouping, etc. === Loops === * `for file in *.mp3; do openssl md5 "$file"; done > mysongs.md5` . '''For loops iterate over all arguments after the `in` keyword'''. <<BR>>One by one, each argument is put in the variable name `file` and the loop's body is executed. <<BR>> <<BR>>'''DO NOT PASS A COMMAND'S OUTPUT TO `for` BLINDLY! <<BR>>`for` will iterate over the WORDS in the command's output; which is almost NEVER what you really want!''' * `for (( i = 0; i < 50; i++ )); do printf "%02d," "$i"; done` . '''Generates a comma-separated list of numbers zero-padded to two digits'''. <<BR>>''(The last character will be a comma, yes, if you really want to get rid of it; you can - but it defeats the simplicity of this example)'' * `while read _ line; do echo "$line"; done < file` . '''This `while` loop continues so long as the `read` command is successful'''. <<BR>>(Meaning, so long as lines can be read from the file). The example basically just throws out the first column of data from a file and prints the rest. * `until myserver; do echo "My Server crashed with exit code: $?; restarting it in 2 seconds .."; sleep 2; done` . '''This loop restarts `myserver` each time it exits with a non-successful exit code'''. <<BR>>It assumes that when `myserver` exits with a non-successful exit code; it crashed and needs to restart; and if it exist with a successful exit code; you ordered it to shut down and it needn't be restarted. * `select fruit in Apple Pear Grape Banana Strawberry; do (( credit -= 2, health += 5 )); echo "You purchased some $fruit. Enjoy!"; done` . '''A simple program which converts credits into health'''. <<BR>>Amazing. == Builtins == === Dummies === * `while true; do ssh lhunath@lyndir.com; done` . '''Reconnect on failure'''. === Declarative === * `alias l='ls -al'` . '''Make an alias called `l` which is replaced by `ls -al`'''. <<BR>>Handy for quickly viewing a directory's detailed contents. * `declare -i myNumber=5` . '''Declare an integer called `myNumber` initialized to the value `5`'''. * `export AUTOSSH_PORT=0` . '''Export a variable on the bash process environment called `AUTOSSH_PORT` which will be inherited by any process this bash process invokes'''. * `foo() { local bar=fooBar; echo "Inside foo(), bar is $bar"; }; echo "Setting bar to 'normalBar'"; bar=normalBar; foo; echo "Outside foo(), bar is $bar"` . '''An exercise in variable scopes'''. * `if ! type -P ssh >/dev/null; then echo "Please install OpenSSH." >&2; exit 1; fi` . '''Check to see if `ssh` is available'''. <<BR>>Suggest the user install ''OpenSSH'' if it is not, and exit. === Input === * `read firstName lastName phoneNumber address` . '''Read data from a single line with four fields into the four named variables'''. === Output === * `echo "I really don't like $nick. He can be such a prick."` . '''Output a simple string on standard output'''. * `printf "I really don't like %s. He can be such a prick." "$nick"` . '''Same thing using `printf` instead of `echo`, nicely separating the text from the data'''. === Execution === * `cd ~lhunath` . '''Change the current directory to `lhunath`'s home directory'''. * `command [ "$a" = a ] || echo "$a is not 'a'!"` . '''Execute the `[` command (not the builtin!) and execute the `echo` statement if it fails'''. * `source bashlib; source ~/.foorc` . '''Run all the bash code in a file called `bashlib` which exists somewhere in `PATH`; then do the same for the file `.foorc` in the current directory'''. * `exec 2>/var/log/foo.log` . '''Send all output to standard error from now on to a log file'''. * `echo "Fatal error occurred! Terminating!"; exit 1` . '''Show an error message and exit the script'''. ---- CategoryShell |
Contents: Bash Reference Sheet
Contents
Basic Structures
Compound Commands
Compound commands are statements that can execute several commands but are considered as a sort of command group by Bash.
Command Lists
{ [command list]; }: Execute the list of commands in the current shell.
([command list]): Execute the list of commands in a subshell.
Expressions
(([arithmetic expression])): Evaluates the given expression in an arithmetic context.
That means, strings are considered names of integer variables, all operators are considered arithmetic operators (such as ++, ==, >, <=, etc..) You should always use this for performing tests on numbers!
[[ [test expression] ]]: Evaluates the given expression as a test-compatible expression.
All test operators are supported but you can also perform Glob pattern matching and several other more advanced tests. It is good to note that word splitting will not take place on unquoted parameter expansions here. You should always use this for performing tests on strings and filenames!
Loops
do [command list]; done
This constitutes the actual loop that is used by the next few commands.
The list of commands between the do and done are the commands that will be executed in every iteration of the loop.
for [name] in [words]
The next loop will iterate over each WORD after the in keyword.
The loop's commands will be executed with the value of the variable denoted by name set to the word.
for (( [arithmetic expression]; [arithmetic expression]; [arithmetic expression] ))
The next loop will run as long as the second arithmetic expression remains true.
The first arithmetic expression will be ran before the loop starts. The third arithmetic expression will be ran after the last command in each iteration has been executed.
while [command list]
The next loop will be repeated for as long as the last command ran in the command list exits successfully.
until [command list]
The next loop will be repeated for as long as the last command ran in the command list exits unsuccessfully ("fails").
select [name] in [words]
The next loop will repeat forever, letting the user choose between the given words.
The iteration's commands are executed with the variable denoted by name's value set to the word chosen by the user. Naturally, you can use break to end this loop.
Builtins
Builtins are commands that perform a certain function that has been compiled into Bash. Understandably, they are also the only types of commands (other than those above) that can modify the Bash shell's environment.
Dummies
true (or :): These commands do nothing at all.
They are NOPs that always return successfully.
false: The same as above, except that the command always "fails".
It returns an exit code of 1 indicating failure.
Declarative
alias: Sets up a Bash alias, or print the bash alias with the given name.
- Aliasses replace a word in the beginning of a command by something else. They only work in interactive shells (not scripts).
declare (or typeset): Assign a value to a variable.
Each argument is a new variable assignment. Each argument's part before the equal sign is the name of the variable, and after comes the data of the variable. Options to declare can be used to toggle special variable flags (like read-only/export/integer/array).
export: Export the given variable to the environment so that child processes inherit it.
This is the same as declare -x. Remember that for the child process, the variable is not the same as the one you exported. It just holds the same data. Which means, you can't change the variable data and expect it to change in the parent process, too.
local: Declare a variable to have a scope limited to the current function.
As soon as the function exits, the variable disappears. Assigning to it in a function also doesn't change a global variable with the same name, should one exist. The same options as taken by declare can be passed to local.
type: Show the type of the command name specified as argument.
The type can be either: alias, keyword, function, builtin, or file.
Input
read: Read a line (unless the -d option is used to change the delimiter from newline to something else) and put it in the variables denoted by the arguments given to read.
If more than one variable name is given, split the line up using the characters in IFS as delimiters. If less variable names are given than there are split chunks in the line, the last variable gets all data left unsplit.
Output
echo: Output each argument given to echo on one line, separated by a single space.
The first arguments can be options that toggle special behaviour (like no newline at end/evaluate escape sequences).
printf: Use the first argument as a format specifier of how to output the other arguments.
See help printf.
pwd: Output the absolute pathname of the current working directory.
You can use the -P option to make pwd resolve any symlinks in the pathname.
Execution
cd: Changes the current directory to the given path.
- If the path doesn't start with a slash, it is relative to the current directory.
command: Run the first argument as an application.
This tells Bash not to look for an alias, function, builtin or keyword by that name; but skip right ahead to looking through PATH.
. or source: Makes Bash read the filename given as first argument and execute its contents in the current shell.
This is kind of like include in other languages. If more arguments are given than just a filename to source, those arguments are set as the positional parameters during the execution of the sourced code. If the filename to source has no slash in it, PATH is searched for it.
exec: Run the command given as first argument and replace the current shell with it.
Other arguments are passed to the command as its arguments. If no arguments are given to exec but you do specify Redirections on the exec command, the redirections will be applied to the current shell.
exit: End the execution of the current script.
- If an argument is given, it is the exit status of the current script (an integer between 0 and 255).
logout: End the execution of a login shell.
return: End the execution of the current function.
An exit status may be specified just like with the exit builtin.
ulimit: Modify resource limitations of the current shell's process.
- These limits are inherited by child processes.
Jobs/Processes
jobs: List the current shell's active jobs.
bg: Send the previous job (or job denoted by the given argument) to run in the background.
- The shell continues to run while the job is running. The shell's input is handled by itself, not the job.
fg: Send the previous job (or job denoted by the given argument) to run in the foreground.
- The shell waits for the job to end and the job can receive the input from the shell.
kill: Send a signal(3) to a process or job.
As argument, give the process ID of the process or the jobspec of the job you want to send the signal to.
trap: Handle a signal(3) sent to the current shell.
The code that is in the first argument is executed whenever a signal is received denoted by any of the other arguments to trap.
suspend: Stops the execution of the current shell until it receives a SIGCONT signal.
This is much like what happens when the shell receives a SIGSTOP signal.
wait: Stops the execution of the current shell until active jobs have finished.
In arguments, you can specify which jobs (by jobspec) or processes (by PID) to wait for.
Conditionals And Loops
break: Break out of the current loop.
When more than one loop is active, break out the last one declared. When a number is given as argument to break, break out of number loops, starting with the last one declared.
continue: Skip the code that is left in the current loop and start a new iteration of that loop.
Just like with break, a number may be given to skip out more loops.
Arguments
set: The set command normally sets various Shell options, but can also set Positional parameters.
Shell options are options that can be passed to the shell, such as bash -x or bash -e. set toggles shell options like this: set -x, set +x, set -e, ... Positional parameters are parameters that hold arguments that were passed to the script or shell, such as bash myscript -foo /bar. set assigns positional parameters like this: set -- -foo /bar.
shift: Moves all positional parameters' values one parameter back.
This way, values that were in $1 are discarted, values from $2 go into $1, values from $3 go into $2, and so on. You can specify an argument to shift which is an integer that specifies how many times to repeat this shift.
getops: Puts an option specified in the arguments in a variable.
getopts Uses the first argument as a specification for which options to look for in the arguments. It then takes the first option in the arguments that is mentioned in this option specification (or next option, if getopts has been ran before), and puts this option in the variable denoted by the name in the second argument to getopts. This command is pretty much always used in a loop:
while getopts abc opt do case $opt in a) ...;; b) ...;; c) ...;; esac done
This way all options in the arguments are parsed and when they are either -a, -b or -c, the respective code in the case statement is executed. Following short style is also valid for specifying multiple options in the arguments that getopts parses: -ac.
Tests
Exit Codes
An Exit Code or Exit Status is an unsigned 8-bit integer returned by a command that indicates how its execution went. It is agreed that an Exit Code of 0 indicates the command was successful at what it was supposed to do. Any other Exit Code indicates that something went wrong. Applications can choose for themselves what number indicates what went wrong; so refer to the manual of the application to find out what the application's Exit Code means.
Testing The Exit Code
if [command list]; then [command list]; elif [command list]; then [command list]; else [command list]; fi
The if command tests whether the last command in the first command list had an exit code of 0.
If so, it executes the command list that follows the then. If not, the next elif is tried in the same manner. If no elifs are present, the command list following else is executed, unless there is no else statement. To summarize, if executes a list of *command*s. It tests the exit code. On success, the then commands are executed. elif and else parts are optional. The fi part ends the entire if block (don't forget it!).
while [command list], and until [command list]
Execute the next iteration depending on the exit code of the last command in the command list.
We've discussed these before, but it's worth repeating them in this section, as they actually do the same thing as the if statement; except that they execute a loop for as long as the tested exit code is respectively 0 or non-0.
Patterns
Bash knows two types of patterns. Glob Patterns is the most important, most used and best readable one. Later versions of Bash also support the "trendy" Regular Expressions. However, it is ill-advised to use regular expressions in scripts unless you have absolutely no other choice or the advantages of using them are far greater than when using globs. Generally speaking, if you need a regular expression, you'll be using awk(1), sed(1), or grep(1) instead of Bash.
Glob Syntax
?: A question mark matches any character.
- That is one single character.
*: A star matches any amount of any characters.
- That is zero or more of whatever characters.
[...]: This matches *one of* any of the characters inside the braces.
- That is one character that is mentioned inside the braces.
[abc]: Matches either a, b, or c but not the string abc.
[a-c]: The dash tells Bash to use a range.
Matches any character between (inclusive) a and c. So this is the same thing as the example just above.
[!a-c] or [^a-c]: The ! or ^ in the beginning tells Bash to invert the match.
Matches any character that is *not* a, b or c. That means any other letter, but *also* a number, a period, a comma, or any other character you can think of.
[[:digit:]]: The [:class:] syntax tells Bash to use a character class.
- Character classes are groups of characters that are predefined and named for convenience. You can use the following classes:
alnum, alpha, ascii, blank, cntrl, digit, graph, lower, print, punct, space, upper, word, xdigit
- Character classes are groups of characters that are predefined and named for convenience. You can use the following classes:
- That is one character that is mentioned inside the braces.
Testing Patterns
[[ [string] = [glob pattern] ]], or [[ [string] =~ [regular expression] ]]:
The [[ command is an improved version of the commonly used [ or test command.
[ and test are commands you often see in sh scripts to perform tests on files, strings and numbers. [[ can do all these things (but better) and it also provides you with Glob Pattern matching. This syntax causes [[ to return a successful exit code (0) when the given string matches the given glob pattern, or the given regular expression with the second syntax.
case [string] in [glob pattern]) [command list];; [glob pattern]) [command list];; esac:
Using case is handy if you want to test a certain string that could match either of several different glob patterns.
The command list that follows the *first* glob pattern that matched your string will be executed. You can specify as many glob pattern and command lists combos as you need.
Parameters
Parameters are what Bash uses to store your script data in. There are Special Parameters and Variables.
Any parameters you create will be variables, since special parameters are read-only parameters managed by Bash. It is recommended you use lower-case names for your own parameters so as not to confuse them with the all-uppercase variable names used by Bash internal variables and environment variables. It is also recommended you use clear and transparent names for your variables. Avoid x, i, t, tmp, foo, etc. Instead, use the variable name to describe the kind of data the variable is supposed to hold.
It is also important that you understand the need for quoting. Generally speaking, whenever you use a parameter, you should quote it: echo "The file is in: $filePath". If you don't, bash will tear the contents of your parameter to bits, delete all the whitespace from it, and feed the bits as arguments to the command. Yes, Bash mutilates your parameter expansions by default - it's called Word Splitting - so use quotes to prevent this.
The exception is keywords and assignment. After myvar= and inside [[, case, etc, you don't need the quotes, but they won't do any harm either - so if you're unsure: quote!
Last but not least: Remember that parameters are the data structures of bash. They hold your application data. They should NOT be used to hold your application logic. So while many ill-written scripts out there may use things like GREP=/usr/bin/grep, or command='mplayer -vo x11 -ao alsa', you should NOT do this. The main reason is because you cannot possibly do it completely right and safe and readable/maintainable.
If you want to avoid retyping the same command multiple times, or make a single place to manage the command's command line, use a function instead. Not parameters.
Special Parameters
1, 2, ...: Positional Parameters are the arguments that were passed to your script or your function.
When your script is started with ./script foo bar, "$1" will become "foo" and "$2" will become "bar". A script ran as ./script "foo bar" hubble will expand "$1" as "foo bar" and "$2" as "hubble".
*: When expanded, it equals the single string that concatenates all positional parameters using the first character of IFS to separate them (- by default, that's a space).
In short, "$*" is the same as "$1x$2x$3x$4x..." where x is the first character of IFS.
With a default IFS, that will become a simple "$1 $2 $3 $4 ...".
@: This will expand into multiple arguments: Each positional parameter that is set will be expanded as a single argument.
So basically, "$@" is the same as "$1" "$2" "$3" ..., all quoted separately.
NOTE: You should always use "$@" before "$*", because "$@" preserves the fact that each argument is its separate entity. With "$*", you loose this data! "$*" is really only useful if you want to separate your arguments by something that's not a space; for instance, a comma: (IFS=,; echo "You ran the script with the arguments: $*") -- output all your arguments, separating them by commas.
#: This parameter expands into a number that represents how many positional parameters are set.
A script executed with 5 arguments, will have "$#" expand to 5. This is mostly only useful to test whether any arguments were set: if (( ! $# )); then echo "No arguments were passed." >&2; exit 1; fi
?: Expands into the exit code of the previously completed foreground command.
We use $? mostly if we want to use the exit code of a command in multiple places; or to test it against many possible values in a case statement.
-: The dash parameter expands into the option flags that are currently set on the Bash process.
See set for an explanation of what option flags are, which exist, and what they mean.
$: The dollar parameter expands into the Process ID of the Bash process.
Handy mostly for creating a PID file for your bash process (echo "$$" > /var/run/foo.pid); so you can easily terminate it from another bash process, for example.
!: Expands into the Process ID of the most recently backgrounded command.
Use this for managing backgrounded commands from your Bash script: foo ./bar & pid=$!; sleep 10; kill "$pid"; wait "$pid"
_: Expanding the underscore argument gives you the last argument of the last command you executed.
This one's used mostly in interactive shells to shorten typing a little: mkdir -p /foo/bar && mv myfile "$_".
Arrays
Arrays are variables that contain multiple strings. Whenever you need to store multiple items in a variable, use an array and NOT a string variable. Arrays allow you to keep the elements nicely separated and allow you to cleanly expand the elements into separate arguments. This is impossible to do if you mash your items together in a string!
Creating Arrays
myarray=( foo bar quux )
Create an array myarray that contains three elements. Arrays are created using the x=(y) syntax and array elements are separated from each other by whitespace.
myarray=( "foo bar" quux )
Create an array myarray that contains two elements. To put elements in an array that contain whitespace, wrap quotes around them to indicate to bash that the quoted text belongs together in a single array element.
myfiles=( *.txt )
Create an array myfiles that contains all the filenames of the files in the current directory that end with .txt. We can use any type of expansion inside the array assignment syntax. The example use pathname expansion to replace a glob pattern by all the filenames it matches. Once replaced, array assignment happens like in the first two examples.
myfiles+=( *.html )
Add all HTML files from the current directory to the myfiles array. The x+=(y) syntax can be used the same way as the normal array assignment syntax, but append elements to the end of the array.
read -ra myarray
Chop a line into fields and store the fields in an array myarray. The read commands reads a line from stdin and uses each character in the IFS variable as a delimiter to split that line into fields.
IFS=, read -ra names <<< "John,Lucas,Smith,Yolanda"
Chop a line into fields using , as the delimiter and store the fields in the array named names. We use the <<< syntax to feed a string to the read command's stdin. IFS is set to , for the duration of the read command, causing it to split the input line into fields separated by a comma. Each field is stored as an element in the names array.
IFS=$'\n' read -d '' -ra lines
Read all lines from stdin into elements of the array named lines. We use read's -d '' switch to tell it not to stop reading after the first line, causing it to read in all of stdin. We then set IFS to a newline character, causing read to chop the input up into fields whenever a new line begins.
files=(); while IFS= read -d '' -r file; do files+=("$file"); done < <(find . -name '*.txt' -print0)
Safely read all TXT files contained recursively in the current directory into the array named files.
We begin by creating an empty array named files. We then start a while loop which runs a read statement to read in a filename from stdin, and then appends that filename (contained in the variable file) to the files array. For the read statement we set IFS to empty, avoiding read's behavior of trimming leading whitespace from the input and we set -d '' to tell read to continue reading until it sees a NUL byte (filenames CAN span multiple lines, so we don't want read to stop reading the filename after one line!). For the input, we attach the find command to while's stdin. The find command uses -print0 to output its filenames by separating them with NUL bytes (see the -d '' on read). NOTE: This is the only truly safe way of building an array of filenames from a command's output! You must delimit your filenames with NUL bytes, because it is the only byte that can't actually appear inside a filename! NEVER use ls to enumerate filenames! First try using the glob examples above, they are just as safe (no need to parse an external command), much simpler and faster.
declare -A homedirs=( ["Peter"]=~pete ["Johan"]=~jo ["Robert"]=~rob )
Create an associative array, mapping names to user home directories. Unlike normal arrays, associative arrays indices are strings (just like the values). Note: you must use declare -A when creating an associative array to indicate to bash that this array's indices are strings and not integers.
homedirs["John"]=~john
Add an element to an associative array, keyed at "John", mapped to john's home directory.
Using Arrays
cp "${myfiles[@]}" /destinationdir/
Copy all files referenced by the filenames within the myfiles array into /destinationdir/. Expanding an array happens using the syntax "${array[@]}". It effectively replaces that expansion syntax by a list of all the elements contained within the array, properly quoted as separate arguments.
rm "./${myfiles[@]}"
Remove all files referenced by the filenames within the myfiles array. It's generally a bad idea to attach strings to an array expansion syntax. What happens is: the string is only prefixed to the first element expanded from the array (or suffixed to the last if you attached the string to the end of the array expansion syntax). If myfiles contained the elements -foo.txt and bar-.html, this command would expand into: rm "./-foo.txt" "bar-.html". Notice only the first element is prefixed with ./. In this particular instance, this is handy because rm fails if the first filename begins with a dash. Now it begins with a dot.
(IFS=,; echo "${names[*]}")
Expand the array names into a single string containing all elements in the array, merging them by separating them with a comma (,). The "${array[*]}" syntax is only very rarely useful. Generally, when you see it in scripts, it is a bug. The one use it has is to merge all elements of an array into a single string for displaying to the user. Notice we surrounded the statement with (brackets), causing a subshell: This will scope the IFS assignment, resetting it after the subshell ends.
for file in "${myfiles[@]}"; do read -p "Delete $file? " && [[ $REPLY = y ]] && rm "$file"; done
Iterate over all elements of the myfiles array after expanding them into the for statement. Then, for each file, ask the user whether he wants to delete it.
for index in "${!myfiles[@]}"; do echo "File number $index is ${myfiles[index]}"; done
Iterate over all keys of the myfiles array after expanding them into the for statement. The syntax "${!array[@]}" (notice the !) gets expanded into a list of array keys, not values. Keys of normal arrays are numbers starting at 0. The syntax for getting to a particular element within an array is "${array[index]}", where index is the key of the element you want to get at.
names=(John Pete Robert); echo "${names[@]/#/Long }"
Perform a parameter expansion operation on every element of the names array. When adding a parameter expansion operation to an array expansion, the operation is applied to every single array element as it is expanded.
printf '%s\n' "${names[@]}"
Output each array element on a new line. This printf statement is a very handy technique for outputting array elements in a common way (in this case, appending a newline to each). The format string given to printf is applied to each element (unless multiple %s's appear in it, of course).
for name in "${!homedirs[@]}"; do echo "$name lives in ${homedirs[$name]}"; done
Iterate over all keys of the homedirs array after expanding them into the for statement. The syntax for getting to the keys of associative arrays is the same as that for normal arrays. Instead of numbers beginning at 0, we now get the keys for which we mapped our associative array's values. We can later use these keys to look up values within the array, just like normal arrays.
Examples: Basic Structures
Compound Commands
Command Lists
[[ $1 ]] || { echo "You need to specify an argument!" >&2; exit 1; }
We use a command group here because the || operator takes just one command.
We want both the echo and exit commands to run if $1 is empty.
(IFS=','; echo "The array contains these elements: ${array[*]}")
We use parenthesis to trigger a subshell here.
When we set the IFS variable, it will only change in the subshell and not in our main script. That avoids us having to reset it to it's default after the expansion in the echo statement (which otherwise we would have to do in order to avoid unexpected behaviour later on).
(cd "$1"; tar -cvjpf archive.tbz2 .)
Here we use the subshell to temporarily change the current directory to what's in $1.
After the tar operation (when the subshell ends), we're back to where we were before the cd command because the current directory of the main script never changed.
Expressions
((completion = current * 100 / total))
Note that arithmetic context follows completely different parsing rules than normal bash statements.
[[ $foo = /* ]] && echo "foo contains an absolute pathname."
We can use the [[ command to perform all tests that test(1) can do.
But as shown in the example it can do far more than test(1); such as glob pattern matching, regular expression matching, test grouping, etc.
Loops
for file in *.mp3; do openssl md5 "$file"; done > mysongs.md5
For loops iterate over all arguments after the in keyword.
One by one, each argument is put in the variable name file and the loop's body is executed.
DO NOT PASS A COMMAND'S OUTPUT TO for BLINDLY!
for will iterate over the WORDS in the command's output; which is almost NEVER what you really want!
for (( i = 0; i < 50; i++ )); do printf "%02d," "$i"; done
Generates a comma-separated list of numbers zero-padded to two digits.
(The last character will be a comma, yes, if you really want to get rid of it; you can - but it defeats the simplicity of this example)
while read _ line; do echo "$line"; done < file
This while loop continues so long as the read command is successful.
(Meaning, so long as lines can be read from the file). The example basically just throws out the first column of data from a file and prints the rest.
until myserver; do echo "My Server crashed with exit code: $?; restarting it in 2 seconds .."; sleep 2; done
This loop restarts myserver each time it exits with a non-successful exit code.
It assumes that when myserver exits with a non-successful exit code; it crashed and needs to restart; and if it exist with a successful exit code; you ordered it to shut down and it needn't be restarted.
select fruit in Apple Pear Grape Banana Strawberry; do (( credit -= 2, health += 5 )); echo "You purchased some $fruit. Enjoy!"; done
A simple program which converts credits into health.
Amazing.
Builtins
Dummies
while true; do ssh lhunath@lyndir.com; done
Reconnect on failure.
Declarative
alias l='ls -al'
Make an alias called l which is replaced by ls -al.
Handy for quickly viewing a directory's detailed contents.
declare -i myNumber=5
Declare an integer called myNumber initialized to the value 5.
export AUTOSSH_PORT=0
Export a variable on the bash process environment called AUTOSSH_PORT which will be inherited by any process this bash process invokes.
foo() { local bar=fooBar; echo "Inside foo(), bar is $bar"; }; echo "Setting bar to 'normalBar'"; bar=normalBar; foo; echo "Outside foo(), bar is $bar"
An exercise in variable scopes.
if ! type -P ssh >/dev/null; then echo "Please install OpenSSH." >&2; exit 1; fi
Check to see if ssh is available.
Suggest the user install OpenSSH if it is not, and exit.
Input
read firstName lastName phoneNumber address
Read data from a single line with four fields into the four named variables.
Output
echo "I really don't like $nick. He can be such a prick."
Output a simple string on standard output.
printf "I really don't like %s. He can be such a prick." "$nick"
Same thing using printf instead of echo, nicely separating the text from the data.
Execution
cd ~lhunath
Change the current directory to lhunath's home directory.
command [ "$a" = a ] || echo "$a is not 'a'!"
Execute the [ command (not the builtin!) and execute the echo statement if it fails.
source bashlib; source ~/.foorc
Run all the bash code in a file called bashlib which exists somewhere in PATH; then do the same for the file .foorc in the current directory.
exec 2>/var/log/foo.log
Send all output to standard error from now on to a log file.
echo "Fatal error occurred! Terminating!"; exit 1
Show an error message and exit the script.