├── LICENSE
├── README.md
├── appa
├── bash_idioms_style_guide.html
└── bash_idioms_style_guide.md
├── bcb2-appd.pdf
├── ch03
├── list.sh
└── wrapper.sh
├── ch04
├── parameter-expansion.out
└── parameter-expansion.sh
├── ch07
├── hashes.out
├── hashes.sh
├── lists.out
├── lists.sh
├── word-count-example.sh
└── word-count-example.txt
├── ch08
├── parseit.sh
├── parselong.sh
└── parselonghelp.sh
├── ch09
├── fancy_mapfile.out
├── fancy_mapfile.sh
├── fiddle-ifs.out
├── fiddle-ifs.sh
├── shebang-bash.sh
├── shebang-env.sh
├── trivial_trap.out
└── trivial_trap.sh
├── ch10
├── embedded-docs.sh
├── run-embedded-docs.out
├── run-embedded-docs.sh
├── select-ssh.sh
└── ssh_config
└── template.sh
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 JP Vossen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # _bash Idioms_ examples
2 |
3 |
4 |
5 |
6 | Welcome to the examples from O'Reilly's _bash Idioms_, by Carl Albing and JP Vossen.
7 |
8 | *
9 | *
10 | * By the same authors, the _bash Cookbook_ (2nd edition)
11 | *
12 | *
13 | *
14 | * Other bash resources
15 | *
16 |
17 |
18 | ## About the Book
19 |
20 | Shell scripts are everywhere, especially those written in bash-compatible syntax. But these scripts can be complex and obscure. Complexity is the enemy of security, but it’s also the enemy of readability and understanding. With this practical book, you’ll learn how to decipher old bash code and write new code that’s as clear and readable as possible.
21 |
22 | Authors Carl Albing and JP Vossen show you how to use the power and flexibility of the shell to your advantage. You may know enough bash to get by, but this book will take your skills from manageable to magnificent. Whether you use Linux, Unix, Windows, or a Mac, you’ll learn how to read and write scripts like an expert. Your future you will thank you. You’ll explore the clear idioms to use and obscure ones to avoid, so that you can:
23 |
24 | * Write useful, flexible, and readable bash code with style
25 | * Decode bash code such as `${MAKEMELC,,}` and `${PATHNAME##*/}`
26 | * Save time and ensure consistency when automating tasks
27 | * Discover how bash idioms can make your code clean and concise
28 |
29 |
30 | ## Example Files
31 |
32 | Each sub-directory contains the important, long, or difficult-to-type examples from the relevant chapter.
33 |
34 | * `bcb2-appd.pdf` is appendix D of _bash Cookbook_ Second Edition (ISBN 978-1-491-97533-6), extracted here as a stand-alone document for ease of reference and to encourage using revisions control for your bash coding.
35 | * `appa\` is the "Bash Idioms Style Guide" from chapter 11, but without the commentary and discussion:
36 | * HTML version: `bash_idioms_style_guide.html`.
37 | * Markdown version: `bash_idioms_style_guide.md`.
38 | *
39 | * `template.sh` is a sample boilerplate or template script to copy when creating a new script.
40 | * `ch*\` examples from chapters:
41 | * `*.sh` is the actual bash code.
42 | * `*.out` in some cases, is the output as included in the book.
43 | * `ch10/ssh_config` is the sample SSH config file to create a menu used in `ch10/select-ssh.sh` and dome debugging
44 |
--------------------------------------------------------------------------------
/appa/bash_idioms_style_guide.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | bash Idioms Style Guide
8 |
14 |
77 |
78 |
79 |
95 |
The bash Idioms Style Guide
96 |
This is a copy of the points in chapter 11 of bash Idioms but without the commentary and examples. There's also a Markdown file in the examples directory so you can download and tweak it as desired, then render or include it as needed using pandoc or some other tool. Get the code from the https://github.com/vossenjp/bashidioms-examples/tree/master/appa[book's GitHub page].
97 |
The bash Idioms Style Guide Is Not Portable
98 |
This bash Idioms style guide is specifically for bash, so it is not portable to POSIX, Bourne, Dash, or other shells. If you need to write for those shells, you will need to test and tweak this guide to account for the supported syntax and feature of those shells.
99 |
Be especially careful in Docker or other containers where /bin/sh is not bash and /bin/bash may not even exist! This applies to Internet of Things and other constrained environments such as industrial controllers. See "bash in Containers" in the preface and "Shebang" in chapter 9 of bash Idioms.
100 |
Readability
101 |
Readability of your code is important! Or as Python says, readability counts. You only write it once, but you (and others) will probably read it many times. Spend the extra few seconds or minutes thinking about the poor clueless person trying to read the code next year...it's very likely to be you. There's a balance and a tension between abstraction (Don't Repeat Yourself) and readability:
102 |
103 |
KISS (Keep It Simple, Stupid!).
104 |
Readability: don't be "clever," be clear.
105 |
Good names are critical!
106 |
Always use a header.
107 |
If at all possible, emit something useful for -h, --help, and incorrect arguments!
108 |
109 |
Prefer using a "here" document (with leading tabs) rather than a bunch of echo lines because there's less friction when you need to update and probably rewrap it later.
110 |
111 |
Use source (instead of ., which is easy to miss seeing and harder to search for) to include config files, which should end in .cfg (or .conf or whatever your standard is).
112 |
If at all possible, use https://oreil.ly/6QyeH[ISO-8601] dates for everything.
113 |
If at all possible, keep lists of things in alphabetical order; this prevents duplication and makes it easier to add or remove items. Examples include IP addresses (use GNU sort -V), hostnames, packages to install, case statements, and contents of variables or arrays/lists.
114 |
If possible, use long arguments to commands for readability, e.g., use diff --quiet instead of diff -q, though watch out for portability to non-GNU/Linux systems.
115 |
116 |
If any options are short or obscure, add comments.
117 |
Strongly consider documenting why you chose or needed the options you chose, and even options you considered but didn't use for some reason.
118 |
Definitely document any options that might seem like a good idea but that actually can cause problems, especially if you commonly use them elsewhere.
119 |
120 |
121 |
Comments
122 |
123 |
Always use a header.
124 |
Write your comments for the new person on the team a year from now.
125 |
Comment your functions.
126 |
Do not comment on what you did. Comment on why you did, or did not do, something.
127 |
128 |
Exception: comment on what you did when bash itself is obscure.
129 |
130 |
Consider adding comments about external program options, especially if they are short or obscure.
131 |
Use an initial capital on the first word of the comment, but omit ending punctuation unless the comment is more than one sentence.
132 |
133 |
Names
134 |
135 |
Good names are critical!
136 |
Global variables and constants are in UPPER case.
137 |
138 |
Prefer not to make changes to global variables, but sometimes that's just much simpler (KISS).
139 |
Use readonly or declare -r for constants.
140 |
141 |
Other variables are in lowercase.
142 |
Functions are in Mixed_Case.
143 |
Use "_", not CamelCase, in place of space (remember, "-" is not allowed in variable names).
144 |
Use bash arrays carefully; they can be hard to read (see chapter 7 of bash Idioms). for var in $regular_var often works as well.
That makes everything much more debuggable and readable, but it also makes it easy to have defaults and add or rearrange arguments.
148 |
149 |
Distinguish between types of referents, like $input_file versus $input_dir.
150 |
Use consistent "FIXME" and "TODO" labels, with names and ticket numbers if appropriate.
151 |
152 |
Functions
153 |
154 |
Always use a header.
155 |
Good names are critical!
156 |
Functions must be defined before they are used.
157 |
158 |
Group them at the top, and use two blank lines and a function separator between each function.
159 |
Do not intersperse code between functions!
160 |
161 |
Use Camel_Case and "_" to make function names stand out from variable names.
162 |
Use function My_Func_Name { instead of My_Func_Name() { because it's clearer and easier to grep -P '^\s*function '.
163 |
Each function should have comments defining what it does, inputs (including GLOBALS), and outputs.
164 |
When you have useful, standalone pieces of code, or any time you use the same (or substantially similar) block of code more than once, make them into functions. If they are very common, like logging or emailing, consider creating a "library," that is, a single common file you can source as needed.
165 |
166 |
Prefix "library" functions with "_", like _Log or some other prefix.
167 |
168 |
Consider using "filler" words for readability in arguments if it makes sense, then define them as local junk1="$2" # Unused filler, e.g.:
169 |
Do use the local builtin when setting variables in functions.
173 |
174 |
But be aware that successfully being "local," it will mask a failed return code, so declare and assign it on separate lines if using command substitution, like local my_input and then my_input="$(some-command)".
175 |
176 |
For any function longer than about 25 lines, close it with } # End of function MyFuncName to make it easier to track where you are in the code on your screen. For functions shorter than 25 lines, this is optional but encouraged unless it gets too cluttered.
177 |
Don't use a main function; it's almost always just an unnecessary layer.
178 |
179 |
That said, using "main" makes sense to Python and C programmers, or if the code is also used as a library, and it may be required if you do a lot of unit testing.
180 |
181 |
Consider using two blank lines and a main separator above the main section, especially when you have a lot of functions and definitions at the top.
182 |
183 |
Quoting
184 |
185 |
Do put quotes around variables and strings because it makes them stand out a little and clarifies your intent.
186 |
187 |
Unless it gets too cluttered.
188 |
Or they need to be unquoted for expansion.
189 |
190 |
Don't quote integers.
191 |
Use single quotes unless interpolation is required.
192 |
Don't use ${var} unless needed; it's too cluttered.
193 |
194 |
But that is needed sometimes, like ${variable}_suffix or ${being_lower_cased,,}.
195 |
196 |
Do quote command substitutions, like var="$(command)".
197 |
Always quote both sides of any test statement, like [[ "$foo" == 'bar' ]].
198 |
199 |
Unless one side is an integer.
200 |
And unless you are using =~, in which case you can't quote the regular expression!
201 |
202 |
Consider single-quoting variables inside echo statements, like echo "cd to '$DIR' failed." because it's visible when a variable is unexpectedly undefined or empty.
203 |
204 |
Or echo "cd to [$DIR] failed." as you like.
205 |
If using set -u, you will get an error if the variable is not defined—but not if it is defined but is just unexpectedly empty.
206 |
207 |
Prefer single quotes around printf formats (see "POSIX output" in chapter 6 of bash Idioms and the rest of chapter 6 in general).
208 |
209 |
Layout
210 |
211 |
Line things up! Multiple spaces almost never matter in bash (except around =), and lining up similar commands makes it easier to read and to see both the similarities and differences.
212 |
Do not allow trailing white space! This will later cause noise in the VCS (version control system) when removed.
213 |
Indent using four spaces, but use TAB with here-documents as needed.
214 |
Break long lines at around 78 columns, indent line continuations two spaces, and break just before | or > so those parts jump out as you scan down the code.
215 |
The code to open a block goes on one line, like:
216 |
217 |
if expression; then
218 |
for expression; do
219 |
220 |
List elements in case..esac are indented four spaces, and closing ;; are at that same indent level. Blocks for each item are also indented four spaces.
221 |
222 |
One-line elements should be closed with ;; on the same line.
223 |
Prefer lining up the ) in each element, unless it gets cluttered or out of hand.
Use #!/bin/bash - or #!/usr/bin/env bash when writing bash code, not #!/bin/sh.
230 |
Use $@ unless you are really sure you need $*.
231 |
Use == instead of = for equality, to reduce confusion with assignment.
232 |
Use $(...) instead of `...` backticks/backquotes.
233 |
Use [[ instead of [ (unless you need [ for portability, e.g., dash).
234 |
Use ((...)) and $((...)) as needed for integer arithmetic; avoid let and expr.
235 |
Use [[ expression ]] && block or [[ expression ]] || block when it is simple and readable to do so. Do not use [[ expression ]] && block || block because that doesn't do what you think it does; use if .. then .. (elif ..) else .. fi for that.
236 |
Consider using "Unofficial bash Strict Mode" (see "Unofficial bash Strict Mode" in chapter 9 of bash Idioms).
237 |
238 |
set -euo pipefail will prevent or unmask many simple errors.
239 |
Watch out for this one, and use it carefully (if you use it at all): IFS=$'\n\t'.
240 |
241 |
242 |
Other
243 |
244 |
For "system" scripts, log to syslog and let the OS worry about final destination(s), log rotation, etc.
245 |
Error messages should go to STDERR, like echo 'A Bad Thing happened' 1>&2.
246 |
Sanity-check that external tools are available using [ -x '/path/to/tool' ] || { ...error code block... }.
308 |
309 |
310 |
--------------------------------------------------------------------------------
/appa/bash_idioms_style_guide.md:
--------------------------------------------------------------------------------
1 | ## The bash Idioms Style Guide
2 |
3 | This is a copy of the points in chapter 11 of _bash Idioms_ but without the commentary and examples. There's also a Markdown file in the examples directory so you can download and tweak it as desired, then render or include it as needed using `pandoc` or some other tool. Get the code from the https://github.com/vossenjp/bashidioms-examples/tree/master/appa[book's GitHub page].
4 |
5 |
6 | ### The _bash Idioms_ Style Guide Is Not Portable
7 |
8 | This _bash Idioms_ style guide is specifically for bash, so it is not portable to POSIX, Bourne, Dash, or other shells. If you need to write for those shells, you will need to test and tweak this guide to account for the supported syntax and feature of those shells.
9 |
10 | Be especially careful in Docker or other containers where `/bin/sh` is not bash and `/bin/bash` may not even exist! This applies to Internet of Things and other constrained environments such as industrial controllers. See "bash in Containers" in the preface and "Shebang" in chapter 9 of _bash Idioms_.
11 |
12 |
13 | ### Readability
14 |
15 | Readability of your code is important! Or as Python says, _readability counts._ You only write it once, but you (and others) will probably read it many times. Spend the extra few seconds or minutes thinking about the poor clueless person trying to read the code next year...it's very likely to be you. There's a balance and a tension between abstraction (Don't Repeat Yourself) and readability:
16 |
17 | * KISS (Keep It Simple, Stupid!).
18 | * _Readability_: don't be "clever," be clear.
19 | * Good names are critical!
20 | * _Always use a header._
21 | * If at all possible, emit something useful for `-h`, `--help`, and incorrect arguments!
22 | * Prefer using a "here" document (with leading tabs) rather than a bunch of echo lines because there's less friction when you need to update and probably rewrap it later.
23 | * Use `source` (instead of `.`, which is easy to miss seeing and harder to search for) to include config files, which should end in `.cfg` (or `.conf` or whatever your standard is).
24 | * If at all possible, use https://oreil.ly/6QyeH[ISO-8601] dates for everything.
25 | * If at all possible, keep lists of things in alphabetical order; this prevents duplication and makes it easier to add or remove items. Examples include IP addresses (use GNU `sort -V`), hostnames, packages to install, `case` statements, and contents of variables or arrays/lists.
26 | * If possible, use long arguments to commands for readability, e.g., use `diff --quiet` instead of `diff -q`, though watch out for portability to non-GNU/Linux systems.
27 | * If any options are short or obscure, add comments.
28 | * Strongly consider documenting why you chose or needed the options you chose, and even options you considered but didn't use for some reason.
29 | * Definitely document any options that might seem like a good idea but that actually can cause problems, especially if you commonly use them elsewhere.
30 |
31 |
32 | ### Comments
33 |
34 | * _Always use a header._
35 | * Write your comments for the new person on the team a year from now.
36 | * Comment your functions.
37 | * Do not comment on what you did. Comment on why you did, or did not do, something.
38 | * Exception: comment on what you did when bash itself is obscure.
39 | * Consider adding comments about external program options, especially if they are short or obscure.
40 | * Use an initial capital on the first word of the comment, but omit ending punctuation unless the comment is more than one sentence.
41 |
42 |
43 | ### Names
44 |
45 | * Good names are critical!
46 | * Global variables and constants are in UPPER case.
47 | * Prefer not to make changes to global variables, but sometimes that's just much simpler (KISS).
48 | * Use `readonly` or `declare -r` for constants.
49 | * Other variables are in lowercase.
50 | * Functions are in Mixed_Case.
51 | * Use "_", not CamelCase, in place of space (remember, "-" is not allowed in variable names).
52 | * Use bash arrays carefully; they can be hard to read (see chapter 7 of _bash Idioms_). `for var in $regular_var` often works as well.
53 | * Replace `$1`, `$2`, .. `$N` with readable names ASAP.
54 | * That makes everything much more debuggable and readable, but it also makes it easy to have defaults and add or rearrange arguments.
55 | * Distinguish between types of referents, like `$input_file` versus `$input_dir`.
56 | * Use consistent "FIXME" and "TODO" labels, with names and ticket numbers if appropriate.
57 |
58 |
59 | ### Functions
60 |
61 | * _Always use a header._
62 | * Good names are critical!
63 | * Functions must be defined before they are used.
64 | * Group them at the top, and use two blank lines and a function separator between each function.
65 | * Do _not_ intersperse code between functions!
66 | * Use Camel_Case and "_" to make function names stand out from variable names.
67 | * Use `function My_Func_Name {` instead of `My_Func_Name() {` because it's clearer and easier to `grep -P '^\s*function '`.
68 | * Each function should have comments defining what it does, inputs (including GLOBALS), and outputs.
69 | * When you have useful, standalone pieces of code, or any time you use the same (or substantially similar) block of code more than once, make them into functions. If they are very common, like logging or emailing, consider creating a "library," that is, a single common file you can source as needed.
70 | * Prefix "library" functions with "_", like `_Log` or some other prefix.
71 | * Consider using "filler" words for readability in arguments if it makes sense, then define them as `local junk1="$2" # Unused filler`, e.g.:
72 | * `_Create_File_If_Needed "/path/to/$file" containing 'important value'`
73 | * Do use the `local` builtin when setting variables in functions.
74 | * But be aware that successfully being "local," it will mask a failed return code, so declare and assign it on separate lines if using command substitution, like `local my_input` and then `my_input="$(some-command)"`.
75 | * For any function longer than about 25 lines, close it with `} # End of function MyFuncName` to make it easier to track where you are in the code on your screen. For functions shorter than 25 lines, this is optional but encouraged unless it gets too cluttered.
76 | * Don't use a `main` function; it's almost always just an unnecessary layer.
77 | * That said, using "main" makes sense to Python and C programmers, or if the code is also used as a library, and it may be required if you do a lot of unit testing.
78 | * Consider using two blank lines and a main separator above the main section, especially when you have a lot of functions and definitions at the top.
79 |
80 |
81 | ### Quoting
82 |
83 | * Do put quotes around variables and strings because it makes them stand out a little and clarifies your intent.
84 | * Unless it gets too cluttered.
85 | * Or they need to be unquoted for expansion.
86 | * Don't quote integers.
87 | * Use single quotes unless interpolation is required.
88 | * Don't use `${var}` unless needed; it's too cluttered.
89 | * But that _is_ needed sometimes, like `${variable}_suffix` or `${being_lower_cased,,}`.
90 | * Do quote command substitutions, like `var="$(command)"`.
91 | * _Always_ quote both sides of any test statement, like `[[ "$foo" == 'bar' ]]`.
92 | * Unless one side is an integer.
93 | * And unless you are using `=~`, in which case you can't quote the regular expression!
94 | * Consider single-quoting variables inside `echo` statements, like `` echo "cd to '$DIR' failed." `` because it's visible when a variable is unexpectedly undefined or empty.
95 | * Or `echo "cd to [$DIR] failed."` as you like.
96 | * If using `set -u`, you will get an error if the variable is not defined—but not if it is defined but is just unexpectedly empty.
97 | * Prefer single quotes around `printf` formats (see "POSIX output" in chapter 6 of _bash Idioms_ and the rest of chapter 6 in general).
98 |
99 |
100 | ### Layout
101 |
102 | * Line things up! Multiple spaces almost never matter in bash (except around `=`), and lining up similar commands makes it easier to read and to see both the similarities and differences.
103 | * _Do not allow trailing white space!_ This will later cause noise in the VCS (version control system) when removed.
104 | * Indent using four spaces, but use TAB with here-documents as needed.
105 | * Break long lines at around 78 columns, indent line continuations two spaces, and break just before `|` or `>` so those parts jump out as you scan down the code.
106 | * The code to open a block goes on one line, like:
107 | * `if expression; then`
108 | * `for expression; do`
109 | * List elements in `case..esac` are indented four spaces, and closing `;;` are at that same indent level. Blocks for each item are also indented four spaces.
110 | * One-line elements should be closed with `;;` on the same line.
111 | * Prefer lining up the `)` in each element, unless it gets cluttered or out of hand.
112 | * See the example code in [parselonghelp.sh](https://github.com/vossenjp/bashidioms-examples/tree/master/ch08/parselonghelp.sh).
113 |
114 |
115 | ### Syntax
116 |
117 | * Use `#!/bin/bash -` or `#!/usr/bin/env bash` when writing bash code, not `#!/bin/sh`.
118 | * Use `$@` unless you are _really_ sure you need `$*`.
119 | * Use `==` instead of `=` for equality, to reduce confusion with assignment.
120 | * Use `$(...)` instead of `` `...` `` backticks/backquotes.
121 | * Use `[[` instead of `[` (unless you need `[` for portability, e.g., `dash`).
122 | * Use `((...))` and `$((...))` as needed for integer arithmetic; avoid `let` and `expr`.
123 | * Use `[[ expression ]] && block` or `[[ expression ]] || block` when it is simple and readable to do so. Do not use `[[ expression ]] && block || block` because that doesn't do what you think it does; use `if .. then .. (elif ..) else .. fi` for that.
124 | * Consider using "Unofficial bash Strict Mode" (see "Unofficial bash Strict Mode" in chapter 9 of _bash Idioms_).
125 | * `set -euo pipefail` will prevent or unmask many simple errors.
126 | * Watch out for this one, and use it carefully (if you use it at all): `IFS=$'\n\t'`.
127 |
128 |
129 | ### Other
130 |
131 | * For "system" scripts, log to syslog and let the OS worry about final destination(s), log rotation, etc.
132 | * Error messages should go to STDERR, like `echo 'A Bad Thing happened' 1>&2`.
133 | * Sanity-check that external tools are available using `[ -x '/path/to/tool' ] || { ...error code block... }`.
134 | * Provide useful messages when things fail.
135 | * Set `exit` codes, especially when you fail.
136 |
137 |
138 | ### Script Template
139 |
140 | ~~~~ {.bash}
141 | #!/bin/bash -
142 | # Or possibly: #!/usr/bin/env bash
143 | # :
144 | # Original Author & date:
145 | # Current maintainer?
146 | # Copyright/License?
147 | # Where this code belongs? (Hosts, paths, etc.)
148 | # Project/repo?
149 | # Caveats/gotchas?
150 | # Usage? (Better to have `-h` and/or `--help` options!)
151 | # $URL$ # If using SVN
152 | ID='' # If using SVN
153 | #_________________________________________________________________________
154 | PROGRAM=${0##*/} # bash version of `basename`
155 |
156 | # Unofficial bash Strict Mode?
157 | #set -euo pipefail
158 | ### CAREFUL: IFS=$'\n\t'
159 |
160 | # GLOBAL and constant variables are in UPPER case
161 | LOG_DIR='/path/to/log/dir'
162 |
163 | ### Consider adding argument handling to YOUR template; see:
164 | # examples/ch08/parseit.sh
165 | # examples/ch08/parselong.sh
166 | # examples/ch08/parselonghelp.sh
167 |
168 | # Functions are in Mixed Case
169 | ###########################################################################
170 | # Define functions
171 |
172 | #--------------------------------------------------------------------------
173 | # Example function
174 | # Globals: none
175 | # Input: nothing
176 | # Output: nothing
177 | function Foo {
178 | local var1="$1"
179 | ...
180 | } # End of function foo
181 |
182 |
183 | #--------------------------------------------------------------------------
184 | # Another example function
185 | # Globals: none
186 | # Input: nothing
187 | # Output: nothing
188 | function Bar {
189 | local var1="$1"
190 | ...
191 | } # End of function bar
192 |
193 |
194 | ###########################################################################
195 | # Main
196 |
197 | # Code...
198 | ~~~~
199 |
--------------------------------------------------------------------------------
/bcb2-appd.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vossenjp/bashidioms-examples/d1e940fa39b06af8d91b9abd65bda8fd60e1940f/bcb2-appd.pdf
--------------------------------------------------------------------------------
/ch03/list.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # list.sh: A wrapper script for ls-related tools & simple `case..esac` demo
3 | # Original Author & date: _bash Idioms_ 2022
4 | # bash Idioms filename: examples/ch03/list.sh
5 | #_________________________________________________________________________
6 | VERSION='v1.2b'
7 |
8 | function Usage_Exit {
9 | echo "$0 [color|last|len|long]"
10 | exit
11 | }
12 |
13 | # Show each filename preceded by the length of its name, sorted by filename
14 | # length. Note '-' is valid but uncommon in function names, but it is not
15 | # valid in variable names. We don't usually use it, but you can.
16 | function Ls-Length {
17 | ls -1 "$@" | while read fn; do
18 | printf '%3d %s\n' ${#fn} ${fn}
19 | done | sort -n
20 | }
21 |
22 | (( $# < 1 )) && Usage_Exit # <1>
23 | sub=$1
24 | shift
25 |
26 | case $sub in
27 | color) # Colorized ls
28 | ls -N --color=tty -T 0 "$@"
29 | ;;
30 |
31 | last | latest) # Latest files # <2>
32 | ls -lrt | tail "-n${1:-5}" # <3>
33 | ;;
34 |
35 | len*) # Files with name lengths # <4>
36 | Ls-Length "$@"
37 | ;;
38 |
39 | long) # File with longest name
40 | Ls-Length "$@" | tail -1
41 | ;;
42 |
43 | *) # Default
44 | echo "unknown command: $sub"
45 | Usage_Exit
46 | ;;
47 | esac
48 |
--------------------------------------------------------------------------------
/ch03/wrapper.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # wrapper.sh: Simple "wrapper" script demo
3 | # Original Author & date: _bash Idioms_ 2022
4 | # bash Idioms filename: examples/ch03/wrapper.sh
5 | #_________________________________________________________________________
6 |
7 | # Trivial Sanity Checks # <1>
8 | [ -n "$BOOK_ASC" ] || {
9 | echo "FATAL: export \$BOOK_ASC to the location of the Asciidoc files!"
10 | exit 1
11 | }
12 | \cd "$BOOK_ASC" || {
13 | echo "FATAL: can't cd to '$BOOK_ASC'!"
14 | exit 2
15 | }
16 |
17 | SELF="$0" # <2>
18 |
19 | action="$1" # <3>
20 | shift # <4>
21 | [ -x /usr/bin/xsel -a $# -lt 1 ] && { # <5>
22 | # Read/write the clipboard on Linux
23 | text=$(xsel -b)
24 | function Output {
25 | echo -en "$*" | xsel -bi
26 | }
27 | } || {
28 | # Read/write STDIN/STDOUT
29 | text=$*
30 | function Output {
31 | echo -en "$*"
32 | }
33 | }
34 |
35 | case "$action" in # <6>
36 |
37 | #######################################################################
38 | # Content/Markup # <7>
39 |
40 | ### Headers # <8>
41 | h1 ) # Inside chapter heading 1 (really AsciiDoc h3) <9>
42 | Output "[[$($SELF id $text)]]\n=== $text" # <10>
43 | ;;
44 | h2 ) # Inside chapter heading 2 (really AsciiDoc h4)
45 | Output "[[$($SELF id $text)]]\n==== $text"
46 | ;;
47 | h3 ) # Inside chapter heading 3 (really AsciiDoc h5)
48 | Output "[[$($SELF id $text)]]\n===== $text"
49 | ;;
50 |
51 | ### Lists
52 | bul|bullet ) # Bullet list (** = level 2, + = multiline element)
53 | Output "* $text"
54 | ;;
55 | nul|number|order* ) # Numbered/ordered list (.. = level 2, + = multiline)
56 | Output ". $text"
57 | ;;
58 | term ) # Terms
59 | Output "term_here::\n $text"
60 | ;;
61 |
62 | ### Inline
63 | bold ) # Inline bold (O'Reilly prefers italics to bold)
64 | Output "*$text*"
65 | ;;
66 | i|italic*|itl ) # Inline italics (O'Reilly prefers italics to bold)
67 | Output "_${text}_"
68 | ;;
69 | c|constant|cons ) # Inline constant width (command, code, keywords, more)
70 | Output "+$text+"
71 | ;;
72 | type|constantbold ) # Inline bold constant width (user types literally)
73 | Output "*+$text+*"
74 | ;;
75 | var|constantitalic ) # Inline italic constant width (user-supplied values)
76 | Output "_++$text++_"
77 | ;;
78 | sub|subscript ) # Inline subscript
79 | Output "~$text~"
80 | ;;
81 | sup|superscript ) # Inline superscript
82 | Output "^$text^"
83 | ;;
84 | foot ) # Create a footnote
85 | Output "footnote:[$text]"
86 | ;;
87 | url|link ) # Create a URL with alternate text
88 | Output "link:\$\$$text\$\$[]" # URL[link shows as]
89 | ;;
90 | esc|escape ) # Escape a character (esp. *)
91 | Output "\$\$$text\$\$" # $$*$$
92 | ;;
93 |
94 |
95 | #######################################################################
96 | # Tools # <11>
97 |
98 | id ) ## Convert a hack/recipe name to an ID
99 | #us_text=${text// /_} # Space to '_'
100 | #lc_text=${us_text,,} # Lowercase; bash 4+ only!
101 | # Initial `tr -s '_' ' '` to preserve _ in case we process an ID
102 | # twice (like from "xref")
103 | # Also note you can break long lines with a trailing \ # <12>
104 | Output $(echo $text | tr -s '_' ' ' | tr '[:upper:]' '[:lower:]' \
105 | | tr -d '[:punct:]' | tr -s ' ' '_')
106 | ;;
107 |
108 | index ) ## Creates 'index.txt' in AsciiDoc dir
109 | # Like:
110 | # ch02.asciidoc:== The Text Utils
111 | # ch02.asciidoc:=== Common Text Utils and similar tools
112 | # ch02.asciidoc:=== Finding data
113 | egrep '^=== ' ch*.asciidoc | egrep -v '^ch00.asciidoc' \
114 | > $BOOK_ASC/index.txt && {
115 | echo "Updated: $BOOK_ASC/index.txt"
116 | exit 0
117 | } || {
118 | echo "FAILED to update: $BOOK_ASC/index.txt"
119 | exit 1
120 | }
121 | ;;
122 |
123 | rerun ) ## Run examples to re-create (existing!) output files
124 | # Only re-run for code that ALREADY HAS a *.out file...not ALL *.sh code
125 | for output in examples/*/*.out; do
126 | code=${output/out/sh}
127 | echo "Re-running code for: $code > $output"
128 | $code > $output
129 | done
130 | ;;
131 |
132 | cleanup ) ## Clean up all the xHTML/XML/PDF cruft
133 | rm -fv {ch??,app?}.{pdf,xml,html} book.{xml,html} docbook-xsl.css
134 | ;;
135 |
136 |
137 | * ) # <13>
138 | \cd - # UGLY cheat to revert the 'cd' above...
139 | ( echo "Usage:" # <14>
140 | egrep '\)[[:space:]]+# ' $0 # <15>
141 | echo ''
142 | egrep '\)[[:space:]]+## ' $0 # <16>
143 | echo ''
144 | egrep '\)[[:space:]]+### ' $0 ) | grep "${1:-.}" | more # <17>
145 | ;;
146 |
147 | esac
148 |
--------------------------------------------------------------------------------
/ch04/parameter-expansion.out:
--------------------------------------------------------------------------------
1 |
2 | Say we have this string: Acme Inc subnet 10.11.12.13/24
3 |
4 | When the code runs we get:
5 |
6 | Customer name: Acme Inc
7 | Subnet: 10.11.12.13/24
8 | IPA 10.11.12.13
9 | CIDR mask: 24
10 | FW Object: acme_inc_subnet_10.11.12.13-24
11 |
--------------------------------------------------------------------------------
/ch04/parameter-expansion.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # parameter-expansion.sh: parameter expansion for parsing, and a big list
3 | # Original Author & date: _bash Idioms_ 2022
4 | # bash Idioms filename: examples/ch04/parameter-expansion.sh
5 | #_________________________________________________________________________
6 | # Does not work on Zsh 5.4.2!
7 |
8 | customer_subnet_name='Acme Inc subnet 10.11.12.13/24'
9 |
10 | echo ''
11 | echo "Say we have this string: $customer_subnet_name"
12 |
13 | customer_name=${customer_subnet_name%subnet*} # Trim from 'subnet' to end
14 | subnet=${customer_subnet_name##* } # Remove leading 'space*'
15 | ipa=${subnet%/*} # Remove trailing '/*'
16 | cidr=${subnet#*/} # Remove up to '/*'
17 | fw_object_name=${customer_subnet_name// /_} # Replace space with '_-
18 | fw_object_name=${fw_object_name////-} # Replace '/' with '-'
19 | fw_object_name=${fw_object_name,,} # Lowercase
20 |
21 | echo ''
22 | echo 'When the code runs we get:'
23 | echo ''
24 | echo "Customer name: $customer_name"
25 | echo "Subnet: $subnet"
26 | echo "IPA $ipa"
27 | echo "CIDR mask: $cidr"
28 | echo "FW Object: $fw_object_name"
29 |
30 | # bash Shell Parameter Expansion: https://oreil.ly/Af8lw
31 |
32 | # ${var#pattern} Remove shortest (nongreedy) leading pattern
33 | # ${var##pattern} Remove longest (greedy) leading pattern
34 | # ${var%pattern} Remove shortest (nongreedy) trailing pattern
35 | # ${var%%pattern} Remove longest (greedy) trailing pattern
36 |
37 | # ${var/pattern/replacement} Replace first +pattern+ with +replacement+
38 | # ${var//pattern/replacement} Replace all +pattern+ with +replacement+
39 |
40 | # ${var^pattern} Uppercase first matching optional pattern
41 | # ${var^^pattern} Uppercase all matching optional pattern
42 | # ${var,pattern} Lowercase first matching optional pattern
43 | # ${var,,pattern} Lowercase all matching optional pattern
44 |
45 | # ${var:offset} Substring starting at +offset+
46 | # ${var:offset:length} Substring starting at +offset+ for +length+
47 |
48 | # ${var:-default} Var if set, otherwise +default+
49 | # ${var:=default} Assign +default+ to +var+ if +var+ not already set
50 | # ${var:?error_message} Barf with +error_message+ if +var+ not set
51 | # ${var:+replaced} Expand to +replaced+ if +var+ _is_ set
52 |
53 | # ${#var} Length of var
54 | # ${!var[*]} Expand to indexes or keys
55 | # ${!var[@]} Expand to indexes or keys, quoted
56 |
57 | # ${!prefix*} Expand to variable names starting with +prefix+
58 | # ${!prefix@} Expand to variable names starting with +prefix+, quoted
59 |
60 | # ${var@Q} Quoted
61 | # ${var@E} Expanded (better than `eval`!)
62 | # ${var@P} Expanded as prompt
63 | # ${var@A} Assign or declare
64 | # ${var@a} Return attributes
65 |
--------------------------------------------------------------------------------
/ch07/hashes.out:
--------------------------------------------------------------------------------
1 |
2 | The key count is: 7 or 7
3 |
4 | The length of the value of key [e] is: 4
5 |
6 | Dump or list:
7 | declare -A myhash=([a]="foo" [b]="bar" [c]="baz" [d]="three"
8 | [e]="four" [f]="five by five" [g]="six" )
9 | ${myhash[@]} = foo|bar|baz|three|four|five|by|five|six|
10 | ${myhash[*]} = foo|bar|baz|three|four|five|by|five|six|
11 | "${myhash[@]}" = foo|bar|baz|three|four|five\ by\ five|six|
12 | "${myhash[*]}" = foo\ bar\ baz\ three\ four\ five\ by\ five\ six| # Broken!
13 |
14 | Join ',' ${myhash[@]} = foo,bar,baz,three,four,five by five,six
15 | String_Join '<>' ${myhash[@]} = foo<>bar<>baz<>three<>four<>five by five<>six
16 | foreach "${!myhash[@]}":
17 | Key: a; value: foo
18 | Key: b; value: bar
19 | Key: c; value: baz
20 | Key: d; value: three
21 | Key: e; value: four
22 | Key: f; value: five by five
23 | Key: g; value: six
24 |
25 | But don't do this: ${myhash[*]}
26 | Key: foo; value:
27 | Key: bar; value:
28 | Key: baz; value:
29 | Key: three; value:
30 | Key: four; value:
31 | Key: five; value:
32 | Key: by; value:
33 | Key: five; value:
34 | Key: six; value:
35 |
36 | Start from hash insertion element 5 and show a slice of 2 elements:
37 | four|five\ by\ five|
38 |
39 | Start from hash insertion element 0 (huh?) and show a slice of 3 elements:
40 | foo|bar|baz|
41 |
42 | Start from hash insertion element 1 and show a slice of 3 elements:
43 | foo|bar|baz|
44 |
45 | Delete key c using unset (dumped before and after):
46 | declare -A myhash=([a]="foo" [b]="bar" [c]="baz" [d]="three"
47 | [e]="four" [f]="five by five" [g]="six" )
48 | declare -A myhash=([a]="foo" [b]="bar" [d]="three" [e]="four"
49 | [f]="five by five" [g]="six" )
50 |
--------------------------------------------------------------------------------
/ch07/hashes.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # hashes.sh: bash Hash example code
3 | # Original Author & date: _bash Idioms_ 2022
4 | # bash Idioms filename: examples/ch07/hashes.sh
5 | #_________________________________________________________________________
6 | # Does not work on Zsh 5.4.2!
7 |
8 | # Books are not as wide as some screens!
9 | FORMAT='fmt --width 70 --split-only'
10 |
11 | # Declare a hash <1>
12 | declare -A myhash # MUST do this, or `local -A` or `readonly -A`
13 |
14 | # Assign to it, note no "+" <2>
15 | ###myhash=(bar) # Error: needs subscript when assigning associative array
16 | myhash[a]='foo' # Insertion 1, not 0...sort of
17 | myhash[b]='bar' # Insertion 2
18 | myhash[c]='baz' # Insertion 3
19 | myhash[d]='three' # 4 Different than our list example
20 | myhash[e]='four' # Insertion 5, note, not 4
21 | myhash[f]='five by five' # 6 Note spaces
22 | myhash[g]='six' # Insertion 7
23 |
24 | # OR
25 | #myhash=([a]=foo [b]=bar [c]=baz [d]="three" [e]="four" [f]="five by five" [g]="six")
26 |
27 | # Display or dump the details and values <3>
28 | echo -e "\nThe key count is: ${#myhash[@]} or ${#myhash[*]}"
29 |
30 | echo -e "\nThe length of the value of key [e] is: ${#myhash[e]}"
31 |
32 | echo -e "\nDump or list:"
33 | declare -p myhash | $FORMAT
34 | echo -n "\${myhash[@]} = " ; printf "%q|" ${myhash[@]}
35 | echo -en "\n\${myhash[*]} = " ; printf "%q|" ${myhash[*]}
36 | echo -en "\n\"\${myhash[@]}\" = " ; printf "%q|" "${myhash[@]}"
37 | echo -en "\n\"\${myhash[*]}\" = " ; printf "%q|" "${myhash[*]}"
38 | echo -e " # Broken!" # Previous line is bad and no newline
39 | # See `help printf` or chapter 6 "printf for reuse or debugging", we need
40 | # this to show the correct words:
41 | # %q quote the argument in a way that can be reused as shell input
42 |
43 | # "Join" the values <4>
44 | function Join { local IFS="$1"; shift; echo "$*"; } # One character delimiter!
45 | # Note the Join above requires "$*" and not "$@"!
46 | echo -en "\nJoin ',' \${myhash[@]} = " ; Join ',' "${myhash[@]}"
47 | function String_Join {
48 | local delimiter="$1"
49 | local first_element="$2"
50 | shift 2
51 | printf '%s' "$first_element" "${@/#/$delimiter}"
52 | # Print first element, then reuse the '%s' format to display the rest of
53 | # the elements (from the function args $@), but add a prefix of $delimiter
54 | # by "replacing" the leading empty pattern (/#) with $delimiter.
55 | }
56 | echo -n "String_Join '<>' \${myhash[@]} = " ; String_Join '<>' "${myhash[@]}"
57 |
58 | # Iterate over the keys and values <5>
59 | echo -e "\nforeach \"\${!myhash[@]}\":"
60 | for key in "${!myhash[@]}"; do
61 | echo -e "\tKey: $key; value: ${myhash[$key]}"
62 | done
63 |
64 | echo -e "\nBut don't do this: \${myhash[*]}"
65 | for key in ${myhash[*]}; do
66 | echo -e "\tKey: $key; value: ${myhash[$key]}"
67 | done
68 |
69 | # Handle slices (subsets) of the hash <6>
70 | echo -e "\nStart from hash insertion element 5 and show a slice of 2 elements:"
71 | printf "%q|" "${myhash[@]:5:2}"
72 | echo '' # No newline in above
73 | echo -e "\nStart from hash insertion element 0 (huh?) and show a slice of 3 elements:"
74 | printf "%q|" "${myhash[@]:0:3}"
75 | echo '' # No newline in above
76 | echo -e "\nStart from hash insertion element 1 and show a slice of 3 elements:"
77 | printf "%q|" "${myhash[@]:1:3}"
78 | echo '' # No newline in above
79 |
80 | #echo -e "\nShift FIRST key [0]:" = makes no sense in a hash!
81 | #echo -e "\nPop LAST key:" = makes no sense in a hash!
82 |
83 | # Delete keys <7>
84 | echo -e "\nDelete key c using unset (dumped before and after):"
85 | declare -p myhash | $FORMAT
86 | unset -v 'myhash[c]'
87 | declare -p myhash | $FORMAT
88 |
89 | # Delete the entire hash <8>
90 | unset -v myhash
91 |
--------------------------------------------------------------------------------
/ch07/lists.out:
--------------------------------------------------------------------------------
1 |
2 | The element count is: 7 or 7
3 |
4 | The length of element [4] is: 4
5 |
6 | Dump or list:
7 | declare -a mylist=([0]="foo" [1]="bar" [2]="baz" [3]="three"
8 | [4]="four" [5]="five by five" [6]="six")
9 | ${mylist[@]} = foo|bar|baz|three|four|five|by|five|six|
10 | ${mylist[*]} = foo|bar|baz|three|four|five|by|five|six|
11 | "${mylist[@]}" = foo|bar|baz|three|four|five\ by\ five|six|
12 | "${mylist[*]}" = foo\ bar\ baz\ three\ four\ five\ by\ five\ six| # Broken!
13 |
14 | Join ',' ${mylist[@]} = foo,bar,baz,three,four,five by five,six
15 | String_Join '<>' ${mylist[@]} = foo<>bar<>baz<>three<>four<>five by five<>six
16 | foreach "${!mylist[@]}":
17 | Element: 0; value: foo
18 | Element: 1; value: bar
19 | Element: 2; value: baz
20 | Element: 3; value: three
21 | Element: 4; value: four
22 | Element: 5; value: five by five
23 | Element: 6; value: six
24 |
25 | But don't do this: ${mylist[*]}
26 | Element: foo; value: foo
27 | Element: bar; value: foo
28 | Element: baz; value: foo
29 | Element: three; value: foo
30 | Element: four; value: foo
31 | Element: five; value: foo
32 | Element: by; value: foo
33 | Element: five; value: foo
34 | Element: six; value: foo
35 |
36 | Start from element 5 and show a slice of 2 elements:
37 | five\ by\ five|six|
38 |
39 | Shift FIRST element [0] (dumped before and after):
40 | declare -a mylist=([0]="foo" [1]="bar" [2]="baz" [3]="three"
41 | [4]="four" [5]="five by five" [6]="six")
42 | declare -a mylist=([0]="bar" [1]="baz" [2]="three" [3]="four"
43 | [4]="five by five" [5]="six")
44 |
45 | Pop LAST element (dumped before and after):
46 | declare -a mylist=([0]="bar" [1]="baz" [2]="three" [3]="four"
47 | [4]="five by five" [5]="six")
48 | declare -a mylist=([0]="bar" [1]="baz" [2]="three" [3]="four" [4]="five by five")
49 |
50 | Delete element 2 using unset (dumped before and after):
51 | declare -a mylist=([0]="bar" [1]="baz" [2]="three" [3]="four" [4]="five by five")
52 | declare -a mylist=([0]="bar" [1]="baz" [3]="four" [4]="five by five")
53 |
--------------------------------------------------------------------------------
/ch07/lists.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # lists.sh: bash list example code
3 | # Original Author & date: _bash Idioms_ 2022
4 | # bash Idioms filename: examples/ch07/lists.sh
5 | #_________________________________________________________________________
6 | # Does not work on Zsh 5.4.2!
7 |
8 | # Books are not as wide as some screens!
9 | FORMAT='fmt --width 70 --split-only'
10 |
11 | # Declare a list <1>
12 | # declare -a mylist # Can do this, or `local -a` or `readonly -a` or:
13 | mylist[0]='foo' # This both declares and assigns to mylist[0]
14 |
15 | # OR Both declares & assigns:
16 | #mylist=(foo bar baz three four "five by five" six)
17 |
18 | # Push or assign, note the += and () <2>
19 | ###mylist=(bar) # Would overwrite mylist[0]
20 | mylist+=(bar) # mylist[1]
21 | mylist+=(baz) # mylist[2]
22 | mylist+=(three four) # mylist[3] AND mylist[4]
23 | mylist+=("five by five") # mylist[5] Note spaces and quotes
24 | mylist+=("six") # mylist[6]
25 |
26 | # OR APPEND, note the "+" and we're assuming foo was already assigned
27 | #mylist+=(bar baz three four "five by five" six)
28 |
29 | # Display or dump the values <3>
30 | echo -e "\nThe element count is: ${#mylist[@]} or ${#mylist[*]}"
31 |
32 | echo -e "\nThe length of element [4] is: ${#mylist[4]}"
33 |
34 | echo -e "\nDump or list:"
35 | declare -p mylist | $FORMAT
36 | echo -n "\${mylist[@]} = " ; printf "%q|" ${mylist[@]}
37 | echo -en "\n\${mylist[*]} = " ; printf "%q|" ${mylist[*]}
38 | echo -en "\n\"\${mylist[@]}\" = " ; printf "%q|" "${mylist[@]}"
39 | echo -en "\n\"\${mylist[*]}\" = " ; printf "%q|" "${mylist[*]}"
40 | echo -e " # Broken!" # Previous line is bad and no newline
41 | # See `help printf` or chapter 6 "printf for reuse or debugging", we need
42 | # this to show the correct words:
43 | # %q quote the argument in a way that can be reused as shell input
44 |
45 | # "Join" the values <4>
46 | function Join { local IFS="$1"; shift; echo "$*"; } # One character delimiter!
47 | # Note that the Join above requires "$*" and not "$@"!
48 | echo -en "\nJoin ',' \${mylist[@]} = "; Join ',' "${mylist[@]}"
49 | function String_Join {
50 | local delimiter="$1"
51 | local first_element="$2"
52 | shift 2
53 | printf '%s' "$first_element" "${@/#/$delimiter}"
54 | # Print first element, then reuse the '%s' format to display the rest of
55 | # the elements (from the function args $@), but add a prefix of $delimiter
56 | # by "replacing" the leading empty pattern (/#) with $delimiter.
57 | }
58 | echo -n "String_Join '<>' \${mylist[@]} = " ; String_Join '<>' "${mylist[@]}"
59 |
60 | # Iterate over the values <5>
61 | echo -e "\nforeach \"\${!mylist[@]}\":"
62 | for element in "${!mylist[@]}"; do
63 | echo -e "\tElement: $element; value: ${mylist[$element]}"
64 | done
65 |
66 | echo -e "\nBut don't do this: \${mylist[*]}"
67 | for element in ${mylist[*]}; do
68 | echo -e "\tElement: $element; value: ${mylist[$element]}"
69 | done
70 |
71 | # Handle slices (subsets) of the list, shift and pop <6>
72 | echo -e "\nStart from element 5 and show a slice of 2 elements:"
73 | printf "%q|" "${mylist[@]:5:2}"
74 | echo '' # No newline in above
75 |
76 | echo -e "\nShift FIRST element [0] (dumped before and after):"
77 | declare -p mylist | $FORMAT # Display before
78 | mylist=("${mylist[@]:1}") # First element, needs quotes
79 | #mylist=("${mylist[@]:$count}") # First #count elements
80 | declare -p mylist | $FORMAT # Display after
81 |
82 | echo -e "\nPop LAST element (dumped before and after):"
83 | declare -p mylist | $FORMAT
84 | unset -v 'mylist[-1]' # bash v4.3+
85 | #unset -v "mylist[${#mylist[*]}-1]" # Older
86 | declare -p mylist
87 |
88 | # Delete slices <7>
89 | echo -e "\nDelete element 2 using unset (dumped before and after):"
90 | declare -p mylist
91 | unset -v 'mylist[2]'
92 | declare -p mylist
93 |
94 | # Delete the entire list <8>
95 | unset -v mylist
96 |
--------------------------------------------------------------------------------
/ch07/word-count-example.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # word-count-example.sh: More examples for bash lists and hashes, and $RANDOM
3 | # Original Author & date: _bash Idioms_ 2022
4 | # bash Idioms filename: examples/ch07/word-count-example.sh
5 | #_________________________________________________________________________
6 | # Does not work on Zsh 5.4.2!
7 | # See also: `man uniq`
8 |
9 | WORD_FILE='/tmp/words.txt'
10 | > $WORD_FILE # <1>
11 | trap "rm -f $WORD_FILE" ABRT EXIT HUP INT QUIT TERM
12 |
13 | declare -A myhash # <2>
14 |
15 | echo "Creating & reading random word list in: $WORD_FILE"
16 |
17 | # Create a list of words to use for the hash example
18 | mylist=(foo bar baz one two three four)
19 |
20 | # Loop, and randomly pick elements out of the list
21 | range="${#mylist[@]}" # <3>
22 | for ((i=0; i<35; i++)); do
23 | random_element="$(( $RANDOM % $range ))" # <4>
24 | echo "${mylist[$random_element]}" >> $WORD_FILE # <5>
25 | done
26 |
27 | # Read the word list into a hash
28 | while read line; do # <6>
29 | (( myhash[$line]++ )) # <7>
30 | done < $WORD_FILE # <8>
31 |
32 |
33 | echo -e "\nUnique words from: $WORD_FILE" # <9>
34 | for key in "${!myhash[@]}"; do
35 | echo "$key"
36 | done | sort
37 |
38 | echo -e "\nWord counts, ordered by word, from: $WORD_FILE" # <10>
39 | for key in "${!myhash[@]}"; do
40 | printf "%s\t%d\n" $key ${myhash[$key]}
41 | done | sort
42 |
43 | echo -e "\nWord counts, ordered by count, from: $WORD_FILE" # <11>
44 | for key in "${!myhash[@]}"; do
45 | printf "%s\t%d\n" $key ${myhash[$key]}
46 | done | sort -k2,2n
47 |
--------------------------------------------------------------------------------
/ch07/word-count-example.txt:
--------------------------------------------------------------------------------
1 | Creating & reading random word list in: /tmp/words.txt
2 |
3 | Unique words from: /tmp/words.txt
4 | bar
5 | baz
6 | foo
7 | four
8 | one
9 | three
10 | two
11 |
12 | Word counts, ordered by word, from: /tmp/words.txt
13 | bar 7
14 | baz 6
15 | foo 4
16 | four 3
17 | one 5
18 | three 4
19 | two 6
20 |
21 | Word counts, ordered by count, from: /tmp/words.txt
22 | four 3
23 | foo 4
24 | three 4
25 | one 5
26 | baz 6
27 | two 6
28 | bar 7
29 |
--------------------------------------------------------------------------------
/ch08/parseit.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # parseit.sh: Use getopts to parse these arguments
3 | # Original Author & date: _bash Idioms_ 2022
4 | # bash Idioms filename: examples/ch08/parseit.sh
5 | #_________________________________________________________________________
6 |
7 | while getopts ':ao:v' VAL ; do # <1>
8 | case $VAL in # <2>
9 | a ) AMODE=1 ;;
10 | o ) OFILE="$OPTARG" ;;
11 | v ) VERBOSE=1 ;;
12 | : ) echo "error: no arg supplied to $OPTARG option" ;; # <3>
13 | * ) # <4>
14 | echo "error: unknown option $OPTARG"
15 | echo " valid options are: aov"
16 | ;;
17 | esac
18 | done
19 | shift $((OPTIND -1)) # <5>
20 |
--------------------------------------------------------------------------------
/ch08/parselong.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # parselong.sh: Use getopts to parse these arguments, including long ones
3 | # Original Author & date: _bash Idioms_ 2022
4 | # bash Idioms filename: examples/ch08/parselong.sh
5 | #_________________________________________________________________________
6 | # Long arguments: --amode
7 | # and --outfile filename or --outfile=filename
8 |
9 | VERBOSE=':' # Default is off (no-op)
10 |
11 | while getopts ':-:ao:v' VAL ; do # <1>
12 | case $VAL in
13 | a ) AMODE=1 ;;
14 | o ) OFILE="$OPTARG" ;;
15 | v ) VERBOSE='echo' ;;
16 | #--------------------------------------------------------
17 | - ) # This section added to support long arguments # <2>
18 | case $OPTARG in
19 | amode ) AMODE=1 ;;
20 | outfile=* ) OFILE="${OPTARG#*=}" ;; # <3>
21 | outfile ) # <4>
22 | OFILE="${!OPTIND}" # <5>
23 | let OPTIND++ # <6>
24 | ;;
25 | verbose ) VERBOSE='echo' ;;
26 | * )
27 | echo "unknown long argument: $OPTARG"
28 | exit
29 | ;;
30 | esac
31 | ;;
32 | #--------------------------------------------------------
33 | : ) echo "error: no argument supplied" ;;
34 | * )
35 | echo "error: unknown option $OPTARG"
36 | echo " valid options are: aov"
37 | ;;
38 | esac
39 | done
40 | shift $((OPTIND -1))
41 |
--------------------------------------------------------------------------------
/ch08/parselonghelp.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # parselonghelp.sh: Use getopts to parse these arguments, including long & help
3 | # Original Author & date: _bash Idioms_ 2022
4 | # bash Idioms filename: examples/ch08/parselonghelp.sh
5 | #_________________________________________________________________________
6 | # Long arguments: --amode and --help
7 | # and --outfile filename or --outfile=filename
8 |
9 | PROGRAM=${0##*/} # bash version of `basename`
10 | VERSION="$PROGRAM v1.2.3"
11 |
12 | VERBOSE=':' # Default is off (no-op)
13 | DEBUG=':' # Default is off (no-op)
14 |
15 | function Display_Help {
16 | # Tab indents below, starting after the EoN (End-of-Note) line!
17 | cat <<-EoN
18 | This script does nothing but show help; a real script should be
19 | more exciting.
20 | usage: $PROGRAM (options)
21 |
22 | Options:
23 | -a | --amode = Enable "amode", default is off
24 | -d | --debug = Include debug output, default is off
25 | -h | --help = Show this help message and exit
26 | -o | --outfile = Send output to file instead of STDOUT
27 | -v | --verbose = Include verbose output, default is off
28 | -V | --version = Show the version and exit
29 |
30 | You can put more detail here if you need to.
31 | EoN
32 | # Tab indents above!
33 | # If we have this next line, the script will always exit after calling
34 | # Display_Help. You may or may not want that...you decide.
35 | # exit 1 # If you use this, remove the other exits after the call!
36 | } # end of function Display_Help
37 |
38 | while getopts ':-:adho:vV' VAL ; do
39 | case $VAL in
40 | # If you keep options in lexical order, they are easier to find and
41 | # you reduce the chances of a collision
42 | a ) AMODE=1 ;;
43 | d ) DEBUG='echo' ;;
44 | h ) Display_Help ; exit 1 ;; # We violated our style here
45 | o ) OFILE="$OPTARG" ;;
46 | v ) VERBOSE='echo' ;;
47 | V ) echo "$VERSION" && exit 0 ;; # We violated our style here too
48 | #--------------------------------------------------------
49 | -) # This section added to support long arguments
50 | case $OPTARG in
51 | amode ) AMODE=1 ;;
52 | debug ) DEBUG='echo' ;;
53 | help )
54 | Display_Help
55 | exit 1
56 | ;;
57 | outfile=* ) OFILE="${OPTARG#*=}" ;;
58 | outfile )
59 | OFILE="${!OPTIND}"
60 | let OPTIND++
61 | ;;
62 | verbose ) VERBOSE='echo' ;;
63 | version )
64 | echo "$VERSION"
65 | exit 0
66 | ;;
67 | * )
68 | echo "unknown long argument: $OPTARG"
69 | exit
70 | ;;
71 | esac
72 | ;;
73 | #--------------------------------------------------------
74 | : ) echo "error: no argument supplied" ;;
75 | * )
76 | echo "error: unknown option $OPTARG"
77 | echo " valid options are: aov"
78 | ;;
79 | esac
80 | done
81 | shift $((OPTIND -1))
82 |
83 | echo "Code for $0 goes here."
84 |
85 | $VERBOSE 'Example verbose message...'
86 | $DEBUG 'Example DEBUG message...'
87 |
88 | echo "End of $PROGRAM run."
89 |
--------------------------------------------------------------------------------
/ch09/fancy_mapfile.out:
--------------------------------------------------------------------------------
1 |
2 | Nodes to process: 10
3 | Batch size and count: 4 / 2
4 | Sleep seconds per node: 1
5 | Sleep seconds per batch: 1
6 |
7 | node 0: node0
8 | node 1: node1
9 | node 2: node2
10 | node 3: node3
11 | Completed 4 of 10 nodes; batch 1 of 2; sleeping for 1 seconds...
12 | node 4: node4
13 | node 5: node5
14 | node 6: node6
15 | node 7: node7
16 | Completed 8 of 10 nodes; batch 2 of 2; sleeping for 1 seconds...
17 | node 8: node8
18 | node 9: node9
19 |
--------------------------------------------------------------------------------
/ch09/fancy_mapfile.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # fancy_mapfile.sh: Fancy `mapfile` example
3 | # Original Author & date: _bash Idioms_ 2022
4 | # bash Idioms filename: examples/ch09/fancy_mapfile.sh
5 | #_________________________________________________________________________
6 | # Does not work on Zsh 5.4.2!
7 |
8 | HOSTS_FILE='/tmp/nodes.txt'
9 |
10 | # Create test file # <1>
11 | > $HOSTS_FILE
12 | for n in node{0..9}; do echo "$n" >> $HOSTS_FILE; done
13 |
14 | ### ADJUSTABLE VARIABLES
15 | #BATCH_SIZE=0 # Do the entire file at once (default); watch out for memory
16 | BATCH_SIZE=4
17 | SLEEP_SECS_PER_NODE=1 # Can set to 0
18 | SLEEP_SECS_PER_BATCH=1 # Set to zero if `BATCH_SIZE=0`!
19 |
20 | # Display runtime feedback to STDERR (so STDOUT can go into `tee` or a file)
21 | node_count="$(wc -l < $HOSTS_FILE)" # <2>
22 | batch_count="$(( node_count / BATCH_SIZE ))" # <3>
23 | echo '' 1>&2
24 | echo "Nodes to process: $node_count" 1>&2
25 | echo "Batch size and count: $BATCH_SIZE / $batch_count" 1>&2 # <4>
26 | echo "Sleep seconds per node: $SLEEP_SECS_PER_NODE" 1>&2
27 | echo "Sleep seconds per batch: $SLEEP_SECS_PER_BATCH" 1>&2
28 | echo '' 1>&2
29 |
30 | node_counter=0
31 | batch_counter=0
32 | # While we're reading data... && there is still data in $HOSTS_FILE
33 | while mapfile -t -n $BATCH_SIZE nodes && ((${#nodes[@]})); do
34 | for node in ${nodes[@]}; do # <5>
35 | echo "node $(( node_counter++ )): $node"
36 | sleep $SLEEP_SECS_PER_NODE
37 | done
38 | (( batch_counter++ ))
39 | # Don't get stuck here AFTER the last (partial) batch...
40 | [ "$node_counter" -lt "$node_count" ] && {
41 | # You can also use `mapfile -C Call_Back -c $BATCH_SIZE` for feedback but
42 | # it runs the callback up front too, so if you have a delay you'll
43 | # have to wait for that
44 | echo "Completed $node_counter of $node_count nodes;" \
45 | "batch $batch_counter of $batch_count;" \
46 | "sleeping for $SLEEP_SECS_PER_BATCH seconds..." 1>&2
47 | sleep $SLEEP_SECS_PER_BATCH
48 | }
49 | done < $HOSTS_FILE
50 |
--------------------------------------------------------------------------------
/ch09/fiddle-ifs.out:
--------------------------------------------------------------------------------
1 | Normal $IFS and `read` operation; split into words:
2 | $IFS before: $' \t\n'
3 | IFS during: $' \t\n' line = line1, w1 = wd1, w2 = wd2, w3 = wd3
4 | IFS during: $' \t\n' line = line2, w1 = wd1, w2 = wd2, w3 = wd3
5 | IFS during: $' \t\n' line = line3, w1 = wd1, w2 = wd2, w3 = wd3
6 | IFS after: $' \t\n'
7 |
8 | Temporary $IFS change for `read` inline:
9 | Words are NOT split, yet $IFS appears unchanged, because only the read
10 | line has the changed $IFS. We also shortened "line" to "ln" to make
11 | it fit a book page.
12 | IFS before: $' \t\n'
13 | IFS during: $' \t\n' ln = line1\ wd1\ wd2\ wd3, w1 = '', w2 = '', w3 = ''
14 | IFS during: $' \t\n' ln = line2\ wd1\ wd2\ wd3, w1 = '', w2 = '', w3 = ''
15 | IFS during: $' \t\n' ln = line3\ wd1\ wd2\ wd3, w1 = '', w2 = '', w3 = ''
16 | IFS after: $' \t\n'
17 |
18 | Temporary $IFS change for `read` in a function; NOT split, $IFS changed:
19 | IFS before: $' \t\n'
20 | IFS during: '' line = line1\ wd1\ wd2\ wd3, w1 = '', w2 = '', w3 = ''
21 | IFS during: '' line = line2\ wd1\ wd2\ wd3, w1 = '', w2 = '', w3 = ''
22 | IFS during: '' line = line3\ wd1\ wd2\ wd3, w1 = '', w2 = '', w3 = ''
23 | IFS after: $' \t\n'
24 |
25 | But you may not need to change $IFS at all... See `help read` and
26 | note the parts about:
27 | ...leftover words assigned to the last NAME
28 | ...[read line until] DELIM is read, rather than newline
29 | Normal $IFS and `read` operation using only 1 variable:
30 | IFS before: $' \t\n'
31 | IFS during: $' \t\n' line = line1\ wd1\ wd2\ wd3
32 | IFS during: $' \t\n' line = line2\ wd1\ wd2\ wd3
33 | IFS during: $' \t\n' line = line3\ wd1\ wd2\ wd3
34 | IFS after: $' \t\n'
35 |
--------------------------------------------------------------------------------
/ch09/fiddle-ifs.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # fiddle-ifs.sh: Fiddling with $IFS for fun and profit, to read files
3 | # Original Author & date: _bash Idioms_ 2022
4 | # bash Idioms filename: examples/ch09/fiddle-ifs.sh
5 | #_________________________________________________________________________
6 |
7 | # Create test file (not spelling out "word" to keep output < 80 columns)
8 | IFS_TEST_FILE='/tmp/ifs-test.txt'
9 | cat <<'EoF' > $IFS_TEST_FILE
10 | line1 wd1 wd2 wd3
11 | line2 wd1 wd2 wd3
12 | line3 wd1 wd2 wd3
13 | EoF
14 |
15 |
16 | #--------------------------------------------------------------------------
17 | echo 'Normal $IFS and `read` operation; split into words:'
18 | printf '$IFS before: %q\n' "$IFS"
19 | while read line w1 w2 w3; do
20 | printf 'IFS during: %q\tline = %q, w1 = %q, w2 = %q, w3 = %q\n' \
21 | "$IFS" "$line" "$w1" "$w2" "$w3"
22 | done < $IFS_TEST_FILE
23 | printf 'IFS after: %q\n' "$IFS"
24 |
25 |
26 | #--------------------------------------------------------------------------
27 | echo ''
28 | echo 'Temporary $IFS change for `read` inline:'
29 | echo 'Words are NOT split, yet $IFS appears unchanged, because only the read'
30 | echo 'line has the changed $IFS. We also shortened "line" to "ln" to make'
31 | echo 'it fit a book page.'
32 | printf 'IFS before: %q\n' "$IFS"
33 | while IFS='' read line w1 w2 w3; do
34 | printf 'IFS during: %q\tln = %q, w1 = %q, w2 = %q, w3 = %q\n' \
35 | "$IFS" "$line" "$w1" "$w2" "$w3"
36 | done < $IFS_TEST_FILE
37 | printf 'IFS after: %q\n' "$IFS"
38 |
39 |
40 | #--------------------------------------------------------------------------
41 | function Read_A_File {
42 | local file="$1"
43 | local IFS=''
44 |
45 | while read line w1 w2 w3; do
46 | printf 'IFS during: %q\tline = %q, w1 = %q, w2 = %q, w3 = %q\n' \
47 | "$IFS" "$line" "$w1" "$w2" "$w3"
48 | done < "$file"
49 | }
50 |
51 | echo ''
52 | echo 'Temporary $IFS change for `read` in a function; NOT split, $IFS changed:'
53 | printf 'IFS before: %q\n' "$IFS"
54 | Read_A_File $IFS_TEST_FILE
55 | printf 'IFS after: %q\n' "$IFS"
56 |
57 |
58 | #--------------------------------------------------------------------------
59 | echo ''
60 | echo 'But you may not need to change $IFS at all... See `help read` and'
61 | echo 'note the parts about:'
62 | echo ' ...leftover words assigned to the last NAME'
63 | echo ' ...[read line until] DELIM is read, rather than newline'
64 | echo 'Normal $IFS and `read` operation using only 1 variable:'
65 | printf 'IFS before: %q\n' "$IFS"
66 | while read line; do
67 | printf 'IFS during: %q\tline = %q\n' "$IFS" "$line"
68 | done < $IFS_TEST_FILE
69 | printf 'IFS after: %q\n' "$IFS"
70 |
--------------------------------------------------------------------------------
/ch09/shebang-bash.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -
2 | :
3 |
--------------------------------------------------------------------------------
/ch09/shebang-env.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | :
3 |
--------------------------------------------------------------------------------
/ch09/trivial_trap.out:
--------------------------------------------------------------------------------
1 | Starting script examples/ch09/trivial_trap.sh...
2 | Setting the trap...
3 | About to exit...
4 | Cleanup was triggered! Cleaning up...
5 |
--------------------------------------------------------------------------------
/ch09/trivial_trap.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # trivial_trap.sh: Trivial bash trap example
3 | # Original Author & date: _bash Idioms_ 2022
4 | # bash Idioms filename: examples/ch09/trivial_trap.sh
5 | #_________________________________________________________________________
6 | # Does not work on Zsh 5.4.2!
7 |
8 | function Cleanup {
9 | echo "$FUNCNAME was triggered! Cleaning up..."
10 | }
11 |
12 | echo "Starting script $0..."
13 |
14 | echo 'Setting the trap...'
15 | # Will call Cleanup on any of these 6 signals
16 | trap Cleanup ABRT EXIT HUP INT QUIT TERM
17 |
18 | echo 'About to exit...'
19 |
--------------------------------------------------------------------------------
/ch10/embedded-docs.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # embedded-docs.sh: Example of bash code with various kinds of embedded docs
3 | # Original Author & date: _bash Idioms_ 2022
4 | # bash Idioms filename: examples/ch10/embedded-docs.sh
5 | #_________________________________________________________________________
6 | # Does not work on Zsh 5.4.2!
7 |
8 | [[ "$1" == '--emit-docs' ]] && {
9 | # Use the Perl "range" operator to print only the lines BETWEEN the bash
10 | # here-document "DOCS" markers, excluding when we talk about this code
11 | # below in the docs themselves. See the output for more.
12 | perl -ne "s/^\t+//; print if m/DOCS'?\$/ .. m/^\s*'?DOCS'?\$/ \
13 | and not m/DOCS'?$/;" $0
14 | exit 0
15 | }
16 |
17 | echo 'Code, code, code... <2>'
18 | echo 'Code, code, code...'
19 | : << 'DOCS'
20 | === Docs for My Script <7>
21 |
22 | Ignore the callout in this title; it only makes sense in the output later.
23 |
24 | Docs can be Markdown, AsciiDoc, Textile, whatever. This block is generic markup.
25 |
26 | We've wrapped them using the no-op ':' operator and a here-doc, but you
27 | have to remember, it's not bash that's processing the here-docs, so using
28 | <<-DOC for indenting will not work. Not quoting your marker will not allow
29 | variable interpolation either, or rather, it will, but that won't affect your
30 | documentation output. So always quote your here-doc marker so your docs do
31 | not interfere with your script (e.g., via the backticks we'll use below).
32 |
33 | All of your docs could be grouped near the top of the file, like this,
34 | for discoverability. Or they could all be at the bottom, to stay grouped
35 | but out of the way of the code. Or they could be interspersed to stay near
36 | the relevant code. Do whatever makes sense to you and your team.
37 |
38 | DOCS
39 | echo 'More code, code, code... <3>'
40 | echo 'More code, code, code...'
41 | : << 'DOCS'
42 | =head1 POD Example <8>
43 |
44 | Ignore the callout in this title; it only makes sense in the output later.
45 |
46 | This block is Perl POD (Plain Old Documentation).
47 |
48 | If you use POD, you can then use `perldoc` and the various `pod2*` tools,
49 | like `pod2html`, that handle that. But you can't indent if using POD, or
50 | Perl won't see the markup, unless you preprocess the indent away before
51 | feeding the POD tools.
52 |
53 | And don't forget the `=cut` line!
54 |
55 | =cut
56 |
57 | DOCS
58 | echo 'Still more code, code, code... <4>'
59 | echo 'Still more code, code, code...'
60 | : << 'DOCS'
61 | Emitting Documentation <9>
62 | ----------------------
63 |
64 | Ignore the callout in this title; it only makes sense in the output later.
65 |
66 | This could be POD or markup, whatever.
67 |
68 | This section uses a TAB indented here-doc, just because we can, but we
69 | handle that in the Perl post processor, not via bash. :-/
70 |
71 | You should add a "handler" to your argument processing/help options to emit
72 | your docs. If you use POD, use those tools, but make sure they are installed!
73 | If you use some other markup, you have to extract it yourself somehow.
74 |
75 | We know this is a bash book, but this Perl one-liner using regular
76 | expressions and the range operator is really handy:
77 | perl -ne "s/^\t+//; print if m/DOCS'?\$/ .. m/^\s*'?DOCS'?\$/ \
78 | and not m/DOCS'?$/;" $0
79 |
80 | That will start printing lines when it matches the regular expression
81 | m/DOCS'?$/, and stop printing when it matches m/^\s*'?DOCS'?\$/, except that
82 | it won't print the actual line containing m/DOCS/ at all.
83 |
84 | Add that to your argument processing as "emit documentation."
85 |
86 | DOCS
87 |
88 | echo 'End of code... <5>'
89 |
90 | exit 0 # Unless we already exited >0 above
91 |
92 | : << 'DOCS'
93 | h2. More Docs AFTER the code <10>
94 |
95 | Ignore the callout in this title; it only makes sense in the output later.
96 |
97 | This block is back to generic markup. We do *not* recommend mixing and
98 | matching like we've done here! Pick a markup and some tools and stick to
99 | them. If in doubt, GitHub has made Markdown _very_ popular.
100 |
101 | Docs can just go *after* the end of the code. There's an argument for putting
102 | all the docs together in one place at the top or bottom of the script. This
103 | makes the bottom easy. On the other hand, there's an argument for keeping
104 | the docs _close_ to the relevant code, especially for functions. So...your call.
105 |
106 | But if this section only has an `exit 0` above it and is not wrapped in a
107 | bogo-here-doc, this might cause some syntax highlighters to be unhappy, and our
108 | Perl doc emitter will miss it, so you have to find a different way to
109 | display the docs.
110 | DOCS
111 |
--------------------------------------------------------------------------------
/ch10/run-embedded-docs.out:
--------------------------------------------------------------------------------
1 | ### Running looks like this (`./embedded-docs.sh`): <1>
2 | Code, code, code... <2>
3 | Code, code, code...
4 | More code, code, code... <3>
5 | More code, code, code...
6 | Still more code, code, code... <4>
7 | Still more code, code, code...
8 | End of code... <5>
9 |
10 |
11 | ### Emitting (un-rendered) docs looks like this (`./embedded-docs.sh --emit-docs`): <6>
12 | === Docs for My Script <7>
13 |
14 | Ignore the callout in this title; it only makes sense in the output later.
15 |
16 | Docs can be Markdown, AsciiDoc, Textile, whatever. This block is generic markup.
17 |
18 | We've wrapped them using the no-op ':' operator and a here-doc, but you
19 | have to remember, it's not bash that's processing the here-docs, so using
20 | <<-DOC for indenting will not work. Not quoting your marker will not allow
21 | variable interpolation either, or rather, it will, but that won't affect your
22 | documentation output. So always quote your here-doc marker so your docs do
23 | not interfere with your script (e.g., via the backticks we'll use below).
24 |
25 | All of your docs could be grouped near the top of the file, like this,
26 | for discoverability. Or they could all be at the bottom, to stay grouped
27 | but out of the way of the code. Or they could be interspersed to stay near
28 | the relevant code. Do whatever makes sense to you and your team.
29 |
30 | =head1 POD Example <8>
31 |
32 | Ignore the callout in this title; it only makes sense in the output later.
33 |
34 | This block is Perl POD (Plain Old Documentation).
35 |
36 | If you use POD, you can then use `perldoc` and the various `pod2*` tools,
37 | like `pod2html`, that handle that. But you can't indent if using POD, or
38 | Perl won't see the markup, unless you preprocess the indent away before
39 | feeding the POD tools.
40 |
41 | And don't forget the `=cut` line!
42 |
43 | =cut
44 |
45 | Emitting Documentation <9>
46 | ----------------------
47 |
48 | Ignore the callout in this title; it only makes sense in the output later.
49 |
50 | This could be POD or markup, whatever.
51 |
52 | This section uses a TAB indented here-doc, just because we can, but we
53 | handle that in the Perl post processor, not via bash. :-/
54 |
55 | You should add a "handler" to your argument processing/help options to emit
56 | your docs. If you use POD, use those tools, but make sure they are installed!
57 | If you use some other markup, you have to extract it yourself somehow.
58 |
59 | We know this is a bash book, but this Perl one-liner using regular
60 | expressions and the range operator is really handy:
61 | perl -ne "s/^\t+//; print if m/DOCS'?\$/ .. m/^\s*'?DOCS'?\$/ \
62 | and not m/DOCS'?$/;" $0
63 |
64 | That will start printing lines when it matches the regular expression
65 | m/DOCS'?$/, and stop printing when it matches m/^\s*'?DOCS'?\$/, except that
66 | it won't print the actual line containing m/DOCS/ at all.
67 |
68 | Add that to your argument processing as "emit documentation."
69 |
70 | h2. More Docs AFTER the code <10>
71 |
72 | Ignore the callout in this title; it only makes sense in the output later.
73 |
74 | This block is back to generic markup. We do *not* recommend mixing and
75 | matching like we've done here! Pick a markup and some tools and stick to
76 | them. If in doubt, GitHub has made Markdown _very_ popular.
77 |
78 | Docs can just go *after* the end of the code. There's an argument for putting
79 | all the docs together in one place at the top or bottom of the script. This
80 | makes the bottom easy. On the other hand, there's an argument for keeping
81 | the docs _close_ to the relevant code, especially for functions. So...your call.
82 |
83 | But if this section only has an `exit 0` above it and is not wrapped in a
84 | bogo-here-doc, this might cause some syntax highlighters to be unhappy, and our
85 | Perl doc emitter will miss it, so you have to find a different way to
86 | display the docs.
87 |
--------------------------------------------------------------------------------
/ch10/run-embedded-docs.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # run-embedded-docs.sh: Wrapper for automatic BI example code output updater
3 | # Original Author & date: _bash Idioms_ 2022
4 | # bash Idioms filename: examples/ch10/run-embedded-docs.sh
5 | #_________________________________________________________________________
6 |
7 | OUTPUT_INCLUDE_FILE='examples/ch10/embedded-docs.out'
8 |
9 | # We *could* send all STDOUT from this script to the output file like this:
10 | #exec > $OUTPUT_INCLUDE_FILE
11 | # We do not do that here because our "example code output updater" already
12 | # does itself, the way we want it to.
13 |
14 | echo '### Running looks like this (`./embedded-docs.sh`): <1>' # <1>
15 | examples/ch10/embedded-docs.sh
16 |
17 | echo -e "\n"
18 | echo '### Emitting (un-rendered) docs looks like this (`./embedded-docs.sh --emit-docs`): <6>' # <6>
19 | examples/ch10/embedded-docs.sh --emit-docs
20 |
--------------------------------------------------------------------------------
/ch10/select-ssh.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # select-ssh.sh: Create a menu from ~/.ssh/config to set ssh_target,
3 | # then SSH to it
4 | # Original Author & date: _bash Idioms_ 2022
5 | # bash Idioms filename: examples/ch10/select-ssh.sh
6 | #_________________________________________________________________________
7 |
8 | #ssh_config="$HOME/.ssh/config" # Real one
9 | # Replace the trailing 'select-ssh.sh' with 'ssh_config'
10 | ssh_config="${0%/*}/ssh_config" # Idioms test file
11 |
12 | PS3='SSH to> '
13 | select ssh_target in Exit \
14 | $(egrep -i '^Host \w+' "$ssh_config" | cut -d' ' -f2-); do
15 | case $REPLY in
16 | 1|q|x|quit|exit) exit 0
17 | ;;
18 | *) break
19 | ;;
20 | esac
21 | done
22 |
23 | # This is only an example, so echo what we would have done
24 | echo ssh $ssh_target
25 |
--------------------------------------------------------------------------------
/ch10/ssh_config:
--------------------------------------------------------------------------------
1 | # ssh_config: SAMPLE SSH config file to create a menu from ~/.ssh/config
2 | # Original Author & date: _bash Idioms_ 2022
3 | # bash Idioms filename: examples/ch10/ssh_config
4 | #_________________________________________________________________________
5 |
6 | Host git.atlas.oreilly.com
7 | User foo
8 |
9 | Host gitlab.com
10 | User bar
11 | IdentityFile ~/.ssh/id_gitlab
12 | IdentitiesOnly yes
13 |
14 | Host github.com
15 | User baz
16 | IdentityFile ~/.ssh/id_github
17 | IdentitiesOnly yes
18 |
19 | host mythtv-be01
20 | User myth
21 |
22 | host kitchen
23 | User shared
24 |
--------------------------------------------------------------------------------
/template.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -
2 | # Or possibly: #!/usr/bin/env bash
3 | # :
4 | # Original Author & date:
5 | # Current maintainer?
6 | # Copyright/License?
7 | # Where this code belongs? (Hosts, paths, etc.)
8 | # Project/repo?
9 | # Caveats/gotchas?
10 | # Usage? (Better to have `-h` and/or `--help` options!)
11 | # $URL$ # If using SVN
12 | ID='' # If using SVN
13 | #_________________________________________________________________________
14 | PROGRAM=${0##*/} # bash version of `basename`
15 |
16 | # Unofficial bash Strict Mode?
17 | #set -euo pipefail
18 | ### CAREFUL: IFS=$'\n\t'
19 |
20 | # GLOBAL and constant variables are in UPPER case
21 | LOG_DIR='/path/to/log/dir'
22 |
23 | ### Consider adding argument handling to YOUR template; see:
24 | # examples/ch08/parseit.sh
25 | # examples/ch08/parselong.sh
26 | # examples/ch08/parselonghelp.sh
27 |
28 | # Functions are in Mixed Case
29 | ###########################################################################
30 | # Define functions
31 |
32 | #--------------------------------------------------------------------------
33 | # Example function
34 | # Globals: none
35 | # Input: nothing
36 | # Output: nothing
37 | function Foo {
38 | local var1="$1"
39 | ...
40 | } # End of function foo
41 |
42 |
43 | #--------------------------------------------------------------------------
44 | # Another example function
45 | # Globals: none
46 | # Input: nothing
47 | # Output: nothing
48 | function Bar {
49 | local var1="$1"
50 | ...
51 | } # End of function bar
52 |
53 |
54 | ###########################################################################
55 | # Main
56 |
57 | # Code...
58 |
--------------------------------------------------------------------------------