diff --git a/docs/scripting/bashbehaviour.md b/docs/scripting/bashbehaviour.md new file mode 100644 index 0000000..10e9254 --- /dev/null +++ b/docs/scripting/bashbehaviour.md @@ -0,0 +1,360 @@ +# Bash\'s behaviour + +![](keywords>bash shell scripting startup files dotfiles modes POSIX) + +FIXME incomplete + +## Bash startup modes + +### Login shell + +As a \"login shell\", Bash reads and sets (executes) the user\'s profile +from `/etc/profile` and one of `~/.bash_profile`, `~/.bash_login`, or +`~/.profile` (in that order, using the first one that\'s readable!). + +When a login shell exits, Bash reads and executes commands from the file +`~/.bash_logout`, if it exists. + +Why an extra login shell mode? There are many actions and variable sets +that only make sense for the initial user login. That\'s why all UNIX(r) +shells have (should have) a \"login\" mode. + +[**Methods to start Bash as a login shell:**]{.underline} + +- the first character of `argv[0]` is `-` (a hyphen): traditional + UNIX(r) shells start from the `login` binary +- Bash is started with the `-l` option +- Bash is started with the `--login` option + +[**Methods to test for login shell mode:**]{.underline} + +- the shell option `login_shell` is set + +[**Related switches:**]{.underline} + +- `--noprofile` disables reading of all profile files + +### Interactive shell + +When Bash starts as an interactive non-login shell, it reads and +executes commands from `~/.bashrc`. This file should contain, for +example, aliases, since they need to be defined in every shell as +they\'re not inherited from the parent shell. + +The feature to have a system-wide `/etc/bash.bashrc` or a similar +system-wide rc-file is specific to vendors and distributors that ship +*their own, patched variant of Bash*. The classic way to have a +system-wide rc file is to `source /etc/bashrc` from every user\'s +`~/.bashrc`. + +[**Methods to test for interactive-shell mode:**]{.underline} + +- the special parameter `$-` contains the letter `i` (lowercase I) + +[**Related switches:**]{.underline} + +- `-i` forces the interactive mode +- `--norc` disables reading of the startup files (e.g. + `/etc/bash.bashrc` if supported) and `~/.bashrc` +- `--rcfile` defines another startup file (instead of + `/etc/bash.bashrc` and `~/.bashrc`) + +### SH mode + +When Bash starts in SH compatiblity mode, it tries to mimic the startup +behaviour of historical versions of `sh` as closely as possible, while +conforming to the POSIX(r) standard as well. The profile files read are +`/etc/profile` and `~/.profile`, if it\'s a login shell. + +If it\'s not a login shell, the environment variable +[ENV](/syntax/shellvars#ENV) is evaluated and the resulting filename is +used as the name of the startup file. + +After the startup files are read, Bash enters the [POSIX(r) compatiblity +mode (for running, not for starting!)](#posix_run_mode). + +[**Bash starts in `sh` compatiblity mode when:**]{.underline} + +- the base filename in `argv[0]` is `sh` (:!: NB: `/bin/sh` may be + linked to `/bin/bash`, but that doesn\'t mean it acts like + `/bin/bash` :!:) + +### POSIX mode + +When Bash is started in POSIX(r) mode, it follows the POSIX(r) standard +for startup files. In this mode, **interactive shells** expand the +[ENV](/syntax/shellvars#ENV) variable and commands are read and executed +from the file whose name is the expanded value.\ +No other startup files are read. Hence, a non-interactive shell doesn\'t +read any startup files in POSIX(r) mode. + +[**Bash starts in POSIX(r) mode when:**]{.underline} + +- the commandline option `--posix` is specified +- the environment variable + [POSIXLY_CORRECT](/syntax/shellvars#POSIXLY_CORRECT) is set + +### Quick startup file reference + +- Eventual system-wide rc-files are usually read when `~/.bashrc` + would be read (at least Debian GNU/Linux behaves like that) +- Regardless of the system-wide files in `/etc` which are always read, + Bash usually reads the first file found, when multiple choices are + given (for user files in `~/`) + + Mode `/etc/profile` `~/.bash_profile` `~/.bash_login` `~/.profile` `~/.bashrc` `${ENV}` + ----------------------- ---------------- ------------------- ----------------- -------------- ------------- ---------- + Login shell X X X X \- \- + Interactive shell \- \- \- \- X \- + SH compatible login X \- \- X \- \- + SH compatible \- \- \- \- \- X + POSIX(r) compatiblity \- \- \- \- \- X + +## Bash run modes + +### Normal Bash + +### POSIX run mode + +In POSIX(r) mode, Bash follows the POSIX(r) standard regarding behaviour +and parsing (excerpt from a Bash maintainer\'s document): + + Starting Bash with the `--posix' command-line option or executing `set + -o posix' while Bash is running will cause Bash to conform more closely + to the POSIX standard by changing the behavior to match that specified + by POSIX in areas where the Bash default differs. + + When invoked as `sh', Bash enters POSIX mode after reading the startup + files. + + The following lists what's changed when Bash is in `POSIX mode': + + 1. When a command in the hash table no longer exists, Bash will + re-search `$PATH' to find the new location. This is also + available with `shopt -s checkhash'. + + 2. The message printed by the job control code and builtins when a job + exits with a non-zero status is `Done(status)'. + + 3. The message printed by the job control code and builtins when a job + is stopped is `Stopped(SIGNAME)', where SIGNAME is, for example, + `SIGTSTP'. + + 4. The `bg' builtin uses the required format to describe each job + placed in the background, which does not include an indication of + whether the job is the current or previous job. + + 5. Reserved words appearing in a context where reserved words are + recognized do not undergo alias expansion. + + 6. The POSIX `PS1' and `PS2' expansions of `!' to the history number + and `!!' to `!' are enabled, and parameter expansion is performed + on the values of `PS1' and `PS2' regardless of the setting of the + `promptvars' option. + + 7. The POSIX startup files are executed (`$ENV') rather than the + normal Bash files. + + 8. Tilde expansion is only performed on assignments preceding a + command name, rather than on all assignment statements on the line. + + 9. The default history file is `~/.sh_history' (this is the default + value of `$HISTFILE'). + + 10. The output of `kill -l' prints all the signal names on a single + line, separated by spaces, without the `SIG' prefix. + + 11. The `kill' builtin does not accept signal names with a `SIG' + prefix. + + 12. Non-interactive shells exit if FILENAME in `.' FILENAME is not + found. + + 13. Non-interactive shells exit if a syntax error in an arithmetic + expansion results in an invalid expression. + + 14. Redirection operators do not perform filename expansion on the word + in the redirection unless the shell is interactive. + + 15. Redirection operators do not perform word splitting on the word in + the redirection. + + 16. Function names must be valid shell names. That is, they may not + contain characters other than letters, digits, and underscores, and + may not start with a digit. Declaring a function with an invalid + name causes a fatal syntax error in non-interactive shells. + + 17. POSIX special builtins are found before shell functions during + command lookup. + + 18. If a POSIX special builtin returns an error status, a + non-interactive shell exits. The fatal errors are those listed in + the POSIX standard, and include things like passing incorrect + options, redirection errors, variable assignment errors for + assignments preceding the command name, etc. + + 19. If `CDPATH' is set, the `cd' builtin will not implicitly append + the current directory to it. This means that `cd' will fail if no + valid directory name can be constructed from any of the entries in + `$CDPATH', even if the a directory with the same name as the name + given as an argument to `cd' exists in the current directory. + + 20. A non-interactive shell exits with an error status if a variable + assignment error occurs when no command name follows the assignment + statements. A variable assignment error occurs, for example, when + trying to assign a value to a readonly variable. + + 21. A non-interactive shell exits with an error status if the iteration + variable in a `for' statement or the selection variable in a + `select' statement is a readonly variable. + + 22. Process substitution is not available. + + 23. Assignment statements preceding POSIX special builtins persist in + the shell environment after the builtin completes. + + 24. Assignment statements preceding shell function calls persist in the + shell environment after the function returns, as if a POSIX + special builtin command had been executed. + + 25. The `export' and `readonly' builtin commands display their output + in the format required by POSIX. + + 26. The `trap' builtin displays signal names without the leading `SIG'. + + 27. The `trap' builtin doesn't check the first argument for a possible + signal specification and revert the signal handling to the original + disposition if it is, unless that argument consists solely of + digits and is a valid signal number. If users want to reset the + handler for a given signal to the original disposition, they + should use `-' as the first argument. + + 28. The `.' and `source' builtins do not search the current directory + for the filename argument if it is not found by searching `PATH'. + + 29. Subshells spawned to execute command substitutions inherit the + value of the `-e' option from the parent shell. When not in POSIX + mode, Bash clears the `-e' option in such subshells. + + 30. Alias expansion is always enabled, even in non-interactive shells. + + 31. When the `alias' builtin displays alias definitions, it does not + display them with a leading `alias ' unless the `-p' option is + supplied. + + 32. When the `set' builtin is invoked without options, it does not + display shell function names and definitions. + + 33. When the `set' builtin is invoked without options, it displays + variable values without quotes, unless they contain shell + metacharacters, even if the result contains nonprinting characters. + + 34. When the `cd' builtin is invoked in LOGICAL mode, and the pathname + constructed from `$PWD' and the directory name supplied as an + argument does not refer to an existing directory, `cd' will fail + instead of falling back to PHYSICAL mode. + + 35. When the `pwd' builtin is supplied the `-P' option, it resets + `$PWD' to a pathname containing no symlinks. + + 36. The `pwd' builtin verifies that the value it prints is the same as + the current directory, even if it is not asked to check the file + system with the `-P' option. + + 37. When listing the history, the `fc' builtin does not include an + indication of whether or not a history entry has been modified. + + 38. The default editor used by `fc' is `ed'. + + 39. The `type' and `command' builtins will not report a non-executable + file as having been found, though the shell will attempt to + execute such a file if it is the only so-named file found in + `$PATH'. + + 40. The `vi' editing mode will invoke the `vi' editor directly when + the `v' command is run, instead of checking `$FCEDIT' and + `$EDITOR'. + + 41. When the `xpg_echo' option is enabled, Bash does not attempt to + interpret any arguments to `echo' as options. Each argument is + displayed, after escape characters are converted. + + + There is other POSIX behavior that Bash does not implement by default + even when in POSIX mode. Specifically: + + 1. The `fc' builtin checks `$EDITOR' as a program to edit history + entries if `FCEDIT' is unset, rather than defaulting directly to + `ed'. `fc' uses `ed' if `EDITOR' is unset. + + 2. As noted above, Bash requires the `xpg_echo' option to be enabled + for the `echo' builtin to be fully conformant. + + + Bash can be configured to be POSIX-conformant by default, by specifying + the `--enable-strict-posix-default' to `configure' when building. + +FIXME help me to find out what breaks in POSIX(r) mode! + +[**The POSIX(r) mode can be switched on by:**]{.underline} + +- Bash starting as `sh` (the basename of `argv[0]` is `sh`) +- starting Bash with the commandline option `--posix` +- on startup, the environment variable + [POSIXLY_CORRECT](/syntax/shellvars#POSIXLY_CORRECT) is set +- the command `set -o posix` + +[**Tests for the POSIX(r) mode:**]{.underline} + +- the variable [SHELLOPTS](/syntax/shellvars#SHELLOPTS) contains + `posix` in its list + +### Restricted shell + +In restricted mode, Bash sets up (and runs) a shell environment that\'s +far more controlled and limited than the standard shell mode. It acts +like normal Bash with the following restrictions: + +- the `cd` command can\'t be used to change directories +- the variables [SHELL](/syntax/shellvars#SHELL), + [PATH](/syntax/shellvars#PATH), [ENV](/syntax/shellvars#ENV) and + [BASH_ENV](/syntax/shellvars#BASH_ENV) can\'t be set or unset +- command names that contain a `/` (slash) can\'t be called (hence + you\'re limited to `PATH`) +- filenames containing a `/` (slash) can\'t be specified as argument + to the `source` or `.` builtin command +- filenames containing a `/` (slash) can\'t be specified as argument + to the `-p` option of the `hash` builtin command +- function definitions are not inherited from the environment at shell + startup +- the environment variable [SHELLOPTS](/syntax/shellvars#SHELLOPTS) is + ignored at startup +- redirecting output using the `>`, `>|`, `<>`, `>&`, `&>`, and `>>` + redirection operators isn\'t allowed +- the `exec` builtin command can\'t replace the shell with another + process +- adding or deleting builtin commands with the `-f` and `-d` options + to the enable builtin command is forbidden +- using the `enable` builtin command to enable disabled shell builtins + doesn\'t work +- the `-p` option to the `command` builtin command doesn\'t work +- turning off restricted mode with `set +r` or `set +o restricted` is + (of course) forbidden + +The \"-r\" restrictions are turned on **after** Bash has read its +startup files. + +When the command that is run is a shell script, then the restrictions +are **turned off** for the (sub-)shell that runs that shell script. + +[**The restricted shell can be switched on by:**]{.underline} + +- calling Bash as `rbash` (the basename of `argv[0]` is `rbash`) +- calling Bash with the `-r` option +- calling Bash with the `--restricted` option + +[**Tests for restricted mode:**]{.underline} + +- the special parameter `$-` contains the letter `r` (lowercase R) +- the shell option `restricted_shell` is set and can be checked by the + `shopt` builtin command diff --git a/docs/scripting/bashchanges.md b/docs/scripting/bashchanges.md new file mode 100644 index 0000000..771507b --- /dev/null +++ b/docs/scripting/bashchanges.md @@ -0,0 +1,333 @@ +# Bash changes + +This article is an **incomplete overview** of changes to Bash over time. +Not all changes are listed, just the ones most likely to be useful for +normal scripting. The overviews are categorized by topic and ordered by +version. + +A useful starting point is the [NEWS +file](https://github.com/bminor/bash/blob/master/NEWS) in bash sources. +If you have more detailed information, or historical information about +Bash versions earlier than V2, feel free to mail me, or use the +discussion below. + +Status: 5.1 (alpha) + +## Shell options + +Note that the `shopt` builtin command first appeared in Bash 2.0. + +For this topic, see also + +- [shell_options](/internals/shell_options) +- [set](/commands/builtin/set) + + Feature or change description Appeared in Bash version See also/remarks + --------------------------------- -------------------------- --------------------------------------------------------------------------------- + `posix` (for `set -o`) 1.14.0 + `hostcomplete` 2.0-alpha3 + `expand_aliases` 2.0 + `huponexit` 2.02-alpha1 + `nocaseglob` 2.02-alpha1 + `extglob` 2.02-alpha1 together with extended globbing, KSH88 + `restricted_shell` 2.03-alpha + `xpg_echo` 2.04-beta1 + `progcomp` 2.04-alpha1 + `no_empty_command_completion` 2.04 + `login_shell` 2.05a-alpha1 + `nolog` (for `set -o`) 2.05a + `gnu_errfmt` 3.0-alpha + `force_fignore` 3.0-alpha + `failglob` 3.0-alpha + `extquote` 3.0-alpha unsure \-- verify! + `extdebug` 3.0-alpha + `pipefail` (for `set -o`) 3.0 + `functrace` (for `set -o`) 3.0 + `errtrace` (for `set -o`) 3.0 + `nocasematch` 3.1-alpha1 + `dirspell` 4.0-alpha + `globstar` 4.0-alpha + `checkjobs` 4.0-alpha + `autocd` 4.0-alpha + `set -e` effects more intuitive 4.0 not directly specified by POSIX, but in consensus with POSIX WG + `compat40` 4.1-beta + `lastpipe` 4.2-alpha only works with job control disabled + `compat41` 4.2-alpha + `globasciiranges` 4.3-alpha enable \"character range globbing\" to always act as if in `C` locale + `compat42` 4.3-alpha + `compat43` 4.4-alpha + `compat44` 5.0-alpha + `localvar_inherit` 5.0-alpha local variables inherit preceeding scope values if they have the same name + `syslog_history` 5.0-alpha send history lines to syslog (undocumented, default off) if syslog is supported + `assoc_expand_once` 5.0-alpha expand associative array subscripts only one + `globasciiranges` 5.0-beta New default: on (default may be configured at compile time) + `localvar_inherit` 5.0-beta guard code against inheriting from an incompatible data type + `checkwinsize` 5.0-beta2 New default: on + `shift_verbose` 5.0-beta2 Default on when in POSIX mode + +### General (all/many builtins) + + Feature or change description Appeared in Bash version See also/remarks + ----------------------------------------------------------------------- -------------------------- ----------------------------------------------------------------- + generally return 2 on usage error 2.0 + generally accept `--` (end of options) 2.0 + (where applicable) implement a `-p` option to produce reusable output 2.0 `shopt` and `umask` builtins were fixed to support that in 2.02 + +### printf + +For this topic, see also + +- [printf](/commands/builtin/printf) + + Feature or change description Appeared in Bash version See also/remarks + ------------------------------------------------------------- -------------------------- -------------------------------------------------------- + new `printf` command 2.02-alpha1 + respects `0..` and `0x..` prefixed numbers 2.04-beta1 consistency with arithmetic + POSIX(r) length specifiers `j`, `t` and `z` 2.05a-alpha1 ISO C99 + POSIX(r) flag `'` 2.05a-alpha1 + conversion `a` and `A` 2.05a-rc1 if provided by the underlying printf(3) + conversion `F` 2.05a-rc1 + conversion `n` 2.05a-rc1 + new option `-v` 3.1-alpha1 + escape sequences `\"` and `\?` 3.0-beta1 + modified option `-v` to assign to individual array elements 4.1-alpha + conversion `(...)T` 4.2-alpha support stftime(3) date/time format; uses current time + `\uNNNN` and `\UNNNNNNNN` escape sequences 4.2-alpha for: `printf`, `echo -e`, `$'...'` + +### Conditional expressions and test command + +For this topic, see also + +- [conditional_expression](/syntax/ccmd/conditional_expression) +- [classictest](/commands/classictest) + + Feature or change description Appeared in Bash version See also/remarks + -------------------------------------------------------------------- -------------------------- -------------------------------------------------------------------------------------- + `test`: `-o`, `==`, `<` and `>` 2.0 + `test`: `-N` 2.02 + `[[...]]`: new 2.02-alpha1 KSH93 + `[[...]]`: regex support (`=~`) 3.0-alpha + `[[...]]`: quotable right-hand-side of `=~` forces string matching 3.2-alpha for consistency with pattern matching + `[[...]]`: `<` and `>` operators respect locale 4.1-alpha for consistency, since 4.1-beta: ensure you have set compatiblity to \>4.0 (default) + `test`/`[`/`[[`: `-v` 4.2-alpha check if a variable is set + `test`/`[`/`[[`: `-v` 4.2-alpha support array syntax to check for elements + `test`/`[`/`[[`: `-N` accepts nanoseconds 5.1-alpha + `test`/`[`/`[[`: `-v` accepts positional parameters 5.1-alpha + +### Other builtins and keywords + + Builtin Feature or change description Appeared in Bash version See also/remarks + ----------------------- -------------------------------------------------------------------------------------------------------------------------------------------- -------------------------- ----------------------------------------------------------------------------------------------- + `bashbug` new 1.14.0 + `select` new 1.14.0 + `disown` new 2.0 + `shopt` new 2.0 [shopt](/commands/builtin/shopt) + `declare` new options `-a` and `-F` 2.0 + `enable` builtin has basic plugin support (dlopen) 2.0 + `exec` options `-l`, `-c` and `-a` 2.0 + `read` options `-p`, `-e` and `-a` 2.0 [read](/commands/builtin/read) + `readonly` option `-a` 2.0 [arrays](/syntax/arrays) + `time` new keyword 2.0 + `shopt` `-p` (reusable output) 2.02 + `umask` `-p` (reusable output) 2.02 + `complete` new 2.04-devel for and together with support for programmable completion + `compgen` new 2.04-devel for and together with support for programmable completion + `read` options `-t`, `-n`, `-d`, `-s` 2.04-devel [read](/commands/builtin/read) + `for ((...;...;...))` new 2.04-devel KSH93 + `set` print shell functions in a format reusable as input 2.05-beta1 + `for` allow an empty word list 2.05a-alpha1 + `read` new option `-u` 2.05b-alpha1 [read](/commands/builtin/read) + `caller` new 3.0 [caller](/commands/builtin/caller) + `coproc` new 4.0-alpha + `declare` new options `-l` and `-u` 4.0-alpha together with case-changing expansion forms + `case` new action list terminators \'\';;& and \'\';& 4.0-alpha ksh93: only `;&`. zsh and mksh: `;|`. mksh: all 4, (`;;&` is undocumented Bash compatibility) + `read` changed `-t` (fractional seconds) 4.0-alpha + `mapfile` new 4.0-alpha + `read` new option `-i` 4.0-alpha + `compopt` new 4.0-alpha + `read` modified option `-t` to test for data 4.0-beta + `read` new option `-N` 4.1-alpha + `mapfile` changed behaviour regarding history spamming 4.1-alpha + `declare` new option `-g` 4.2-alpha + `mapfile` calls the callback with an additional argument: The line (data) 4.2-alpha + `cd` new option `-e` 4.2-alpha + `echo` `\uNNNN` and `\UNNNNNNNN` escape sequences 4.2-alpha for: `printf`, `echo -e`, `$'...'` + `exec` option `-a` to give a `argv[0]` string 4.2-alpha + `time` allowed as a command by itself to display timing values of the shell and its children 4.2-alpha POSIX change + `help` `help` now searches exact topic-strings (i.e. `help read` won\'t find `readonly` anymore) 4.3-alpha + `return` accept negative values as return value (e.g. `return -1` will show as (8 bit) 255 in the caller) 4.3-alpha + `exit` accept negative values as return value (e.g. `return -1` will show as (8 bit) 255 in the caller) 4.3-alpha + `read` `read` skips `NUL` (ASCII Code 0) in input 4.3-alpha + `declare` new option `-n`/`+n` to support nameref variable type 4.3-alpha + `wait` new option `-n` to wait for the next background job to finish, returning its exit status. 4.3-alpha + `read` `read` checks first variable argument for validity before trying to read inout 4.3-beta + `help` attempts substring matching (as it did through bash-4.2) if exact string matching fails 4.3-beta2 + `fc` interprets option `-0` (zero) as the current command line 4.3-beta2 + `cd` new option `-@` to browse a file\'s extended attributes (on systems that support `O_XATTR`) 4.3-rc1 + `kill` new option `-L` (upper case ell) to list signals like the normal lowercase option `-l` (compatiblity with some standalone `kill` commands) 4.4-beta + `mapfile` new option `-d` 4.4-alpha + `wait` new option `-f` 5.0-alpha + `history` option `-d` allows negative numbers to index from the end of the history list 5.0-alpha + `umask` allows modes greater than octal 777 5.0-alpha + `times` honors current locale settings when printing decimal points 5.0-alpha + `kill` New options `-n SIGNUMBER` and `-s SIGNAME` 5.0-beta2 [kill](/commands/builtin/kill) + `select` Support for an empty wordlist following `in` 5.0-beta2 + `read` Option `-e` (use ReadLine to obtain input) now works with arbitrary file descriptors (given by `-u` option) 5.1-alpha + `trap` `-p` option prints signals with SIG_DFL/SIG_IGN on shell start (POSIX mode) 5.1-alpha + `unset` automatically tries to unset a function if the given name is an invalid variable name 5.1-aplha + `wait` option `-n` now accepts a list of jobs 5.1-alpha + `wait` new option `-p NAME` to store PID/JobID (useful when waiting for a list of jobs) 5.1-alpha + `local` new option `-p` to print local variables in the current scope 5.1-alpha + `ulimit` new option `-R` to get/set `RLIMIT_RTTIME` resource 5.1-alpha + +## Builtin variables + + Feature or change description Appeared in Bash version See also + --------------------------------------------------- -------------------------- ---------------------------------------------------------------------------------- + `HISTCMD` 1.14.0 interactive usage + `PS1`, `PS2`, `PATH`, and `IFS` are unsettable 2.0 + `DIRSTACK` array variable 2.0 + `PIPESTATUS` array variable 2.0 + `BASH_VERSINFO` array variable 2.0 + `HOSTNAME` 2.0 + `SHELLOPTS` 2.0 + `MACHTYPE` 2.0 + `GLOBIGNORE` 2.0 + `HISTIGNORE` 2.0 + respect `LC_ALL` 2.0 + respect `LC_MESSAGES` 2.0 + respect `LC_CTYPE` 2.0 + respect `LC_COLLATE` 2.0 + respect `LANG` 2.0 + `GROUPS` array variable 2.01 + `GROUPS` unsettable/takes (discarded) assignments 2.04 + `FUNCNAME` 2.04 + respect `LC_NUMERIC` 2.04 + `TMOUT` 2.05b + `BASH_REMATCH` 3.0 together with regex support in `[[...]]` + `BASH_ARGC` 3.0 debugger support + `BASH_ARGV` 3.0 debugger support + `BASH_SOURCE` 3.0 debugger support + `BASH_LINENO` 3.0 debugger support + `BASH_SUBSHELL` 3.0 debugger support + `BASH_EXECUTION_STRING` 3.0 debugger support + `BASH_COMMAND` 3.0 debugger support + `HISTTIMEFORMAT` 3.0 + `COMP_WORDBREAKS` 3.0 + respect `LC_TIME` 3.1 + `BASHPID` 4.0-alpha Added to mksh R41. + `PROMPT_DIRTRIM` 4.0 + `BASH_XTRACEFD` 4.1-alpha + `BASHOPTS` 4.1-alpha + `FUNCNEST` 4.2-alpha + `HISTSIZE` 4.3-alpha can be set to negative values for unlimited history length + `HISTFILESIZE` 4.3-alpha can be set to negative values for unlimit history file size + `CHILD_MAX` 4.3-alpha max. number of exit status of children the shell remembers + `BASH_COMPAT` 4.3-alpha set shell compatiblity levels + `EPOCHSECONDS` 5.0-alpha expands to the time in seconds since Unix epoch + `EPOCHREALTIME` 5.0-alpha expands to the time in seconds since Unix epoch with microsecond granularity + `BASH_ARGV0` 5.0-alpha get/set `$0` + `PATH` 5.0-alpha Possibility to set a static path for use in a restricted shell (at compile time) + `HISTSIZE` 5.0-beta Default can now be set at runtime + `SRANDOM` 5.1-alpha New random generator for 32bit numbers (using various methods in the backend) + `ARGV0` 5.1-alpha Respected when set in initial shell environment, then initially used to set `$0` + `BASH_REMATCH` 5.1-alpha Not readonly anymore + `PROMPT_COMMANDS` 5.1-alpha New array variable. List of commands to be executed like `PROMPT_COMMAND` + `SECONDS` 5.1-alpha Assignment using arithmetic expressions (is nominally an integer variabnle) + `RANDOM` 5.1-alpha Assignment using arithmetic expressions (is nominally an integer variabnle) + `LINENO` 5.1-alpha Not an integer variabe + +## Quoting, expansions, substitutions and related + +For this topic, see also + +- [pe](/syntax/pe). + + Feature or change description Appeared in Bash version Remarks + ------------------------------------------------------------------------------------------------------ -------------------------- --------------------------------------------------------------------------------------------------------------- + Support for integer-indexed arrays 2.0 relevant builtins also got array support + `${PARAMETER//PATTERN/REPLACEMENT}` 2.0 + `${PARAMETER:OFFSET:LENGTH}` 2.0 + `${!PARAMETER}` (indirection) 2.0 + `$"..."` (localized strings) 2.0 + `$'...'` (ANSI-C-like strings) 2.0 + `\xNNN` in `$'...'` (and `echo -e`) 2.02-alpha1 + `$(< FILENAME)` (file content) 2.02-alpha1 + globbing (`fnmatch()`) capable of POSIX(r) character classes etc. 2.02-alpha1 + extended globbing 2.02-alpha1 KSH88 + globbing inside array mass-assignment: `ARRAY=(*.txt)` 2.03-alpha + `$'...\'...'` escaped single quote inside ANSI-C-like strings 2.04-devel KSH93 + `${!PREFIX*}` (parameter name expansion) 2.04 KSH93 + `$'...'` expands `\cx` (Control-x) 2.05b + `[:class:]` syntax for pattern matching 2.05b KSH93 + `${!ARRAY[@]}` (array index expansion) 3.0-alpha KSH93 + `{x..y}` (range brace expansion) 3.0-alpha + `$'...'` expands `\xNNN` (Hexdigits) 3.0 + `+=` operator for arrays and strings 3.1-alpha1 + `${PARAMETER//PATTERN/REPLACEMENT}` behaviour changed 3.2-alpha anchoring for global substitution is no longer allowed, changes the way old syntax may work + `${@:0:x}` includes `$0` 4.0-alpha + Support for associative arrays 4.0-alpha relevant builtins also got associative array support + case modification operators for expansions 4.0-alpha + `{0x..0y}` (zeropadding brace expansion) 4.0-alpha + numerically indexed arrays can be accessed (on expansion time) from the end using negative indexes 4.1-alpha + `\uNNNN` and `\uNNNNNNNN` in `$'...'` 4.2-alpha for: `printf`, `echo -e`, `$'...'` + `${PARAMETER:OFFSET:LENGTH}`: Negative `LENGTH` values are used as offset from the end of the string 4.2-alpha Substrings only for Bash and ksh93. Works also for argument expansions in zsh. ksh93 can use `${arr[n..-m]}`. + Word expansions like `${foo##bar}` understand indirect variable references 4.3-beta + Transformations 4.4 + Process substitution now works in POSIX mode 5.1-alpha + New transformations: `U`, `u`, `L` 5.1-alpha Case-transformation + New transformation: `K` 5.1-alpha Display associative arrays as key/value pairs + +## Arithmetic + +For this topic, see also + +- [arith_expr](/syntax/arith_expr) +- [arith](/syntax/expansion/arith) + + Feature or change description Appeared in Bash version Remarks + -------------------------------------------- -------------------------- ------------------------------------------- + `((...))` 2.0-beta2 KSH93 + ternary operator 2.0 + base 64 integer constants 2.0 the max. base before is unknown. Anybody? + deprecated `$[...]` in favor of `$((...))` 2.0 + exponentiaition operator (`**`) 2.02-alpha1 + comma operator `EXPR,EXPR` 2.04-devel + pre- and postfix operators 2.04-devel + +## Redirection and related + +For this topic, see also + +- [redirection](/syntax/redirection) + + Feature or change description Appeared in Bash version Remarks + --------------------------------------------------------------------------------------- -------------------------- --------- + socket redirection (`/dev/tcp/`, `/dev/udp/`) 2.04-devel + OS/filesystem-independent support for `/dev/std(in|out|err)` and `/dev/fd/*` 2.04 + socket redirection accepts service names 2.05 + `[n]<&word-` and `[n]>&word-` FD-duplicate/closing 2.05b-alpha1 KSH93 + Here strings: `<<< WORD` 2.05b-alpha1 + `|&` (synonym for `2>&1 |`) 4.0-alpha + `&>>` (equiv. to `>>FILE 2>&1`) 4.0-alpha + `{varname}` style automatic file descriptor allocation 4.1-alpha ksh93 + `{varname[idx]}` fd allocation accepts array subscripts and special-meaning variables 4.3-alpha ksh93 + +## Misc + + Feature or change description Appeared in Bash version See also/remarks + ------------------------------------------------------------------------------------------------- -------------------------- ------------------------------------------------------------------------------------------------------------------------------------------ + `DEBUG` trap 2.0 + `ERR` trap 2.05a KSH93 + Support for multibyte characters: Unicode / UTF8 2.05b + `RETURN` trap 3.0 ksh93 `EXIT` trap evaluates in caller scope (for `function name {`). Bash `RETURN` in same scope. + `command_not_found_handle` handler function 4.0-alpha + official introduction of switchable \"compatiblity levels\" 4.0-alpha `compat31` was introduced in a 3.2 version, mainly because of the incompatibilities that were introduced by the changed `=~` operator + `[[...]]` and `((...))` conditional commands are subject to the `ERR` trap and `set -e` feature 4.1-alpha + ACL support for file status checks 4.1-alpha + Assignment to negative array indices 4.3-alpha ksh93, zsh + `declare`/`typeset -n` 4.3-alpha Support for nameref variable type, a variable referencing another one by name + shells started to run process substitutions now run any trap set on `EXIT` 4.3-beta + process substitution does not inherit the `v` flag 5.0-alpha + `ERR` trap 5.0-alpha Reports more reliable line numbers + Variable assignment 5.0-beta Assignments preceeding a special builtin that chages variable attributes are not propagated back unless compatiblity mode is 44 or lower diff --git a/docs/scripting/basics.md b/docs/scripting/basics.md new file mode 100644 index 0000000..3fc3571 --- /dev/null +++ b/docs/scripting/basics.md @@ -0,0 +1,357 @@ +# The basics of shell scripting + +![](keywords>bash shell scripting basics learning tutorial) + +## Script files + +A shell script usually resides inside a file. The file can be +executable, but you can call a Bash script with that filename as a +parameter: + + bash ./myfile + +There is **no need to add a boring filename extension** like `.bash` or +`.sh`. That is a holdover from UNIX(r), where executables are not tagged +by the extension, but by **permissions** (filemode). The file name can +be any combination of legal filename characters. Adding a proper +filename extension is a convention, nothing else. + + chmod +x ./myfile + +If the file is executable, and you want to use it by calling only the +script name, the shebang must be included in the file. + +## The Shebang + +The in-file specification of the interpreter of that file, for example: + +``` bash +#!/bin/bash +echo "Hello world..." +``` + +This is interpreted by the kernel [^1] of your system. In general, if a +file is executable, but not an executable (binary) program, and such a +line is present, the program specified after `#!` is started with the +scriptname and all its arguments. These two characters `#` and `!` must +be **the first two bytes** in the file! + +You can follow the process by using `echo` as a fake interpreter: + + #!/bin/echo + +We don\'t need a script body here, as the file will never be interpreted +and executed by \"`echo`\". You can see what the Operating System does, +it calls \"`/bin/echo`\" with the name of the executable file and +following arguments. + + $ /home/bash/bin/test testword hello + /home/bash/bin/test testword hello + +The same way, with `#!/bin/bash` the shell \"`/bin/bash`\" is called +with the script filename as an argument. It\'s the same as executing +\"`/bin/bash /home/bash/bin/test testword hello`\" + +If the interpreter can be specified with arguments and how long it can +be is system-specific (see +[#!-magic](http://www.in-ulm.de/~mascheck/various/shebang/)). When Bash +executes a file with a #!/bin/bash shebang, the shebang itself is +ignored, since the first character is a hashmark \"#\", which indicates +a comment. The shebang is for the operating system, not for the shell. +Programs that don\'t ignore such lines, may not work as shebang driven +interpreters. + +\ [**Attention:**]{.underline}When the +specified interpreter is unavailable or not executable (permissions), +you usually get a \"`bad interpreter`\" error message., If you get +nothing and it fails, check the shebang. Older Bash versions will +respond with a \"`no such file or directory`\" error for a nonexistant +interpreter specified by the shebang. \ + +**Additional note:** When you specify `#!/bin/sh` as shebang and that\'s +a link to a Bash, then Bash will run in POSIX(r) mode! See: + +- [Bash behaviour](/scripting/bashbehaviour). + +A common method is to specify a shebang like + + #!/usr/bin/env bash + +\...which just moves the location of the potential problem to + +- the `env` utility must be located in /usr/bin/ +- the needed `bash` binary must be located in `PATH` + +Which one you need, or whether you think which one is good, or bad, is +up to you. There is no bulletproof portable way to specify an +interpreter. **It\'s a common misconception that it solves all problems. +Period.** + +## The standard filedescriptors + +Once Initialized, every normal UNIX(r)-program has *at least 3 open +files*: + +- **stdin**: standard input +- **stdout**: standard output +- **stderr**: standard error output + +Usually, they\'re all connected to your terminal, stdin as input file +(keyboard), stdout and stderr as output files (screen). When calling +such a program, the invoking shell can change these filedescriptor +connections away from the terminal to any other file (see redirection). +Why two different output filedescriptors? It\'s convention to send error +messages and warnings to stderr and only program output to stdout. This +enables the user to decide if they want to see nothing, only the data, +only the errors, or both - and where they want to see them. + +When you write a script: + +- always read user-input from `stdin` +- always write diagnostic/error/warning messages to `stderr` + +To learn more about the standard filedescriptors, especially about +redirection and piping, see: + +- [An illustrated redirection tutorial](/howto/redirection_tutorial) + +## Variable names + +It\'s good practice to use lowercase names for your variables, as shell +and system-variable names are usually all in UPPERCASE. However, you +should avoid naming your variables any of the following (incomplete +list!): + + ---------------- ------------- ---------------- --------------- ------------------ ----------------- + `BASH` `BASH_ARGC` `BASH_ARGV` `BASH_LINENO` `BASH_SOURCE` `BASH_VERSINFO` + `BASH_VERSION` `COLUMNS` `DIRSTACK` `DISPLAY` `EDITOR` `EUID` + `GROUPS` `HISTFILE` `HISTFILESIZE` `HISTSIZE` `HOME` `HOSTNAME` + `IFS` `LANG` `LANGUAGE` `LC_ALL` `LINES` `LOGNAME` + `LS_COLORS` `MACHTYPE` `MAILCHECK` `OLDPWD` `OPTERR` `OPTIND` + `OSTYPE` `PATH` `PIPESTATUS` `PPID` `PROMPT_COMMAND` `PS1` + `PS2` `PS4` `PS3` `PWD` `SHELL` `SHELLOPTS` + `SHLVL` `TERM` `UID` `USER` `USERNAME` `XAUTHORITY` + ---------------- ------------- ---------------- --------------- ------------------ ----------------- + +This list is incomplete. **The safest way is to use all-lowercase +variable names.** + +## Exit codes + +Every program you start terminates with an exit code and reports it to +the operating system. This exit code can be utilized by Bash. You can +show it, you can act on it, you can control script flow with it. The +code is a number between 0 and 255. Values from 126 to 255 are reserved +for use by the shell directly, or for special purposes, like reporting a +termination by a signal: + +- **126**: the requested command (file) was found, but can\'t be + executed +- **127**: command (file) not found +- **128**: according to ABS it\'s used to report an invalid argument + to the exit builtin, but I wasn\'t able to verify that in the source + code of Bash (see code 255) +- **128 + N**: the shell was terminated by the signal N +- **255**: wrong argument to the exit builtin (see code 128) + +The lower codes 0 to 125 are not reserved and may be used for whatever +the program likes to report. A value of 0 means **successful** +termination, a value not 0 means **unsuccessful** termination. This +behavior (== 0, != 0) is also what Bash reacts to in some flow control +statements. + +An example of using the exit code of the program `grep` to check if a +specific user is present in /etc/passwd: + +``` bash +if grep ^root /etc/passwd; then + echo "The user root was found" +else + echo "The user root was not found" +fi +``` + +A common decision making command is \"`test`\" or its equivalent +\"`[`\". But note that, when calling test with the name \"`[`\", the +square brackets are not part of the shell syntax, the left bracket +**is** the test command! + +``` bash +if [ "$mystring" = "Hello world" ]; then + echo "Yeah dude, you entered the right words..." +else + echo "Eeeek - go away..." +fi +``` + +Read more about [the test command](/commands/classictest) + +A common exit code check method uses the \"`||`\" or \"`&&`\" operators. +This lets you execute a command based on whether or not the previous +command completed successfully: + +``` bash +grep ^root: /etc/passwd >/dev/null || echo "root was not found - check the pub at the corner." +which vi && echo "Your favourite editor is installed." +``` + +Please, when your script exits on errors, provide a \"FALSE\" exit code, +so others can check the script execution. + +## Comments + +In a larger, or complex script, it\'s wise to comment the code. Comments +can help with debugging or tests. Comments start with the \# character +(hashmark) and continue to the end of the line: + +``` bash +#!/bin/bash +# This is a small script to say something. +echo "Be liberal in what you accept, and conservative in what you send" # say something +``` + +The first thing was already explained, it\'s the so-called shebang, for +the shell, **only a comment**. The second one is a comment from the +beginning of the line, the third comment starts after a valid command. +All three syntactically correct. + +### Block commenting + +To temporarily disable complete blocks of code you would normally have +to prefix every line of that block with a \# (hashmark) to make it a +comment. There\'s a little trick, using the pseudo command `:` (colon) +and input redirection. The `:` does nothing, it\'s a pseudo command, so +it does not care about standard input. In the following code example, +you want to test mail and logging, but not dump the database, or execute +a shutdown: + +``` bash +#!/bin/bash +# Write info mails, do some tasks and bring down the system in a safe way +echo "System halt requested" | mail -s "System halt" netadmin@example.com +logger -t SYSHALT "System halt requested" + +##### The following "code block" is effectively ignored +: <<"SOMEWORD" +/etc/init.d/mydatabase clean_stop +mydatabase_dump /var/db/db1 /mnt/fsrv0/backups/db1 +logger -t SYSHALT "System halt: pre-shutdown actions done, now shutting down the system" +shutdown -h NOW +SOMEWORD +##### The ignored codeblock ends here +``` + +What happened? The `:` pseudo command was given some input by +redirection (a here-document) - the pseudo command didn\'t care about +it, effectively, the entire block was ignored. + +The here-document-tag was quoted here **to avoid substitutions** in the +\"commented\" text! Check [redirection with +here-documents](/syntax/redirection#tag_heredoc) for more + +## Variable scope + +In Bash, the scope of user variables is generally *global*. That means, +it does **not** matter whether a variable is set in the \"main program\" +or in a \"function\", the variable is defined everywhere. + +Compare the following *equivalent* code snippets: + +``` bash +myvariable=test +echo $myvariable +``` + +``` bash +myfunction() { + myvariable=test +} + +myfunction +echo $myvariable +``` + +In both cases, the variable `myvariable` is set and accessible from +everywhere in that script, both in functions and in the \"main +program\". + +**[Attention:]{.underline}** When you set variables in a child process, +for example a *subshell*, they will be set there, but you will **never** +have access to them outside of that subshell. One way to create a +subshell is the pipe. It\'s all mentioned in a small article about [Bash +in the processtree](/scripting/processtree)! + +### Local variables + +Bash provides ways to make a variable\'s scope *local* to a function: + +- Using the `local` keyword, or +- Using `declare` (which will *detect* when it was called from within + a function and make the variable(s) local). + +``` bash +myfunc() { +local var=VALUE + +# alternative, only when used INSIDE a function +declare var=VALUE + +... +} +``` + +The *local* keyword (or declaring a variable using the `declare` +command) tags a variable to be treated *completely local and separate* +inside the function where it was declared: + +``` bash +foo=external + +printvalue() { +local foo=internal + +echo $foo +} + + +# this will print "external" +echo $foo + +# this will print "internal" +printvalue + +# this will print - again - "external" +echo $foo +``` + +### Environment variables + +The environment space is not directly related to the topic about scope, +but it\'s worth mentioning. + +Every UNIX(r) process has a so-called *environment*. Other items, in +addition to variables, are saved there, the so-called *environment +variables*. When a child process is created (in Bash e.g. by simply +executing another program, say `ls` to list files), the whole +environment *including the environment variables* is copied to the new +process. Reading that from the other side means: **Only variables that +are part of the environment are available in the child process.** + +A variable can be tagged to be part of the environment using the +`export` command: + +``` bash +# create a new variable and set it: +# -> This is a normal shell variable, not an environment variable! +myvariable="Hello world." + +# make the variable visible to all child processes: +# -> Make it an environment variable: "export" it +export myvariable +``` + +Remember that the *exported* variable is a **copy**. There is no +provision to \"copy it back to the parent.\" See the article about [Bash +in the process tree](/scripting/processtree)! + +[^1]: under specific circumstances, also by the shell itself diff --git a/docs/scripting/debuggingtips.md b/docs/scripting/debuggingtips.md new file mode 100644 index 0000000..d172cfd --- /dev/null +++ b/docs/scripting/debuggingtips.md @@ -0,0 +1,374 @@ +# Debugging a script + +![](keywords>bash shell scripting bug debug debugging) + +These few lines are not intended as a full-fledged debugging tutorial, +but as hints and comments about debugging a Bash script. + +## Use a unique name for your script + +Do **not** name your script `test`, for example! *Why?* `test` is the +name of a UNIX(r)-command, and [most likely built into your +shell]{.underline} (it\'s a built-in in Bash) - so you won\'t be able to +run a script with the name `test` in a normal way. + +**Don\'t laugh!** This is a classic mistake :-) + +## Read the error messages + +Many people come into IRC and ask something like *\"Why does my script +fail? I get an error!\"*. And when you ask them what the error message +is, they don\'t even know. Beautiful. + +Reading and interpreting error messages is 50% of your job as debugger! +Error messages actually **mean** something. At the very least, they can +give you hints as to where to start debugging. **READ YOUR ERROR +MESSAGES!** + +You may ask yourself why is this mentioned as debugging tip? Well, [you +would be surprised how many shell users ignore the text of error +messages!]{.underline} When I find some time, I\'ll paste 2 or 3 IRC +log-snips here, just to show you that annoying fact. + +## Use a good editor + +Your choice of editor is a matter of personal preference, but one with +**Bash syntax highlighting** is highly recommended! Syntax highlighting +helps you see (you guessed it) syntax errors, such as unclosed quotes +and braces, typos, etc. + +From my personal experience, I can suggest `vim` or `GNU emacs`. + +## Write logfiles + +For more complex scripts, it\'s useful to write to a log file, or to the +system log. Nobody can debug your script without knowing what actually +happened and what went wrong. + +An available syslog interface is `logger` ([online +manpage](http://unixhelp.ed.ac.uk/CGI/man-cgi?logger+1)). + +## Inject debugging code + +Insert **echos** everywhere you can, and print to `stderr`: + + echo "DEBUG: current i=$i" >&2 + +If you read input from **anywhere**, such as a file or [command +substitution](/syntax/expansion/cmdsubst), print the debug output with +literal quotes, to see leading and trailing spaces! + + pid=$(< fooservice.pid) + echo "DEBUG: read from file: pid=\"$pid\"" >&2 + +Bash\'s [printf](/commands/builtin/printf) command has the `%q` format, +which is handy for verifying whether strings are what they appear to be. + + foo=$(< inputfile) + printf "DEBUG: foo is |%q|\n" "$foo" >&2 + # exposes whitespace (such as CRs, see below) and non-printing characters + +## Use shell debug output + +There are two useful debug outputs for that task (both are written to +`stderr`): + +- `set -v` mode (`set -o verbose`) + - print commands to be executed to `stderr` as if they were read + from input (script file or keyboard) + - print everything **before** any ([substitution and + expansion](/syntax/expansion/intro), \...) is applied +- `set -x` mode (`set -o xtrace`) + - print everything as if it were executed, after [substitution and + expansion](/syntax/expansion/intro) is applied + - indicate the depth-level of the subshell (by default by + prefixing a `+` (plus) sign to the displayed command) + - indicate the recognized words after [word + splitting](/syntax/expansion/wordsplit) by marking them like + `'x y'` + - in shell version 4.1, this debug output can be printed to a + configurable file descriptor, rather than sdtout by setting the + [BASH_XTRACEFD](/syntax/shellvars#BASH_XTRACEFD) variable. + +**[Hint:]{.underline}** These modes can be entered when calling Bash: + +- from commandline: `bash -vx ./myscript` +- from shebang (OS dependant): `#!/bin/bash -vx` + +### Simple example of how to interpret xtrace output + +Here\'s a simple command (a string comparison using the [classic test +command](/commands/classictest)) executed while in `set -x` mode: + + set -x + foo="bar baz" + [ $foo = test ] + +That fails. Why? Let\'s see the `xtrace` output: + + + '[' bar baz = test ']' + +And now you see that it\'s (\"bar\" and \"baz\") recognized as two +separate words (which you would have realized if you READ THE ERROR +MESSAGES ;) ). Let\'s check it\... + + # next try + [ "$foo" = test ] + +`xtrace` now gives + + + '[' 'bar baz' = test ']' + ^ ^ + word markers! + +### Making xtrace more useful + +(by AnMaster) + +`xtrace` output would be more useful if it contained source file and +line number. Add this assignment [PS4](/syntax/shellvars#PS4) at the +beginning of your script to enable the inclusion of that information: + + export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' + +**Be sure to use single quotes here!** + +The output would look like this when you trace code *outside a +function*: + + +(somefile.bash:412): echo 'Hello world' + +\...and like this when you trace code *inside a function*: + + +(somefile.bash:412): myfunc(): echo 'Hello world' + +That helps a lot when the script is long, or when the main script +sources many other files. + +#### Set flag variables with descriptive words + +If you test variables that flag the state of options, such as with +`if [[ -n $option ]];`, consider using descriptive words rather than +short codes, such as 0, 1, Y, N, because xtrace will show +`[[ -n word ]]` rather than `[[ -n 1 ]]` when the option is set. + +## Debugging commands depending on a set variable + +For general debugging purposes you can also define a function and a +variable to use: + + debugme() { + [[ $script_debug = 1 ]] && "$@" || : + # be sure to append || : or || true here or use return 0, since the return code + # of this function should always be 0 to not influence anything else with an unwanted + # "false" return code (for example the script's exit code if this function is used + # as the very last command in the script) + } + +This function does nothing when `script_debug` is unset or empty, but it +executes the given parameters as commands when `script_debug` is set. +Use it like this: + + script_debug=1 + # to turn it off, set script_debug=0 + + debugme logger "Sorting the database" + database_sort + debugme logger "Finished sorting the database, exit code $?" + +Of course this can be used to execute something other than echo during +debugging: + + debugme set -x + # ... some code ... + debugme set +x + +## Dry-run STDIN driven commands + +Imagine you have a script that runs FTP commands using the standard FTP +client: + +``` bash +ftp user@host < delimited by end-of-file (wanted `') `` + +### Unexpected end of file while looking for matching \... + + script.sh: line 50: unexpected EOF while looking for matching `"' + script.sh: line 100: syntax error: unexpected end of file + +This one indicates the double-quote opened in line 50 does not have a +matching closing quote. + +These *unmatched errors* occur with: + +- double-quote pairs +- single-quote pairs (also `$'string'`!) +- missing a closing `}` with [parameter expansion syntax](/syntax/pe) + +### Too many arguments + + bash: test: too many arguments + +You most likely forgot to quote a variable expansion somewhere. See the +example for `xtrace` output from above. External commands may display +such an error message though in our example, it was the **internal** +test-command that yielded the error. + +### !\": event not found + + $ echo "Hello world!" + bash: !": event not found + +This is not an error per se. It happens in interactive shells, when the +C-Shell-styled history expansion (\"`!searchword`\") is enabled. This is +the default. Disable it like this: + + set +H + # or + set +o histexpand + +### syntax error near unexpected token \`(\' + +When this happens during a script **function definition** or on the +commandline, e.g. + + $ foo () { echo "Hello world"; } + bash: syntax error near unexpected token `(' + +you most likely have an alias defined with the same name as the function +(here: `foo`). Alias expansion happens before the real language +interpretion, thus the alias is expanded and makes your function +definition invalid. + +## The CRLF issue + +### What is the CRLF issue? + +There\'s a big difference in the way that UNIX(r) and Microsoft(r) (and +possibly others) handle the **line endings** of plain text files. The +difference lies in the use of the CR (Carriage Return) and LF (Line +Feed) characters. + +- MSDOS uses: `\r\n` (ASCII `CR` #13 `^M`, ASCII LF #10) +- UNIX(r) uses: `\n` (ASCII `LF` #10) + +Keep in mind your script is a **plain text file**, and the `CR` +character means nothing special to UNIX(r) - it is treated like any +other character. If it\'s printed to your terminal, a carriage return +will effectively place the cursor at the beginning of the *current* +line. This can cause much confusion and many headaches, since lines +containing CRs are not what they appear to be when printed. In summary, +CRs are a pain. + +### How did a CR end up in my file? + +Some possible sources of CRs: + +- a DOS/Windows text editor +- a UNIX(r) text editor that is \"too smart\" when determining the + file content type (and thinks \"*it\'s a DOS text file*\") +- a direct copy and paste from certain webpages (some pastebins are + known for this) + +### Why do CRs hurt? + +CRs can be a nuisance in various ways. They are especially bad when +present in the shebang/interpreter specified with `#!` in the very first +line of a script. Consider the following script, written with a +Windows(r) text editor (`^M` is a symbolic representation of the `CR` +carriage return character!): + + #!/bin/bash^M + ^M + echo "Hello world"^M + ... + +Here\'s what happens because of the `#!/bin/bash^M` in our shebang: + +- the file `/bin/bash^M` doesn\'t exist (hopefully) +- So Bash prints an error message which (depending on the terminal, + the Bash version, or custom patches!) may or may not expose the + problem. +- the script can\'t be executed + +The error message can vary. If you\'re lucky, you\'ll get: + + bash: ./testing.sh: /bin/bash^M: bad interpreter: No such file or directory + +which alerts you to the CR. But you may also get the following: + + : bad interpreter: No such file or directory + +Why? Because when printed literally, the `^M` makes the cursor go back +to the beginning of the line. The whole error message is *printed*, but +you *see* only part of it! + +\ It\'s easy to imagine the `^M` is bad in other places +too. If you get weird and illogical messages from your script, rule out +the possibility that`^M` is involved. Find and eliminate it! \ + +### How can I find and eliminate them? + +**To display** CRs (these are only a few examples) + +- in VI/VIM: `:set list` +- with `cat(1)`: `cat -v FILE` + +**To eliminate** them (only a few examples) + +- blindly with `tr(1)`: `tr -d '\r' FILE.new` +- controlled with `recode(1)`: `recode MSDOS..latin1 FILE` +- controlled with `dos2unix(1)`: `dos2unix FILE` + +## See also + +- [the set builtin command](/commands/builtin/set) (for `-v` and `-x`) + +FIXME + +- DEBUG trap +- BASH Debugger diff --git a/docs/scripting/newbie_traps.md b/docs/scripting/newbie_traps.md new file mode 100644 index 0000000..e494181 --- /dev/null +++ b/docs/scripting/newbie_traps.md @@ -0,0 +1,282 @@ +# Beginner Mistakes + +![](keywords>bash shell scripting pitfalls traps beginners) + +Here are some typical traps: + +## Script execution + +### Your perfect Bash script executes with syntax errors + +If you write Bash scripts with Bash specific syntax and features, run +them with [Bash]{.underline}, and run them with Bash in [native +mode]{.underline}. + +**Wrong**: + +- no shebang + - the interpreter used depends on the OS implementation and + current shell + - **can** be run by calling bash with the script name as an + argument, e.g. `bash myscript` +- `#!/bin/sh` shebang + - depends on what `/bin/sh` actually is, for a Bash it means + compatiblity mode, **not** native mode + +See also: + +- [Bash startup mode: SH mode](/scripting/bashbehaviour#sh_mode) +- [Bash run mode: POSIX mode](/scripting/bashbehaviour#posix_run_mode) + +### Your script named \"test\" doesn\'t execute + +Give it another name. The executable `test` already exists. + +In Bash it\'s a builtin. With other shells, it might be an executable +file. Either way, it\'s bad name choice! + +Workaround: You can call it using the pathname: + + /home/user/bin/test + +## Globbing + +### Brace expansion is not globbing + +The following command line is not related to globbing (filename +expansion): + + # YOU EXPECT + # -i1.vob -i2.vob -i3.vob .... + + echo -i{*.vob,} + + # YOU GET + # -i*.vob -i + +**Why?** The brace expansion is simple text substitution. All possible +text formed by the prefix, the postfix and the braces themselves are +generated. In the example, these are only two: `-i*.vob` and `-i`. The +filename expansion happens **after** that, so there is a chance that +`-i*.vob` is expanded to a filename - if you have files like +`-ihello.vob`. But it definitely doesn\'t do what you expected. + +Please see: + +- [brace](/syntax/expansion/brace) + +## Test-command + +- `if [ $foo ] ...` +- `if [-d $dir] ...` +- \... + +Please see: + +- [The classic test command - + pitfalls](/commands/classictest#pitfalls_summarized) + +## Variables + +### Setting variables + +#### The Dollar-Sign + +There is no `$` (dollar-sign) when you reference the **name** of a +variable! Bash is not PHP! + + # THIS IS WRONG! + $myvar="Hello world!" + +A variable name preceeded with a dollar-sign always means that the +variable gets **expanded**. In the example above, it might expand to +nothing (because it wasn\'t set), effectively resulting in\... + + ="Hello world!" + +\...which **definitely is wrong**! + +When you need the **name** of a variable, you write **only the name**, +for example + +- (as shown above) to set variables: + `picture=/usr/share/images/foo.png` +- to name variables to be used by the `read` builtin command: + `read picture` +- to name variables to be unset: `unset picture` + +When you need the **content** of a variable, you prefix its name with +**a dollar-sign**, like + +- echo \"The used picture is: \$picture\" + +#### Whitespace + +Putting spaces on either or both sides of the equal-sign (`=`) when +assigning a value to a variable **will** fail. + + # INCORRECT 1 + example = Hello + + # INCORRECT 2 + example= Hello + + # INCORRECT 3 + example =Hello + +The only valid form is **no spaces between the variable name and +assigned value**: + + # CORRECT 1 + example=Hello + + # CORRECT 2 + example=" Hello" + +### Expanding (using) variables + +A typical beginner\'s trap is quoting. + +As noted above, when you want to **expand** a variable i.e. \"get the +content\", the variable name needs to be prefixed with a dollar-sign. +But, since Bash knows various ways to quote and does word-splitting, the +result isn\'t always the same. + +Let\'s define an example variable containing text with spaces: + + example="Hello world" + + Used form result number of words + -------------- --------------- ----------------- + `$example` `Hello world` 2 + `"$example"` `Hello world` 1 + `\$example` `$example` 1 + `'$example'` `$example` 1 + +If you use parameter expansion, you **must** use the **name** (`PATH`) +of the referenced variables/parameters. i.e. **not** (`$PATH`): + + # WRONG! + echo "The first character of PATH is ${$PATH:0:1}" + + # CORRECT + echo "The first character of PATH is ${PATH:0:1}" + +Note that if you are using variables in [arithmetic +expressions](/syntax/arith_expr), then the bare **name** is allowed: + + ((a=$a+7)) # Add 7 to a + ((a = a + 7)) # Add 7 to a. Identical to the previous command. + ((a += 7)) # Add 7 to a. Identical to the previous command. + + a=$((a+7)) # POSIX-compatible version of previous code. + +Please see: + +- [words](/syntax/words) +- [quoting](/syntax/quoting) +- [wordsplit](/syntax/expansion/wordsplit) +- [pe](/syntax/pe) + +### Exporting + +Exporting a variable means giving **newly created** (child-)processes a +copy of that variable. It does **not** copy a variable created in a +child process back to the parent process. The following example does +**not** work, since the variable `hello` is set in a child process (the +process you execute to start that script `./script.sh`): + + $ cat script.sh + export hello=world + + $ ./script.sh + $ echo $hello + $ + +Exporting is one-way. The direction is from parent process to child +process, not the reverse. The above example **will** work, when you +don\'t execute the script, but include (\"source\") it: + + $ source ./script.sh + $ echo $hello + world + $ + +In this case, the export command is of no use. + +Please see: + +- [processtree](/scripting/processtree) + +## Exit codes + +### Reacting to exit codes + +If you just want to react to an exit code, regardless of its specific +value, you **don\'t need** to use `$?` in a test command like this: + +``` bash +grep ^root: /etc/passwd >/dev/null 2>&1 + +if [ $? -ne 0 ]; then + echo "root was not found - check the pub at the corner" +fi +``` + +This can be simplified to: + +``` bash +if ! grep ^root: /etc/passwd >/dev/null 2>&1; then + echo "root was not found - check the pub at the corner" +fi +``` + +Or, simpler yet: + +``` bash +grep ^root: /etc/passwd >/dev/null 2>&1 || echo "root was not found - check the pub at the corner" +``` + +If you need the specific value of `$?`, there\'s no other choice. But if +you need only a \"true/false\" exit indication, there\'s no need for +`$?`. + +See also: + +- [Exit codes](/scripting/basics#exit_codes) + +### Output vs. Return Value + +It\'s important to remember the different ways to run a child command, +and whether you want the output, the return value, or neither. + +When you want to run a command (or a pipeline) and save (or print) the +**output**, whether as a string or an array, you use Bash\'s +`$(command)` syntax: + + $(ls -l /tmp) + newvariable=$(printf "foo") + +When you want to use the **return value** of a command, just use the +command, or add ( ) to run a command or pipeline in a subshell: + + if grep someuser /etc/passwd ; then + # do something + fi + + if ( w | grep someuser | grep sqlplus ) ; then + # someuser is logged in and running sqlplus + fi + +Make sure you\'re using the form you intended: + + # WRONG! + if $(grep ERROR /var/log/messages) ; then + # send alerts + fi + +Please see: + +- [intro](/syntax/ccmd/intro) +- [cmdsubst](/syntax/expansion/cmdsubst) +- [grouping_subshell](/syntax/ccmd/grouping_subshell) diff --git a/docs/scripting/nonportable.md b/docs/scripting/nonportable.md new file mode 100644 index 0000000..3d99885 --- /dev/null +++ b/docs/scripting/nonportable.md @@ -0,0 +1,204 @@ +# Portability talk + +![](keywords>bash shell scripting portability POSIX portable) + +The script programming language of BASH is based on the Bourne Shell +syntax, with some extensions and derivations. + +If scripts need to be portable, some of the BASH-specific syntax +elements should be avoided. Others should be avoided for all scripts, +e.g. if there is a corresponding POSIX(r)-compatible syntax (see +[obsolete](/scripting/obsolete)). + +Some syntax elements have a BASH-specific, and a portable[^1]) pendant. +In these cases the portable syntax should be preferred. + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + construct portable equivalent Description Portability + ------------------------------------------ --------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ----------------------------------------------- + `source\ FILE` `. FILE` include a script file Bourne shell (bash, ksh, POSIX(r), zsh, \...) + + `declare`\\ keyword `typeset` keyword define local variables (or variables with special attributes) ksh, zsh, \..., **not POSIX!** + + `command\ <<<\ WORD` `command <\ TEMPFILE COMMAND\ <\ TEMPFILE` Process substitution (here used with redirection); use the old fashioned way (tempfiles) POSIX(r) and others + + `((echo X);(echo Y))` `( (echo X); (echo Y) )` Nested subshells (separate the inner `()` from the outer `()` by spaces, to not confuse the shell regarding arithmetic control operators) POSIX(r) and others + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +## Portability rationale + +Here is some assorted portability information. Take it as a small guide +to make your scripts a bit more portable. It\'s not complete (it never +will be!) and it\'s not very detailed (e.g. you won\'t find information +about how which shell technically forks off which subshell). It\'s just +an assorted small set of portability guidelines. *-Thebonsai* + +FIXME UNIX shell gurus out there, please be patient with a newbie like +me and give comments and hints instead of flames. + +### Environment (exported) variables + +When a new value is assigned to an **existing environment variable**, +there are two possibilities: + +The *new value* is seen by subsequent programs + +- without any special action (e.g. Bash) +- only after an explicit export with `export VARIABLE` (e.g. Sun\'s + `/bin/sh`) + +Since an extra `export` doesn\'t hurt, the safest and most portable way +is to always (re-)export a changed variable if you want it to be seen by +subsequent processes. + +### Arithmetics + +Bash has a special compound command to do arithmetic without expansion. +However, POSIX has no such command. In the table at the top, there\'s +the `: $((MATH))` construct mentioned as possible alternative. Regarding +the exit code, a 100% equivalent construct would be: + + # Bash (or others) compound command + if ((MATH)); then + ... + + # portable equivalent command + if [ "$((MATH))" -ne 0 ]; then + ... + +Quotes around the arithmetic expansion `$((MATH))` should not be +necessary as per POSIX, but Bash and AT&T-KSH perform word-splitting on +aritrhmetic expansions, so the most portable is *with quotes*. + +### echo command + +The overall problem with `echo` is, that there are 2 (maybe more) +mainstream flavours around. The only case where you may safely use an +`echo` on all systems is: Echoing non-variable arguments that don\'t +start with a `-` (dash) and don\'t contain a `\` (backslash). + +Why? (list of known behaviours) + +- may or may not automatically interpret backslash escpape codes in + the strings +- may or may not automatically interpret switches (like `-n`) +- may or may not ignore \"end of options\" tag (`--`) +- `echo -n` and `echo -e` are neither portable nor standard (**even + within the same shell**, depending on the version or environment + variables or the build options, especially KSH93 and Bash) + +For these, and possibly other, reasons, POSIX (SUS) standardized the +existance of [the `printf` command](/commands/builtin/printf). + +### Parameter expansions + +- `${var:x:x}` is KSH93/Bash specific +- `${var/../..}` and `${var//../..}` are KSH93/Bash specific +- `var=$*` and `var=$@` are not handled the same in all shells if the + first char of IFS is not \" \" (space). `var="$*"` should work + (except the Bourne shell always joins the expansions with space) + +### Special variables + +#### PWD + +[PWD](/syntax/shellvars#PWD) is POSIX but not Bourne. Most shells are +*not POSIX* in that they don\'t ignore the value of the `PWD` +environment variable. Workaround to fix the value of `PWD` at the start +of your script: + + pwd -P > dev/null + +#### RANDOM + +[RANDOM](/syntax/shellvars#RANDOM) is Bash/KSH/ZSH specific variable +that will give you a random number up to 32767 (2\^15-1). Among many +other available external options, you can use awk to generate a random +number. There are multiple implementations of awk and which version your +system uses will depend. Most modern systems will call \'gawk\' (i.e. +GNU awk) or \'nawk\'. \'oawk\' (i.e. Original/Old awk) does not have the +rand() or srand() functions, so is best avoided. + + # 'gawk' can produce random numbers using srand(). In this example, 10 integers between 1 and 500: + randpm=$(gawk -v min=1 -v max=500 -v nNum=10 'BEGIN { srand(systime() + PROCINFO["pid"]); for (i = 0; i < nNum; ++i) {print int(min + rand() * (max - min)} }') + + # 'nawk' and 'mawk' does the same, but needs a seed to be provided for its rand() function. In this example we use $(date) + randpm=$(mawk -v min=1 -v max=500 -v nNum=10 -v seed="$(date +%Y%M%d%H%M%S)" 'BEGIN { srand(seed); for (i = 0; i < nNum; ++i) {print int(min + rand() * (max - min)} }') + +*Yes, I\'m not an `awk` expert, so please correct it, rather than +complaining about possible stupid code!* + + # Well, seeing how this //is// BASH-hackers.org I kinda missed the bash way of doing the above ;-) + # print a number between 0 and 500 :-) + printf $(( 500 * RANDOM / 32767 )) + + # Or print 30 random numbers between 0 and 10 ;) + X=0; while (( X++ < 30 )); do echo $(( 10 * RANDOM / 32767 )); done + +#### SECONDS + +[SECONDS](/syntax/shellvars#SECONDS) is KSH/ZSH/Bash specific. Avoid it. +Find another method. + +### Check for a command in PATH + +The [PATH](/syntax/shellvars#PATH) variable is a colon-delimited list of +directory names, so it\'s basically possible to run a loop and check +every `PATH` component for the command you\'re looking for and for +executability. + +However, this method doesn\'t look nice. There are other ways of doing +this, using commands that are *not directly* related to this task. + +#### hash + +The `hash` command is used to make the shell store the full pathname of +a command in a lookup-table (to avoid re-scanning the `PATH` on every +command execution attempt). Since it has to do a `PATH` search, it can +be used for this check. + +For example, to check if the command `ls` is available in a location +accessible by `PATH`: + + if hash ls >/dev/null 2>&1; then + echo "ls is available" + fi + +Somewhat of a mass-check: + + for name in ls grep sed awk; do + if ! hash "$name" >/dev/null 2>&1; then + echo "FAIL: Missing command '$name'" + exit 1 + fi + done + +Here (bash 3), `hash` also respects builtin commands. I don\'t know if +this works everywhere, but it seems logical. + +#### command + +The `command` command is used to explicitly call an external command, +rather than a builtin with the same name. For exactly this reason, it +has to do a `PATH` search, and can be used for this check. + +For example, to check if the command `sed` is available in a location +accessible by `PATH`: + + if command -v sed >/dev/null 2>&1; then + echo "sed is available" + fi + +[^1]: \"portable\" doesn\'t necessarily mean it\'s POSIX, it can also + mean it\'s \"widely used and accepted\", and thus maybe more + portable than POSIX(r diff --git a/docs/scripting/obsolete.md b/docs/scripting/obsolete.md new file mode 100644 index 0000000..344f317 --- /dev/null +++ b/docs/scripting/obsolete.md @@ -0,0 +1,80 @@ +# Obsolete and deprecated syntax + +![](keywords>bash shell scripting obsolete deprecated outdated) + +This (incomplete) page describes some syntax and commands considered +obsolete by some measure. A thorough discussion of the rationale is +beyond the scope of this page. See the [portability +page](/scripting/nonportable) for a discussion on portability issues. + +This first table lists syntax that is tolerated by Bash but has few if +any legitimate uses. These features exist mostly for Bourne, csh, or +some other backward compatibility with obsolete shells, or were +Bash-specific features considered failed experiments and deprecated or +replaced with a better alternative. These should be irrelevant to most +everyone except maybe code golfers. New scripts should never use them. +None of the items on this list are specified by the most current version +of POSIX, and some may be incompatible with POSIX. + + Syntax Replacement Description + ---------------------------------- --------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + `&>FILE` and `>&FILE` `>FILE 2>&1` This redirection syntax is short for `>FILE 2>&1` and originates in the C Shell. The latter form is especially uncommon and should never be used, and the explicit form using separate redirections is preferred over both. These shortcuts contribute to confusion about the copy descriptor because the syntax is unclear. They also introduce parsing ambiguity, and conflict with POSIX. Shells without this feature treat `cmd1 &>file cmd2` as: \"background `cmd1` and then execute `cmd2` with its stdout redirected to `file`\", which is the correct interpretation of this expression. See: [redirection](/syntax/redirection) \`\$ { bash; dash \/dev/null&\>/dev/fd/2 echo bar\' foo echo bar bar\` + `$[EXPRESSION]` `$((EXPRESSION))` This undocumented syntax is completely replaced by the POSIX-conforming arithmetic expansion `$((EXPRESSION))`. It is unimplemented almost everywhere except Bash and Zsh. See [arithmetic expansion](/syntax/expansion/arith). [Some discussion](http://lists.gnu.org/archive/html/bug-bash/2012-04/msg00034.html). + `COMMAND\ |&\ COMMAND` `COMMAND 2>&1 | COMMAND` This is an alternate pipeline operator derived from Zsh. Officially, it is not considered deprecated by Bash, but I highly discourage it. It conflicts with the list operator used for [coprocess](/syntax/keywords/coproc) creation in most Korn shells. It also has confusing behavior. The stdout is redirected first like an ordinary pipe, while the stderr is actually redirected last \-- after other redirects preceding the pipe operator. Overall, it\'s pointless syntax bloat. Use an explicit redirect instead. + `function\ NAME()\ COMPOUND-CMD` `NAME()\ COMPOUND-CMD` or `function\ NAME\ {\ CMDS;\ }` This is an amalgamation between the Korn and POSIX style function definitions - using both the `function` keyword and parentheses. It has no useful purpose and no historical basis or reason to exist. It is not specified by POSIX. It is accepted by Bash, mksh, zsh, and perhaps some other Korn shells, where it is treated as identical to the POSIX-style function. It is not accepted by AT&T ksh. It should never be used. See the next table for the `function` keyword. Bash doesn\'t have this feature documented as expressly deprecated. + `for x; { ...;}` `do`, `done`, `in`, `esac`, etc. This undocumented syntax replaces the `do` and `done` reserved words with braces. Many Korn shells support various permutations on this syntax for certain compound commands like `for`, `case`, and `while`. Which ones and certain details like whether a newline or semicolon are required vary. Only `for` works in Bash. Needless to say, don\'t use it. + +This table lists syntax that is specified by POSIX (unless otherwise +specified below), but has been superseded by superior alternatives +(either in POSIX, Bash, or both), or is highly discouraged for other +reasons such as encouraging bad practices or dangerous code. Those that +are specified by POSIX may be badly designed and unchangeable for +historical reasons. + + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Syntax Replacement Description + ----------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Unquoted expansions, [wordsplit](/syntax/expansion/wordsplit), and [globs](/syntax/expansion/globs) [Proper quoting](http://mywiki.wooledge.org/Quotes), Ksh/Bash-style [arrays](/syntax/arrays), The \"\$@\" expansion, [read](/commands/builtin/read) *Quoting errors* are a broad category of common mistakes brought about by a few unintuitive features carried over from the Bourne shell due to complaints of broken scripts and changes in previously documented behavior. Most of the important expansions are performed at the same time from left to right. However, a few expansions, most notably word-splitting and globbing, and in shells other than Bash, [brace expansion](/syntax/expansion/brace), are performed **on the results of previous expansions, by default, unless they are quoted.** This means that the act of expanding an unquoted variable in an ordinary argument context, depending on the value of the variable, can yield different results depending on possibly uncontrolled side-effects like the value of `IFS`, and the names of files in the current working directory. You can\'t get globbing without word-splitting, or vice versa (without `set -f`). [You can\'t store a command or character-delimited list in a variable and safely evaluate it with unquoted expansion](http://mywiki.wooledge.org/BashFAQ/050). If possible, always choose a shell that supports Korn shell arrays such as Bash. They are a vital but non-standard feature for writing clean, safe scripts. Well-written scripts don\'t use word-splitting. A few exceptions are listed on the [word splitting page](/syntax/expansion/wordsplit). A significant proportion of the issues on the famous [Pitfalls list](http://mywiki.wooledge.org/BashPitfalls) fall under this category. See also: *[Don\'t read lines with for!](http://mywiki.wooledge.org/DontReadLinesWithFor)* + + `` `COMMANDS` `` `$(COMMANDS)` This is the older Bourne-compatible form of the [command substitution](/syntax/expansion/cmdsubst). Both the `` `COMMANDS` `` and `$(COMMANDS)` syntaxes are specified by POSIX, but the latter is [greatly]{.underline} preferred, though the former is unfortunately still very prevalent in scripts. New-style command substitutions are widely implemented by every modern shell (and then some). The only reason for using backticks is for compatibility with a real Bourne shell (like Heirloom). Backtick command substitutions require special escaping when nested, and examples found in the wild are improperly quoted more often than not. See: *[Why is \$(\...) preferred over \`\...\` (backticks)?](http://mywiki.wooledge.org/BashFAQ/082)*. + + `[\ EXPRESSION\ ]`\\ and\\ `test\ EXPRESSION` `[[\ EXPRESSION\ ]]` `test` and `[` are the Bourne/POSIX commands for evaluating test expressions (they are almost identical, and `[` is somewhat more common). The expressions consist of regular arguments, unlike the Ksh/Bash `[[` command. While the issue is analogous to `let` vs `((`, the advantages of `[[` vs `[` are even more important because the arguments/expansions aren\'t just concatenated into one expression. With the classic `[` command, the number of arguments is significant. If at all possible, use the [conditional expression](/syntax/ccmd/conditional_expression) (\"new test command\") `[[ EXPRESSION ]]`. Unless there is a need for POSIX compatibility, there are only a few reasons to use `[`. `[[` is one of the most portable and consistent non-POSIX ksh extensions available. See: [conditional_expression](/syntax/ccmd/conditional_expression) and *[What is the difference between test, \[ and \[\[ ?](http://mywiki.wooledge.org/BashFAQ/031)* + + `set -e`, `set -o errexit`\ proper control flow and error handling `set -e` causes untested non-zero exit statuses to be fatal. It is a debugging feature intended for use only during development and should not be used in production code, especially init scripts and other high-availability scripts. Do not be tempted to think of this as \"error handling\"; it\'s not, it\'s just a way to find the place you\'ve *forgotten* to put error handling.\ + and the `ERR` trap Think of it as akin to `use strict` in Perl or `throws` in C++: tough love that makes you write better code. Many guides recommend avoiding it entirely because of the apparently-complex rules for when non-zero statuses cause the script to abort. Conversely, large software projects with experienced coders may recommend or even mandate its use.\ + Because it provides no notification of the location of the error, it\'s more useful combined with `set -x` or the `DEBUG` trap and other Bash debug features, and both flags are normally better set on the command line rather than within the script itself.\ + Most of this also applies to the `ERR` trap, though I\'ve seen it used in a few places in shells that lack `pipefail` or `PIPESTATUS`. The `ERR` trap is not POSIX, but `set -e` is. `failglob` is another Bash feature that falls into this category (mainly useful for debugging).\ + **The `set -e` feature generates more questions and false bug reports on the Bash mailing list than all other features combined!** Please do not rely on `set -e` for logic in scripts. If you still refuse to take this advice, make sure you understand **exactly** how it works. See: *[Why doesn\'t set -e (or set -o errexit, or trap ERR) do what I expected?](http://mywiki.wooledge.org/BashFAQ/105)* and + + `set -u` or `set -o nounset` Proper control flow and error handling `set -u` causes attempts to expand unset variables or parameters as fatal errors. Like `set -e`, it bypasses control flow and exits immediately from the current shell environment. Like non-zero statuses, unset variables are a normal part of most non-trivial shell scripts. Living with `set -u` requires hacks like `${1+"$1"}` for each expansion that might possibly be unset. Only very current shells guarantee that expanding `@` or `*` won\'t trigger an error when no parameters are set (, ). Apparently some find it useful for debugging. See *[How do I determine whether a variable is already defined? Or a function?](http://mywiki.wooledge.org/BashFAQ/083)* for how to properly test for defined variables. Don\'t use `set -u`. + + `${var?msg}` or `${var:?msg}` Proper control flow and error handling Like `set -u`, this expansion causes a fatal error which immediately exits the current shell environment if the given parameter is unset or is null. It prints the error message given, to the right of the operator. If a value is expected and you\'d like to create an assertion or cause errors, it\'s better to test for undefined variables using one of [these techniques](http://mywiki.wooledge.org/BashFAQ/083) and handle the error manually, or call a `die` function. This expansion is defined by POSIX. It\'s better than `set -u`, because it\'s explicit, but not by much. It also allows you to accidentally construct hilariously deceptive error messages: `bash -c 'f() { definitely_not_printf "${printf:?"$1" - No such option}"; }; f -v' + bash: printf: -v - No such option` + ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +This table lists features that are used only if you have a specific +reason to prefer it over another alternative. These have some legitimate +uses if you know what you\'re doing, such as for those with specific +portability requirements, or in order to make use of some subtle +behavioral differences. These are frequently (mis)used for no reason. +Writing portable scripts that go outside of POSIX features requires +knowing how to account for many (often undocumented) differences across +many shells. If you do happen to know what you\'re doing, don\'t be too +surprised if you run across someone telling you not to use these. + + Syntax Replacement Description + ------------------------------- --------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + `function\ NAME\ {\ CMDS;\ }` `NAME()\ COMPOUND-CMD` This is the ksh form of function definition created to extend the Bourne and POSIX form with modified behaviors and additional features like local variables. The idea was for new-style functions to be analogous to regular builtins with their own environment and scope, while POSIX-style functions are more like special builtins. `function` is supported by almost every ksh-derived shell including Bash and Zsh, but isn\'t specified by POSIX. Bash treats all function styles the same, but this is unusual. `function` has some preferable characteristics in many ksh variants, making it more portable for scripts that use non-POSIX extensions by some measures. If you\'re going to use the `function` keyword, it implies that you\'re either targeting Ksh specifically, or that you have detailed knowledge of how to compensate for differences across shells. It should always be used consistently with `typeset`, but never used with `declare` or `local`. Also in ksh93, the braces are not a [command group](/syntax/ccmd/grouping_plain), but a required part of the syntax (unlike Bash and others). See [shell function definitions](/syntax/basicgrammar#shell_function_definitions) + `typeset` `declare`, `local`, `export`, `readonly` This is closely related to the above, and should often be used together. `typeset` exists primarily for `ksh` compatibility, but is marked as \"deprecated\" in Bash (though I don\'t entirely agree with this). This makes some sense, because future compatibility can\'t be guaranteed, and any compatibility at all, requires understanding the non-POSIX features of other shells and their differences. Using `declare` instead of `typeset` emphasizes your intention to be \"Bash-only\", and definitely breaks everywhere else (except possibly zsh if you\'re lucky). The issue is further complicated by Dash and the [Debian policy](http://www.debian.org/doc/debian-policy/ch-files.html#s-scripts) requirement for a `local` builtin, which is itself not entirely compatible with Bash and other shells. + \'\'let \'EXPR\' \'\' `((EXPR))` or `[\ $((EXPR))\ -ne\ 0 ]` `let` is the \"simple command\" variant of arithmetic evaluation command, which takes regular arguments. Both `let` and `((expr))` were present in ksh88, and everything that supports one should support the other. Neither are POSIX. The compound variant is preferable because it doesn\'t take regular arguments for [wordsplitting](/syntax/expansion/wordsplit) and [globbing](/syntax/expansion/globs), which makes it safer and clearer. It is also usually faster, especially in Bash, where compound commands are typically significantly faster. Some of the (few) reasons for using `let` are detailed on the [let](/commands/builtin/let) page. See [arithmetic evaluation compound command](/syntax/ccmd/arithmetic_eval) + `eval` Depends. Often code can be restructured to use better alternatives. `eval` is thrown in here for good measure, as sadly it is so often misused that any use of `eval` (even the rare clever one) is immediately dismissed as wrong by experts, and among the most immediate solutions abused by beginners. In reality, there are correct ways to use `eval`, and even cases in which it\'s necessary, even in sophisticated shells like Bash and Ksh. `eval` is unusual in that it is less frequently appropriate in more feature-rich shells than in more minimal shells like Dash, where it is used to compensate for more limitations. If you find yourself needing `eval` too frequently, it might be a sign that you\'re either better off using a different language entirely, or trying to borrow an idiom from some other paradigm that isn\'t well suited to the shell language. By the same token, there are some cases in which working too hard to avoid `eval` ends up adding a lot of complexity and sacrificing all portability. Don\'t substitute a clever `eval` for something that\'s a bit \"too clever\", just to avoid the `eval`, yet, take reasonable measures to avoid it where it is sensible to do so. See: [eval](/commands/builtin/eval) and [Eval command and security issues](http://mywiki.wooledge.org/BashFAQ/048). + +## See also + +- [Non-portable syntax and command uses](/scripting/nonportable) +- [bashchanges](/scripting/bashchanges) +- [Greg\'s BashFAQ 061: List of essential features added (with the + Bash version tag)](BashFAQ>061) +- [Bash \<-\> POSIX Portability guide with a focus on + Dash](http://mywiki.wooledge.org/Bashism) +- diff --git a/docs/scripting/posparams.md b/docs/scripting/posparams.md new file mode 100644 index 0000000..8009d8c --- /dev/null +++ b/docs/scripting/posparams.md @@ -0,0 +1,397 @@ +# Handling positional parameters + +![](keywords>bash shell scripting arguments positional parameters options) + +## Intro + +The day will come when you want to give arguments to your scripts. These +arguments are known as **positional parameters**. Some relevant special +parameters are described below: + + Parameter(s) Description + ------------------ ------------------------------------------------------------------------------------------------------------------------------------ + `$0` the first positional parameter, equivalent to `argv[0]` in C, see [the first argument](/scripting/posparams#the_first_argument) + `$FUNCNAME` the function name ([**attention**]{.underline}: inside a function, `$0` is still the `$0` of the shell, **not** the function name) + `$1 ... $9` the argument list elements from 1 to 9 + `${10} ... ${N}` the argument list elements beyond 9 (note the [parameter expansion](/syntax/pe) syntax!) + `$*` all positional parameters except `$0`, see [mass usage](/scripting/posparams#mass_usage) + `$@` all positional parameters except `$0`, see [mass usage](/scripting/posparams#mass_usage) + `$#` the number of arguments, not counting `$0` + +These positional parameters reflect exactly what was given to the script +when it was called. + +Option-switch parsing (e.g. `-h` for displaying help) is not performed +at this point. + +See also [the dictionary entry for +\"parameter\"](/dict/terms/parameter). + +## The first argument + +The very first argument you can access is referenced as `$0`. It is +usually set to the script\'s name exactly as called, and it\'s set on +shell initialization: + +[Testscript]{.underline} - it just echos `$0`: + + #!/bin/bash + echo "$0" + +You see, `$0` is always set to the name the script is called with (`>` +is the prompt\...): + + > ./testscript + ./testscript + + > /usr/bin/testscript + /usr/bin/testscript + +However, this isn\'t true for login shells: + + > echo "$0" + -bash + +In other terms, `$0` is not a positional parameter, it\'s a special +parameter independent from the positional parameter list. It can be set +to anything. In the **ideal** case it\'s the pathname of the script, but +since this gets set on invocation, the invoking program can easily +influence it (the `login` program does that for login shells, by +prefixing a dash, for example). + +Inside a function, `$0` still behaves as described above. To get the +function name, use `$FUNCNAME`. + +## Shifting + +The builtin command `shift` is used to change the positional parameter +values: + +- `$1` will be discarded +- `$2` will become `$1` +- `$3` will become `$2` +- \... +- in general: `$N` will become `$N-1` + +The command can take a number as argument: Number of positions to shift. +e.g. `shift 4` shifts `$5` to `$1`. + +## Using them + +Enough theory, you want to access your script-arguments. Well, here we +go. + +### One by one + +One way is to access specific parameters: + + #!/bin/bash + echo "Total number of arguments: $#" + echo "Argument 1: $1" + echo "Argument 2: $2" + echo "Argument 3: $3" + echo "Argument 4: $4" + echo "Argument 5: $5" + +While useful in another situation, this way is lacks flexibility. The +maximum number of arguments is a fixedvalue - which is a bad idea if you +write a script that takes many filenames as arguments. + +=\> forget that one + +### Loops + +There are several ways to loop through the positional parameters. + +------------------------------------------------------------------------ + +You can code a [C-style for-loop](/syntax/ccmd/c_for) using `$#` as the +end value. On every iteration, the `shift`-command is used to shift the +argument list: + + numargs=$# + for ((i=1 ; i <= numargs ; i++)) + do + echo "$1" + shift + done + +Not very stylish, but usable. The `numargs` variable is used to store +the initial value of `$#` because the shift command will change it as +the script runs. + +------------------------------------------------------------------------ + +Another way to iterate one argument at a time is the `for` loop without +a given wordlist. The loop uses the positional parameters as a wordlist: + + for arg + do + echo "$arg" + done + +[Advantage:]{.underline} The positional parameters will be preserved + +------------------------------------------------------------------------ + +The next method is similar to the first example (the `for` loop), but it +doesn\'t test for reaching `$#`. It shifts and checks if `$1` still +expands to something, using the [test command](/commands/classictest): + + while [ "$1" ] + do + echo "$1" + shift + done + +Looks nice, but has the disadvantage of stopping when `$1` is empty +(null-string). Let\'s modify it to run as long as `$1` is defined (but +may be null), using [parameter expansion for an alternate +value](/syntax/pe#use_an_alternate_value): + + while [ "${1+defined}" ]; do + echo "$1" + shift + done + +### Getopts + +There is a [small tutorial dedicated to +`getopts`](/howto/getopts_tutorial) (*under construction*). + +## Mass usage + +### All Positional Parameters + +Sometimes it\'s necessary to just \"relay\" or \"pass\" given arguments +to another program. It\'s very inefficient to do that in one of these +loops, as you will destroy integrity, most likely (spaces!). + +The shell developers created `$*` and `$@` for this purpose. + +As overview: + + Syntax Effective result + -------- ----------------------------- + `$*` `$1 $2 $3 ... ${N}` + `$@` `$1 $2 $3 ... ${N}` + `"$*"` `"$1c$2c$3c...c${N}"` + `"$@"` `"$1" "$2" "$3" ... "${N}"` + +Without being quoted (double quotes), both have the same effect: All +positional parameters from `$1` to the last one used are expanded +without any special handling. + +When the `$*` special parameter is double quoted, it expands to the +equivalent of: `"$1c$2c$3c$4c........$N"`, where \'c\' is the first +character of `IFS`. + +But when the `$@` special parameter is used inside double quotes, it +expands to the equivanent of\... + +`"$1" "$2" "$3" "$4" ..... "$N"` + +\...which **reflects all positional parameters as they were set +initially** and passed to the script or function. If you want to re-use +your positional parameters to **call another program** (for example in a +wrapper-script), then this is the choice for you, use double quoted +`"$@"`. + +Well, let\'s just say: **You almost always want a quoted `"$@"`!** + +### Range Of Positional Parameters + +Another way to mass expand the positional parameters is similar to what +is possible for a range of characters using [substring +expansion](/syntax/pe#substring_expansion) on normal parameters and the +mass expansion range of [arrays](/syntax/arrays). + +`${@:START:COUNT}` + +`${*:START:COUNT}` + +`"${@:START:COUNT}"` + +`"${*:START:COUNT}"` + +The rules for using `@` or `*` and quoting are the same as above. This +will expand `COUNT` number of positional parameters beginning at +`START`. `COUNT` can be omitted (`${@:START}`), in which case, all +positional parameters beginning at `START` are expanded. + +If `START` is negative, the positional parameters are numbered in +reverse starting with the last one. + +`COUNT` may not be negative, i.e. the element count may not be +decremented. + +[**Example:**]{.underline} START at the last positional parameter: + + echo "${@: -1}" + +[**Attention**]{.underline}: As of Bash 4, a `START` of `0` includes the +special parameter `$0`, i.e. the shell name or whatever \$0 is set to, +when the positional parameters are in use. A `START` of `1` begins at +`$1`. In Bash 3 and older, both `0` and `1` began at `$1`. + +## Setting Positional Parameters + +Setting positional parameters with command line arguments, is not the +only way to set them. The [builtin command, set](/commands/builtin/set) +may be used to \"artificially\" change the positional parameters from +inside the script or function: + + set "This is" my new "set of" positional parameters + + # RESULTS IN + # $1: This is + # $2: my + # $3: new + # $4: set of + # $5: positional + # $6: parameters + +It\'s wise to signal \"end of options\" when setting positional +parameters this way. If not, the dashes might be interpreted as an +option switch by `set` itself: + + # both ways work, but behave differently. See the article about the set command! + set -- ... + set - ... + +Alternately this will also preserve any verbose (-v) or tracing (-x) +flags, which may otherwise be reset by `set` + + set -$- ... + +FIXME continue + +## Production examples + +### Using a while loop + +To make your program accept options as standard command syntax: + +`COMMAND [options] ` \# Like \'cat -A file.txt\' + +See simple option parsing code below. It\'s not that flexible. It +doesn\'t auto-interpret combined options (-fu USER) but it works and is +a good rudimentary way to parse your arguments. + + #!/bin/sh + # Keeping options in alphabetical order makes it easy to add more. + + while : + do + case "$1" in + -f | --file) + file="$2" # You may want to check validity of $2 + shift 2 + ;; + -h | --help) + display_help # Call your function + # no shifting needed here, we're done. + exit 0 + ;; + -u | --user) + username="$2" # You may want to check validity of $2 + shift 2 + ;; + -v | --verbose) + # It's better to assign a string, than a number like "verbose=1" + # because if you're debugging the script with "bash -x" code like this: + # + # if [ "$verbose" ] ... + # + # You will see: + # + # if [ "verbose" ] ... + # + # Instead of cryptic + # + # if [ "1" ] ... + # + verbose="verbose" + shift + ;; + --) # End of all options + shift + break; + -*) + echo "Error: Unknown option: $1" >&2 + exit 1 + ;; + *) # No more options + break + ;; + esac + done + + # End of file + +### Filter unwanted options with a wrapper script + +This simple wrapper enables filtering unwanted options (here: `-a` and +`--all` for `ls`) out of the command line. It reads the positional +parameters and builds a filtered array consisting of them, then calls +`ls` with the new option set. It also respects the `--` as \"end of +options\" for `ls` and doesn\'t change anything after it: + + #!/bin/bash + + # simple ls(1) wrapper that doesn't allow the -a option + + options=() # the buffer array for the parameters + eoo=0 # end of options reached + + while [[ $1 ]] + do + if ! ((eoo)); then + case "$1" in + -a) + shift + ;; + --all) + shift + ;; + -[^-]*a*|-a?*) + options+=("${1//a}") + shift + ;; + --) + eoo=1 + options+=("$1") + shift + ;; + *) + options+=("$1") + shift + ;; + esac + else + options+=("$1") + + # Another (worse) way of doing the same thing: + # options=("${options[@]}" "$1") + shift + fi + done + + /bin/ls "${options[@]}" + +### Using getopts + +There is a [small tutorial dedicated to +`getopts`](/howto/getopts_tutorial) (*under construction*). + +## See also + +- Internal: [getopts_tutorial](/howto/getopts_tutorial) +- Internal: [while_loop](/syntax/ccmd/while_loop) +- Internal: [c_for](/syntax/ccmd/c_for) +- Internal: [arrays](/syntax/arrays) (for equivalent syntax for + mass-expansion) +- Internal: [Substring expansion on a + parameter](/syntax/pe#substring_expansion) (for equivalent syntax + for mass-expansion) +- Dictionary, internal: [parameter](/dict/terms/parameter) diff --git a/docs/scripting/processtree.md b/docs/scripting/processtree.md new file mode 100644 index 0000000..dbcc6ee --- /dev/null +++ b/docs/scripting/processtree.md @@ -0,0 +1,179 @@ +# Bash and the process tree + +![](keywords>bash shell scripting processes pipes variables environment) + +## The process tree + +The processes in UNIX(r) are - unlike other systems - **organized as a +tree**. Every process has a parent process that started, or is +responsible, for it. Every process has its own **context memory** (Not +the memory where the process stores its data, rather, the memory where +data is stored that doesn\'t directly belong to the process, but is +needed to run the process) i.e. [**The environment**]{.underline}. + +Every process has its **own** environment space. + +The environment stores, among other things, data that\'s useful to us, +the **environment variables**. These are strings in common `NAME=VALUE` +form, but they are not related to shell variables. A variable named +`LANG`, for example, is used by every program that looks it up in its +environment to determinate the current locale. + +**[Attention:]{.underline}** A variable that is set, like with +`MYVAR=Hello`, is **not** automatically part of the environment. You +need to put it into the environment with the bash builtin command +`export`: + + export MYVAR + +Common system variables like [PATH](/syntax/shellvars#PATH) or +[HOME](/syntax/shellvars#HOME) are usually part of the environment (as +set by login scripts or programs). + +## Executing programs + +All the diagrams of the process tree use names like \"`xterm`\" or +\"`bash`\", but that\'s just to make it easier to understand what\'s +going on, it doesn\'t mean those processes are actually executed. + +Let\'s take a short look at what happens when you \"execute a program\" +from the Bash prompt, a program like \"ls\": + + $ ls + +Bash will now perform **two steps**: + +- It will make a copy of itself +- The copy will replace itself with the \"ls\" program + +The copy of Bash will inherit the environment from the \"main Bash\" +process: All environment variables will also be copied to the new +process. This step is called **forking**. + +For a short moment, you have a process tree that might look like +this\... + + xterm ----- bash ----- bash(copy) + +\...and after the \"second Bash\" (the copy) replaces itself with the +`ls` program (the copy execs it), it might look like + + xterm ----- bash ----- ls + +If everything was okay, the two steps resulted in one program being run. +The copy of the environment from the first step (forking) becomes the +environment for the final running program (in this case, `ls`). + +[**What is so important about it?**]{.underline} In our example, what +the program `ls` does inside its own environment, it can\'t affect the +environment of its parent process (in this case, `bash`). The +environment was copied when ls was executed. Nothing is \"copied back\" +to the parent environment when `ls` terminates. + +## Bash playing with pipes + +Pipes are a very powerful tool. You can connect the output of one +process to the input of another process. We won\'t delve into piping at +this point, we just want to see how it looks in the process tree. Again, +we execute some commands, this time, we\'ll run `ls` and `grep`: + + $ ls | grep myfile + +It results in a tree like this: + + +-- ls + xterm ----- bash --| + +-- grep + +Note once again, `ls` can\'t influence the `grep` environment, `grep` +can\'t influence the `ls` environment, and neither `grep` nor `ls` can +influence the `bash` environment. + +[**How is that related to shell programming?!?**]{.underline} + +Well, imagine some Bash code that reads data from a pipe. For example, +the internal command `read`, which reads data from *stdin* and puts it +into a variable. We run it in a loop here to count input lines: + + counter=0 + + cat /etc/passwd | while read; do ((counter++)); done + echo "Lines: $counter" + +What? It\'s 0? Yes! The number of lines might not be 0, but the variable +`$counter` still is 0. Why? Remember the diagram from above? Rewriting +it a bit, we have: + + +-- cat /etc/passwd + xterm ----- bash --| + +-- bash (while read; do ((counter++)); done) + +See the relationship? The forked Bash process will count the lines like +a charm. It will also set the variable `counter` as directed. But if +everything ends, this extra process will be terminated - **your +\"counter\" variable is gone.** You see a 0 because in the main shell it +was 0, and wasn\'t changed by the child process! + +[**So, how do we count the lines?**]{.underline} Easy: **Avoid the +subshell.** The details don\'t matter, the important thing is the shell +that sets the counter must be the \"main shell\". For example: + + counter=0 + + while read; do ((counter++)); done output.txt + +### Command substitution + +With [command substitution](/syntax/expansion/cmdsubst) you re-use the +output of another command as text in your command line, for example to +set a variable. The other command is run in a subshell: + + number_of_users=$(cat /etc/passwd | wc -l) + +Note that, in this example, a second subshell was created by using a +pipe in the command substitution: + + +-- cat /etc/passwd + xterm ----- bash ----- bash (cmd. subst.) --| + +-- wc -l + +FIXME to be continued diff --git a/docs/scripting/style.md b/docs/scripting/style.md new file mode 100644 index 0000000..d9bbafd --- /dev/null +++ b/docs/scripting/style.md @@ -0,0 +1,409 @@ +# Scripting with style + +FIXME continue + +These are some coding guidelines that helped me to read and understand +my own code over the years. They also will help to produce code that +will be a bit more robust than \"if something breaks, I know how to fix +it\". + +This is not a bible, of course. But I have seen so much ugly and +terrible code (not only in shell) during all the years, that I\'m 100% +convinced there needs to be *some* code layout and style. No matter +which one you use, use it throughout your code (at least don\'t change +it within the same shellscript file); don\'t change your code layout +with your mood. + +Some good code layout helps you to read your own code after a while. And +of course it helps others to read the code. + +## Indentation guidelines + +Indentation is nothing that technically influences a script, it\'s only +for us humans. + +I\'m used to seeing/using indentation of *two space characters* (though +many may prefer 4 spaces, see below in the discussion section): + +- it\'s easy and fast to type +- it\'s not a hard-tab that\'s displayed differently in different + environments +- it\'s wide enough to give a visual break and small enough to not + waste too much space on the line + +Speaking of hard-tabs: Avoid them if possible. They only make trouble. I +can imagine one case where they\'re useful: Indenting +[here-documents](/syntax/redirection#here_documents). + +### Breaking up lines + +Whenever you need to break lines of long code, you should follow one of +these two rules: + +[**Indention using command width:**]{.underline} + + activate some_very_long_option \ + some_other_option + +[**Indention using two spaces:**]{.underline} + + activate some_very_long_option \ + some_other_option + +Personally, with some exceptions, I prefer the first form because it +supports the visual impression of \"these belong together\". + +### Breaking compound commands + +[Compound commands](/syntax/ccmd/intro) form the structures that make a +shell script different from a stupid enumeration of commands. Usually +they contain a kind of \"head\" and a \"body\" that contains command +lists. This type of compound command is relatively easy to indent. + +I\'m used to (not all points apply to all compound commands, just pick +the basic idea): + +- put the introducing keyword and the initial command list or + parameters on one line (\"head\") +- put the \"body-introducing\" keyword on the same line +- the command list of the \"body\" on separate lines, indented by two + spaces +- put the closing keyword on a separated line, indented like the + initial introducing keyword + +What?! Well, here again: + +##### Symbolic + + HEAD_KEYWORD parameters; BODY_BEGIN + BODY_COMMANDS + BODY_END + +##### if/then/elif/else + +This construct is a bit special, because it has keywords (`elif`, +`else`) \"in the middle\". The visually appealing way is to indent them +like this: + + if ...; then + ... + elif ...; then + ... + else + ... + fi + +##### for + + for f in /etc/*; do + ... + done + +##### while/until + + while [[ $answer != [YyNn] ]]; do + ... + done + +##### The case construct + +The `case` construct might need a bit more discussion here, since its +structure is a bit more complex. + +In general, every new \"layer\" gets a new indentation level: + + case $input in + hello) + echo "You said hello" + ;; + bye) + echo "You said bye" + if foo; then + bar + fi + ;; + *) + echo "You said something weird..." + ;; + esac + +Some notes: + +- if not 100% needed, the optional left parenthesis on the pattern is + not used +- the patterns (`hello)`) and the corresponding action terminator + (`;;`) are indented at the same level +- the action command lists are indented one more level (and continue + to have their own indentation, if needed) +- though optional, the very last action terminator is given + +## Syntax and coding guidelines + +### Cryptic constructs + +Cryptic constructs, we all know them, we all love them. If they are not +100% needed, avoid them, since nobody except you may be able to decipher +them. + +It\'s - just like in C - the middle ground between smart, efficient and +readable. + +If you need to use a cryptic construct, include a comment that explains +what your \"monster\" does. + +### Variable names + +Since all reserved variables are `UPPERCASE`, the safest way is to only +use `lowercase` variable names. This is true for reading user input, +loop counting variables, etc., \... (in the example: `file`) + +- prefer `lowercase` variables +- if you use `UPPERCASE` names, **do not use reserved variable names** + (see + [SUS](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08) + for an incomplete list) +- if you use `UPPERCASE` names, prepend the name with a unique prefix + (`MY_` in the example below) + +```{=html} + +``` + #!/bin/bash + + # the prefix 'MY_' + MY_LOG_DIRECTORY=/var/adm/ + + for file in "$MY_LOG_DIRECTORY"/*; do + echo "Found Logfile: $file" + done + +### Variable initialization + +As in C, it\'s always a good idea to initialize your variables, though, +the shell will initialize fresh variables itself (better: Unset +variables will generally behave like variables containing a null +string). + +It\'s no problem to pass an **environment variable** to the script. If +you blindly assume that all variables you use for the first time are +**empty**, anybody can **inject** content into a variable by passing it +via the environment. + +The solution is simple and effective: **Initialize them** + + my_input="" + my_array=() + my_number=0 + +If you do that for every variable you use, then you also have some +in-code documentation for them. + +### Parameter expansion + +Unless you are really sure what you\'re doing, **quote every parameter +expansion**. + +There are some cases where this isn\'t needed from a technical point of +view, e.g. + +- inside `[[ ... ]]` (other than the RHS of the `==`, `!=`, and `=~` + operators) +- the parameter (`WORD`) in `case $WORD in ....` +- variable assignment: `VAR=$WORD` + +But quoting these is never a mistake. If you quote every parameter +expansion, you\'ll be safe. + +If you need to parse a parameter as a list of words, you can\'t quote, +of course, e.g. + + list="one two three" + + # you MUST NOT quote $list here + for word in $list; do + ... + done + +### Function names + +Function names should be all `lowercase` and meaningful. The function +names should be human readable. A function named `f1` may be easy and +quick to write down, but for debugging and especially for other people, +it reveals nothing. Good names help document your code without using +extra comments. + +**do not use command names for your functions**. e.g. naming a script or +function `test`, will collide with the UNIX `test` command. + +Unless absolutely necessary, only use alphanumeric characters and the +underscore for function names. `/bin/ls` is a valid function name in +Bash, but is not a good idea. + +### Command substitution + +As noted in [the article about command +substitution](/syntax/expansion/cmdsubst), you should use the `$( ... )` +form. + +If portability is a concern, use the backquoted form `` ` ... ` ``. + +In any case, if other expansions and word splitting are not wanted, you +should quote the command substitution! + +### Eval + +Well, like Greg says: **\"If eval is the answer, surely you are asking +the wrong question.\"** + +Avoid it, unless absolutely neccesary: + +- `eval` can be your neckshot +- there are most likely other ways to achieve what you want +- if possible, re-think the way your script works, if it seems you + can\'t avoid `eval` with your current method +- if you really, really, have to use it, then take care, and be sure + about what you\'re doing + +## Basic structure + +The basic structure of a script simply reads: + + #!SHEBANG + + CONFIGURATION_VARIABLES + + FUNCTION_DEFINITIONS + + MAIN_CODE + +### The shebang + +If possible (I know it\'s not always possible!), use [a +shebang](/dict/terms/shebang). + +Be careful with `/bin/sh`: The argument that \"on Linux `/bin/sh` is +Bash\" **is a lie** (and technically irrelevant) + +The shebang serves two purposes for me: + +- it specifies the interpreter to be used when the script file is + called directly: If you code for Bash, specify `bash`! +- it documents the desired interpreter (so: use `bash` when you write + a Bash-script, use `sh` when you write a general Bourne/POSIX + script, \...) + +### Configuration variables + +I call variables that are meant to be changed by the user +\"configuration variables\" here. + +Make them easy to find (directly at the top of the script), give them +meaningful names and maybe a short comment. As noted above, use +`UPPERCASE` for them only when you\'re sure about what you\'re doing. +`lowercase` will be the safest. + +### Function definitions + +Unless there are reasons not to, all function definitions should be +declared before the main script code runs. This gives a far better +overview and ensures that all function names are known before they are +used. + +Since a function isn\'t parsed before it is executed, you usually don\'t +have to ensure they\'re in a specific order. + +The portable form of the function definition should be used, without the +`function` keyword (here using the [grouping compound +command](/syntax/ccmd/grouping_plain)): + + getargs() { + ... + } + +Speaking about the command grouping in function definitions using +`{ ...; }`: If you don\'t have a good reason to use another compound +command directly, you should always use this one. + +## Behaviour and robustness + +### Fail early + +**Fail early**, this sounds bad, but usually is good. Failing early +means to error out as early as possible when checks indicate an error or +unmet condition. Failing early means to error out **before** your script +begins its work in a potentially broken state. + +### Availability of commands + +If you use external commands that may not be present on the path, or not +installed, check for their availability, then tell the user they\'re +missing. + +Example: + + my_needed_commands="sed awk lsof who" + + missing_counter=0 + for needed_command in $my_needed_commands; do + if ! hash "$needed_command" >/dev/null 2>&1; then + printf "Command not found in PATH: %s\n" "$needed_command" >&2 + ((missing_counter++)) + fi + done + + if ((missing_counter > 0)); then + printf "Minimum %d commands are missing in PATH, aborting\n" "$missing_counter" >&2 + exit 1 + fi + +### Exit meaningfully + +The [exit code](/dict/terms/exit_status) is your only way to directly +communicate with the calling process without any special provisions. + +If your script exits, provide a meaningful exit code. That minimally +means: + +- `exit 0` (zero) if everything is okay +- `exit 1` - in general non-zero - if there was an error + +This, **and only this**, will enable the calling component to check the +operation status of your script. + +You know: **\"One of the main causes of the fall of the Roman Empire was +that, lacking zero, they had no way to indicate successful termination +of their C programs.\"** *\-- Robert Firth* + +## Misc + +### Output and appearance + +- if the script is interactive, if it works for you and if you think + this is a nice feature, you can try to [save the terminal content + and restore it](/snipplets/screen_saverestore) after execution +- output clean and understandable screen messages +- if applicable, you can use colors or specific prefixes to tag error + and warning messages + - make it easy for the user to identify those messages +- write normal output to `STDOUT`. write error, warning and diagnostic + messages to `STDERR` + - enables message filtering + - keeps the script from mixing output data with diagnostic, or + error messages + - if the script gives syntax help (`-?` or `-h` or `--help` + arguments), it should go to `STDOUT` +- if applicable, write error/diagnostic messages to a logfile + - avoids screen clutter + - messages are available for diagnostic use + +### Input + +- never blindly assume anything. If you want the user to input a + number, **check for numeric input, leading zeros**, etc. If you have + specific format or content needs, **always validate the input!** + +### Tooling + +- some of these guidelines, such as indentation, positioning of + \"body-introducing\" keywords, and portable function declarations, + can be enforced by [shfmt](https://github.com/mvdan/sh) diff --git a/docs/scripting/terminalcodes.md b/docs/scripting/terminalcodes.md new file mode 100644 index 0000000..0f80f10 --- /dev/null +++ b/docs/scripting/terminalcodes.md @@ -0,0 +1,354 @@ +# Terminal codes (ANSI/VT100) introduction + +![](keywords>bash shell scripting colors cursor control vt100 ansi) + +Terminal (control) codes are used to issue specific commands to your +terminal. This can be related to switching colors or positioning the +cursor, i.e. anything that can\'t be done by the application itself. + +## How it technically works + +A terminal control code is a special sequence of characters that is +printed (like any other text). If the terminal understands the code, it +won\'t display the character-sequence, but will perform some action. You +can print the codes with a simple `echo` command. + +[**Note:**]{.underline} I see codes referenced as \"Bash colors\" +sometimes (several \"Bash tutorials\" etc\...): That\'s a completely +incorrect definition. + +## The tput command + +Because there\'s a large number of different terminal control languages, +usually a system has an intermediate communication layer. The real codes +are looked up in a database **for the currently detected terminal type** +and you give standardized requests to an API or (from the shell) to a +command. + +One of these commands is `tput`. Tput accepts a set of acronyms called +*capability names* and any parameters, if appropriate, then looks up the +correct escape sequences for the detected terminal in the `terminfo` +database and prints the correct codes (the terminal hopefully +understands). + +## The codes + +In this list I\'ll focus on ANSI/VT100 control codes for the most common +actions - take it as quick reference. The documentation of your terminal +or the `terminfo` database is always the preferred source when something +is unclear! Also the `tput` acronyms are usually the ones dedicated for +ANSI escapes! + +I listed only the most relevant codes, of course, any ANSI terminal +understands many more! But let\'s keep the discussion centered on common +shell scripting ;-) + +If I couldn\'t find a matching ANSI escape, you\'ll see a :?: as the +code. Feel free to mail me or fix it. + +The ANSI codes always start with the ESC character. (ASCII 0x1B or octal +033) This isn\'t part of the list, but **you should avoid using the ANSI +codes directly - use the `tput` command!** + +All codes that can be used with `tput` can be found in terminfo(5). (on +OpenBSD at least) See [OpenBSD\'s +terminfo(5)](http://www.openbsd.org/cgi-bin/man.cgi?query=terminfo&apropos=0&sektion=5&manpath=OpenBSD+Current&arch=i386&format=html) +under the [Capabilities]{.underline} section. The *cap-name* is the code +to use with tput. A description of each code is also provided. + +### General useful ASCII codes + +The **Ctrl-Key** representation is simply associating the non-printable +characters from ASCII code 1 with the printable (letter) characters from +ASCII code 65 (\"A\"). ASCII code 1 would be `^A` (Ctrl-A), while ASCII +code 7 (BEL) would be `^G` (Ctrl-G). This is a common representation +(and input method) and historically comes from one of the VT series of +terminals. + + Name decimal octal hex C-escape Ctrl-Key Description + ------- --------- ------- ------ ---------- ---------- -------------------------------- + `BEL` 7 007 0x07 `\a` `^G` Terminal bell + `BS` 8 010 0x08 `\b` `^H` Backspace + `HT` 9 011 0x09 `\t` `^I` Horizontal TAB + `LF` 10 012 0x0A `\n` `^J` Linefeed (newline) + `VT` 11 013 0x0B `\v` `^K` Vertical TAB + `FF` 12 014 0x0C `\f` `^L` Formfeed (also: New page `NP`) + `CR` 13 015 0x0D `\r` `^M` Carriage return + `ESC` 27 033 0x1B `` `^[` Escape character + `DEL` 127 177 0x7F `` `` Delete character + +### Cursor handling + + ------------------------------------------------------------------------------------------------------------------------------- + ANSI terminfo equivalent Description + ----------------------------------------- --------------------- --------------------------------------------------------------- + `[ ; H`\ `cup ` Home-positioning to `X` and `Y` coordinates\ + `[ ; f` :!: it seems that ANSI uses 1-1 as home while `tput` uses 0-0 + + `[ H` `home` Move cursor to home position (0-0) + + `7` `sc` Save current cursor position + + `8` `rc` Restore saved cursor position + + :?: most likely a normal code like `\b` `cub1` move left one space (backspace) + + VT100 `[ ? 25 l` `civis` make cursor invisible + + VT100 `[ ? 25 h` `cvvis` make cursor visible + ------------------------------------------------------------------------------------------------------------------------------- + +### Erasing text + + ------------------------------------------------------------------------------------------------ + ANSI terminfo equivalent Description + --------- --------------------- ---------------------------------------------------------------- + `[ K`\ `el` **Clear line** from current cursor position **to end** of line + `[ 0 K` + + `[ 1 K` `el1` **Clear line from beginning** to current cursor position + + `[ 2 K` `el2`:?: **Clear whole line** (cursor position unchanged) + ------------------------------------------------------------------------------------------------ + +### General text attributes + + ANSI terminfo equivalent Description + --------- ----------------------------- ------------------------------------------------ + `[ 0 m` `sgr0` Reset all attributes + `[ 1 m` `bold` Set \"bright\" attribute + `[ 2 m` `dim` Set \"dim\" attribute + `[ 3 m` `smso` Set \"standout\" attribute + `[ 4 m` set `smul` unset `rmul` :?: Set \"underscore\" (underlined text) attribute + `[ 5 m` `blink` Set \"blink\" attribute + `[ 7 m` `rev` Set \"reverse\" attribute + `[ 8 m` `invis` Set \"hidden\" attribute + +### Foreground coloring + + ANSI terminfo equivalent Description + ----------- --------------------- ---------------------------------------------- + `[ 3 0 m` `setaf 0` Set **foreground** to color #0 - **black** + `[ 3 1 m` `setaf 1` Set **foreground** to color #1 - **red** + `[ 3 2 m` `setaf 2` Set **foreground** to color #2 - **green** + `[ 3 3 m` `setaf 3` Set **foreground** to color #3 - **yellow** + `[ 3 4 m` `setaf 4` Set **foreground** to color #4 - **blue** + `[ 3 5 m` `setaf 5` Set **foreground** to color #5 - **magenta** + `[ 3 6 m` `setaf 6` Set **foreground** to color #6 - **cyan** + `[ 3 7 m` `setaf 7` Set **foreground** to color #7 - **white** + `[ 3 9 m` `setaf 9` Set **default** color as foreground color + +### Background coloring + + ANSI terminfo equivalent Description + ----------- --------------------- ---------------------------------------------- + `[ 4 0 m` `setab 0` Set **background** to color #0 - **black** + `[ 4 1 m` `setab 1` Set **background** to color #1 - **red** + `[ 4 2 m` `setab 2` Set **background** to color #2 - **green** + `[ 4 3 m` `setab 3` Set **background** to color #3 - **yellow** + `[ 4 4 m` `setab 4` Set **background** to color #4 - **blue** + `[ 4 5 m` `setab 5` Set **background** to color #5 - **magenta** + `[ 4 6 m` `setab 6` Set **background** to color #6 - **cyan** + `[ 4 7 m` `setab 7` Set **background** to color #7 - **white** + `[ 4 9 m` `setab 9` Set **default** color as background color + +### Misc codes + +#### Save/restore screen + +Used capabilities: `smcup`, `rmcup` + +You\'ve undoubtedly already encountered programs that restore the +terminal contents after they do their work (like `vim`). This can be +done by the following commands: + + # save, clear screen + tput smcup + clear + + # example "application" follows... + read -n1 -p "Press any key to continue..." + # example "application" ends here + + # restore + tput rmcup + +These features require that certain capabilities exist in your +termcap/terminfo. While `xterm` and most of its clones (`rxvt`, `urxvt`, +etc) will support the instructions, your operating system may not +include references to them in its default xterm profile. (FreeBSD, in +particular, falls into this category.) If \`tput smcup\` appears to do +nothing for you, and you don\'t want to modify your system +termcap/terminfo data, and you KNOW that you are using a compatible +xterm application, the following may work for you: + + echo -e '\033[?47h' # save screen + echo -e '\033[?47l' # restore screen + +Certain software uses these codes (via their termcap capabilities) as +well. You may have seen the screen save/restore in `less`, `vim`, `top`, +`screen` and others. Some of these applications may also provide +configuration options to \*disable\* this behaviour. For example, `less` +has a `-X` option for this, which can also be set in an environment +variable: + + export LESS=X + less /path/to/file + +Similarly, `vim` can be configured not to \"restore\" the screen by +adding the following to your `~/.vimrc`: + + set t_ti= t_te= + +#### Additional colors + +Some terminal emulators support additional colors. The most common +extension used by xterm-compatible terminals supports 256 colors. These +can be generated by `tput` with `seta{f,b} [0-255]` when the `TERM` +value has a `-256color` suffix. [Some +terminals](https://gist.github.com/XVilka/8346728#now-supporting-truecolour) +also support full 24-bit colors, and any X11 color code can be written +directly into a special escape sequence. ([More +infos](https://gist.github.com/XVilka/8346728)) Only a few programs make +use of anything beyond 256 colors, and tput doesn\'t know about them. +Colors beyond 16 usually only apply to modern terminal emulators running +in graphical environments. + +The Virtual Terminal implemented in the Linux kernel supports only 16 +colors, and the usual default terminfo entry for `TERM=linux` defines +only 8. There is sometimes an alternate \"linux-16color\" that you can +switch to, to get the other 8 colors. + +## Bash examples + +### Hardcoded colors + + printf '%b\n' 'It is \033[31mnot\033[39m intelligent to use \033[32mhardcoded ANSI\033[39m codes!' + +### Colors using tput + +[Directly inside the echo:]{.underline} + + echo "TPUT is a $(tput setaf 2)nice$(tput setaf 9) and $(tput setaf 5)user friendly$(tput setaf 9) terminal capability database." + +[With preset variables:]{.underline} + + COL_NORM="$(tput setaf 9)" + COL_RED="$(tput setaf 1)" + COL_GREEN="$(tput setaf 2)" + echo "It's ${COL_RED}red${COL_NORM} and ${COL_GREEN}green${COL_NORM} - have you seen?" + +### Misc + +[HOME function]{.underline} + + home() { + # yes, actually not much shorter ;-) + tput home + } + +### Silly but nice effect + + #!/bin/bash + + DATA[0]=" _/ _/ _/ _/ " + DATA[1]=" _/_/_/_/_/ _/_/_/ _/_/_/ _/_/_/ _/_/_/ " + DATA[2]=" _/ _/ _/ _/ _/ _/ _/_/ _/ _/" + DATA[3]="_/_/_/_/_/ _/ _/ _/ _/ _/_/ _/ _/ " + DATA[4]=" _/ _/ _/_/_/ _/_/_/ _/_/_/ _/ _/ " + + # virtual coordinate system is X*Y ${#DATA} * 5 + + REAL_OFFSET_X=0 + REAL_OFFSET_Y=0 + + draw_char() { + V_COORD_X=$1 + V_COORD_Y=$2 + + tput cup $((REAL_OFFSET_Y + V_COORD_Y)) $((REAL_OFFSET_X + V_COORD_X)) + + printf %c ${DATA[V_COORD_Y]:V_COORD_X:1} + } + + + trap 'exit 1' INT TERM + trap 'tput setaf 9; tput cvvis; clear' EXIT + + tput civis + clear + + while :; do + + for ((c=1; c <= 7; c++)); do + tput setaf $c + for ((x=0; x<${#DATA[0]}; x++)); do + for ((y=0; y<=4; y++)); do + draw_char $x $y + done + done + done + + done + +### Mandelbrot set + +This is a slightly modified version of Charles Cooke\'s colorful +Mandelbrot plot scripts ([original w/ +screenshot](http://earth.gkhs.net/ccooke/shell.html)) \-- ungolfed, +optimized a bit, and without hard-coded terminal escapes. The `colorBox` +function is [memoized](http://en.wikipedia.org/wiki/Memoization) to +collect `tput` output only when required and output a new escape only +when a color change is needed. This limits the number of `tput` calls to +at most 16, and reduces raw output by more than half. The `doBash` +function uses integer arithmetic, but is still ksh93-compatible (run as +e.g. `bash ./mandelbrot` to use it). The ksh93-only floating-point +`doKsh` is almost 10x faster than `doBash` (thus the ksh shebang by +default), but uses only features that don\'t make the Bash parser crash. + + #!/usr/bin/env ksh + + # Charles Cooke's 16-color Mandelbrot + # http://earth.gkhs.net/ccooke/shell.html + # Combined Bash/ksh93 flavors by Dan Douglas (ormaaj) + + function doBash { + typeset P Q X Y a b c i v x y + for ((P=10**8,Q=P/100,X=320*Q/cols,Y=210*Q/lines,y=-105*Q,v=-220*Q,x=v;y<105*Q;x=v,y+=Y)); do + for ((;x