Sample config file, /path/to/my/db.conf:
- defaults: ; defaults=$DASH_TOP
- port=5432
- username=dev
- encoding=utf8
---
- staging: ; staging=$DASH_TOP
+ "$defaults"
- password=abc123
- database=staging_db
- host=10.1.2.10
---
- production:
+ "$defaults"
- username=demo
- password=ADFASj1ADF814
- database=production_db
- host=production-db.example.com
---
= qa "$staging"
---
- users:
- pparker
- bwayne
- tstark
---
Here's a script showing how you can load and work with the configuration:
#!/bin/bash
set -eEu
source dash.sh
source /path/to/my/db.conf
drop_all_tables() ( # [env]
local env=${1:-staging}
local -n conf; -set conf=/$env
local pg_args=(
-h "${conf[host]}" -p "${conf[port]}"
-U "${conf[username]}"
"${conf[database]}"
)
export PGPASSWORD=${conf[password]}
local tables
tables=$(psql -At "${pg_args[@]}" <<<"
SELECT table_name FROM information_schema.tables WHERE table_schema = 'public';
")
# Assuming no space or strange characters in table names
for t in $tables; do
echo "DROP TABLE IF EXISTS $t CASCADE;"
done \
| psql "${pg_args[@]}"
)
read -r -p "I'm about to drop all tables in the staging db! Cool? (y/n) "
case $REPLY in
y|Y|yes) drop_all_tables staging;;
*) echo "Skipped!" ;;
esac
In short, Dash is a set of bash functions that help you compose arrays(indexed or associative), and work with the items nested in them easily.
Hope you find this tool useful. Pull requests, bug reports, and suggestions, are always welcome.
Yes, I'm aware there's a POSIX shell named, dash(the Debian Almquist shell).
- Introduction
- Creating Hash Nodes
- Adding String Items
- Creating Array Nodes
- Leaving the Current Node
- Copying and Aliasing Nodes
- Getting Items From Nodes
- Escaping Special Characters
- Traversing the Nodes
- Handling Errors
- Global Variables
In Dash you work with nodes, adding items to them and nesting nodes within nodes.
An item is either a string(value), or a node that is added to a node, under a name, which can be any string without the newline character.
There are two types of nodes: hash nodes(associative arrays) and array nodes( indexed arrays). Dash decides the type of node to create, based on the first argument.
At any moment, you are working under a current node, which will be the parent of the
items you specify with the - command.
A hash node is created like this:
- database:
Notice that it has a single argument to - that ends with a colon(:).
A hash node like this creates an associative array and makes it the current node.
In this case, the name of the node is database under its parent(which is also a
hash node, and is actually a nameless global/root node), and the value is the name
of the Bash associative array that Dash created for you.
String items can be added using the - command with name=value
arguments:
- name1=value1 name2=value2 name3
Here because the first argument has an unescaped = in it, a name=value
argument will add the value under the name in the current hash node.
Moreover, if name is omitted then the value will be used as the name.
(second or later arguments can also omit the = when omitting name).
Alternatively, you can add implicitly named/indexed items:
- this is value 1 # index 0
- this is value 2 # index 1
In this case, because the first argument has no = nor : at the end, it's
considered an indexed item. The current max index + 1 will be used as the name
of the item. Index in each hash node starts from 0.
Note that you can mix indexed and named string items in a hash node. For example:
- name1=value1
- value2 # index 0
- name3=value3
- value4 # index 1
There are two ways to create an indexed array under a hash node:
The first way does it in one - command, and doesn't start a new context(i.e.,
doesn't make it the current node):
- fruits: apple banana coconut
This creates an indexed array with three values(apple, banana and coconut)
under the name, fruits, in the current node.
Notice that the syntax is similar to creating a hash node, except that there are more arguments, which are taken as the members of the array, after the first argument.
The second way is exactly the same as a hash node:
- fruits:
- apple # index 0
- banana # index 1
- coconut # index 2
- : # index 3
- red # index 0 under index 3 of fruits
- yello # index 1 under index 3 of fruits
- green # index 2 under index 3 of fruits
As long as you don't add any named items (name=value items or named nodes)
under a hash node, Dash will automatically convert it to an array node upon
leaving the hash node context.
NOTE:
Incidentally,
- name: valueis similar to- name=valuebut different in that the former creates an one element indexed array, holdingvalue, undernamein the current node; while the later directly putsvalueundernamein the current node. Although, as we'll see later, it might not matter, depends on how you get the value, because referencing an indexed array element without an explicit index is equivalent to referencing its first element.
Whenever you create a node with the - command, Dash automatically makes it the
current node. The --- command leaves the current node and makes the previous
current node the current current node.
There is also a -cd item-path command that allows you to make any node the current node:
- node1: # enters node1
- node2: # in node2
- name1=value1
--- # back to node1
- node3: # enters node3
- item1
- item2
-cd /node2; - name2=value2 # go directly to node2 and add one more item
-cd /node3; - item3 # go directly to node3 and add another item
-cd /
There are three ways to get to the items you've created with Dash.
-
-catitem-path ...Prints the value of an item to stdout. If item-path refers to a node, then the name of the idexed/associative array representing that node is printed.
-
-setvar-name=item-path ...Similar to
-cat, but instead of printing to stdout, the value of the node or item referred to by item_path is assigned to the var-name variable. Note that, to bind a variable to a node, you can declare it withdeclare -nfirst. For example:- path: - to: - something=somevalue - the: - node: - name1=value1 -cd / declare -n mynode -set something=/path/to/something mynode=/path/to/the/node mynode[name2]=123 echo "$something" # prints 'somevalue' -cat /path/to/the/node/name2 # prints '123'Notice that we use
/to separate items in a path. It's also possible to use another character as the separator with the-soption, which should be available to all dash commands taking an item path. For example:-cat -s . .path.to.the.node.name1Moreover, you can use relative path by omitting the leading path separator, and in which case, the path will be relative to the current node.
-
-do() { ... }; -withvar-name=item-path ...You can declare a function named,
-do, to work with the items and nodes you want, and then immediately calls it via-with. When calling,-with, specifyvar1=path1 var2=path2 ..., and Dash will make the item atpath1available as$var1, and so on. If the item referred to by a path is actually a node, then the array representing that node will be bound to the var. Example:- node1: - item 1 - item 2 --- - node2: - item 2-1 - node3: - name1: value1 - name2=value2 --- --- -do() { node1[0]=111 echo "$item" # prints 'item 2-1' echo "$name1" # same as ${name1[0]}, prints 'value1' node3[name2]=222 }; -with item=/node2/0 \ node3=/node2/node3 \ name1=/node2/node3/name1 \ node1 # same as 'node1=node1' -cat /node1/0 # prints '111' -cat /node3/name2 # prints '222'
There are two characters that are considered special in the first argument
to the - command. In precedence, they are:
- The first unescaped
=character. - The last character that is an unescaped
:.
The two special characters can be escaped with \(which needs to be esacped
or quoted in bash first), and \ can be escaped with itself(i.e., \\ becomes
\, but only before a special character).
Let's see some examples:
- name1=value1 # '=' unescaped, so this is a named string item
- name1\\=value1 # '=' escaped with '\', so this becomes an indexed string item
- name1: value1 # ':' unescaped, this is an array with an element, 'value1'
- "name1: value1" # an indexed string item, 'name1: value1'
- name1\\: # ':' escaped with '\', so this is an indexed string item, 'name1:'
- name1=value1: # a named string item, name is 'name1' and value is 'value1:'
- 'name1\=value1:' # '=' is escaped, so this becomes a node due to the ':' at the end.
- 'name1=value1\:' # a named string item, value is 'value1\:'
# a named string item. name is '\name1=\' and value is 'value\\'
- '\\name1\=\\=value\\'
Here's how it works.
The - command scan its first argument for the special characters from left to right,
collapsing double \'s into single \'s until an unescaped special character is
determined. Then, the rest of the argument is then taken literally as is, without
any further processing. If, from the first argument, it's determined that we are
creating a node or an indexed item, then we're done. Otherwise, it's dealing with
a named string item. Then, the same escape processing logic is applied to the rest
of name=value arguments.
When creating a node, it's possible to, instead, reference an already created node under the new name, or copy the contents and children an existing node to the new node. For example:
- a node: ; HERE=$DASH_TOP # save the path of the current node
- name1=value1
- name2=value2
---
- another node:
= a-node "$HERE" # and reference it here under a new name "a-node"
# as a child of "another node".
---
- third:
+ "$HERE" # copy its children here
- name1=111
---
-cd "/another node/a-node"
- name2=222
-cd /
-cat "/a node/name1" # prints value1
-cat "/a node/name2" # prints 222
-cat "/third/name1" # prints 111
FIXME
Each dash command returns a non-zero status if there's an error.
The last error message is stored in the DASH_ERROR global variable.
Dash should work with or without set -eu
| Variable | Description |
|---|---|
| DASH_TOP | Path to the current node. Expansion of this variable should always be quoted. |
| DASH_NODE | Name of the Bash array representing the current node. |
| DASH_ROOT | The array representing the global/nameless root node in Dash. |
| DASH_ERROR | The last Dash error message. |