123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346 |
- #!/bin/bash
-
- shellfu import pretty
-
- #
- # grep for (some) INIs
- #
- # inigrep is designed to read a particular simplistic dialect of
- # INI configuration files. In a sense, it can be considered as
- # a "grep for INIs", in that rather than parsing the file into
- # a typed memory structure for later access, it passes file each
- # time a query is done, and spits out relevant parts; treating
- # everything as text. Hence, it's not intended as replacement
- # for a full-blown configuration system but rather a quick & dirty
- # "swiss axe" for quick & dirty scripts.
- #
- # That's not to say that you cannot do things nicely; but don't
- # count on speed -- well since you're using bash you obviously
- # don't -- and compliance -- simple things are simple, but there
- # are a bit unusual pitfalls.
- #
- #
- # The format by examples
- # ----------------------
- #
- # The most basic example understood by inigrep is identical
- # to most INI formats:
- #
- # # Let's call this simple.ini
- #
- # [foo]
- # bar = baz
- # qux = quux
- #
- # [corge]
- # grault = graply
- #
- # Structure here is obvious: two sections named `foo` and `corge`,
- # the first one has two key/value pairs and the other has one pair.
- #
- # Getting values from this file is trivial:
- #
- # inigrep foo.bar simple.ini
- # inigrep foo.qux simple.ini
- # inigrep corge.grault simple.ini
- #
- # would list `baz`, `quux` and `graply`.
- #
- # This is where 80% of use cases are covered.
- #
- #
- # Multi-line
- # ----------
- #
- # Multi-line values are rather unusual but very simple:
- #
- # [lipsum]
- #
- # latin = Lorem ipsum dolor sit amet, consectetur adipiscing
- # latin = elit, sed do eiusmod tempor incididunt ut labore et
- # latin = dolore magna aliqua. Ut enim ad minim veniam, quis
- # latin = ...
- #
- # english = [32] But I must explain to you how all this mistaken
- # english = idea of denouncing of a pleasure and praising pain
- # english = was born and I will give you a complete account of
- # english = ...
- #
- # This file can be read as:
- #
- # inigrep lipsum.latin lipsum.ini
- # inigrep lipsum.english lipsum.ini
- #
- #
- # Exploration
- # -----------
- #
- # Other than basic value retrieval, inigrep allows you to look around
- # first. For example, to list all keypaths available in a file:
- #
- # inigrep -P simple.ini
- #
- # In case of simple.ini, this would print:
- #
- # foo.bar
- # foo.qux
- # corge.grault
- #
- # Similarly:
- #
- # inigrep -S simple.ini
- #
- # would list just the section names:
- #
- # foo
- # corge
- #
- # and
- #
- # inigrep -K foo simple.ini
- #
- # would list all keys from section 'foo'
- #
- # bar
- # qux
- #
-
-
- _inigrep__fltcmt() {
- #
- # Just strip comments
- #
- perl -ne '
- chomp;
- next if m/^[[:space:]]*[#;]/;
- s/ *#.*//;
- s/ *;.*//;
- print "$_\n" if $_;
- '
- }
-
-
- _inigrep__fltkey() {
- #
- # Filter values from `key = value pairs`
- #
- # In listing mode, prints *key names* instead
- #
- local line # every line
- local key # key name part
- local value # name part
- while IFS= read -r line; # ' key = val '
- do
- line="${line##*([[:space:]])}" # line='key = val '
- key="${line%%*([[:space:]])=*}" # key='key'
- value="${line##$key*([[:space:]])=}" # value=' val '
- $StrictMode || value="${value##*([[:space:]])}" # value='val'
- test "$key" = "$WantedKey" || continue
- $ListingMode || echos "$value"
- $ListingMode && echos "$line"
- done
- }
-
-
- _inigrep__fltsct() {
- #
- # Filter out only the wanted section body
- #
- # In listing mode prints also section name
- #
- local sct_ok=false # is this the section we want?
- local sct_name # wanted section name
- local line # current line
- while IFS= read -r line;
- do
- if [[ $line =~ ^[[:space:]]*\[[^]]*\].* ]]; # IOW: 'spaces[name]anything'
- then
- # hack out only the name between '[' and ']'
- sct_name=$line
- sct_name=${sct_name##*([[:space:]])[} # drop leading space
- sct_name=${sct_name%%]*} # and trailing anything
- if test "$sct_name" == "$WantedSection";
- then
- sct_ok=true; # our section
- $ListingMode && echos "[$sct_name]"; # inform lister
- else
- sct_ok=false
- fi
- continue
- else
- $sct_ok || continue
- $StrictMode || line="${line##*([[:space:]])}"
- echos "$line"
- fi
- done
- }
-
-
- _inigrep__fltlst() {
- #
- # Filter only section/keys/path names from the stream
- #
- local line="" # current line
- local key="__NOKEY__" # current key
- local sct="__NOSCT__" # current section
- while read -r line;
- do
- case "$line" in
- \[*\]) sct=${line#[}; sct=${sct%]}; key=__NOKEY__ ;;
- *=*) key="${line%%*([[:space:]])=*}" ;; # key='key'
- *) continue ;; # whatever
- esac
- case "$Engine:$sct:$key" in
- lspth:__NOSCT__:*) echos "$sct.$key" ;;
- lspth:*:__NOKEY__) continue ;;
- lspth:*:*) echos "$sct.$key" ;;
- lssct:__NOSCT__:*) echos "$sct" ;;
- lssct:*:__NOKEY__) echos "$sct" ;;
- lssct:*:*) continue ;;
- lskey:__NOSCT__:__NOKEY__) continue ;;
- lskey:__NOSCT__:*) echos "$key" ;;
- lskey:*:__NOKEY__) continue ;;
- lskey:*:*) echos "$key" ;;
- esac
- done
- }
-
-
- _inigrep__query() {
- #
- # Query INI stream composed of files at $@ or stdin
- #
- # Compose filter pipe of potential parts and run it.
- #
- local pipe="" # filter pipe
- local ListingMode=false # listing mode on?
- local StrictMode=false # strict mode on?
- local ifile # current input file
- local restore_shopt # shopt backup (executable code)
- case $Engine in
- raw) StrictMode=true ;;
- ls*) ListingMode=true ;;
- esac
- # which will come first? prefix all and strip the first prefix
- #
- $StrictMode || pipe+=" | _inigrep__fltcmt"
- test -n "$WantedSection" && pipe+=" | _inigrep__fltsct"
- test -n "$WantedKey" && pipe+=" | _inigrep__fltkey"
- $ListingMode && pipe+=" | _inigrep__fltlst"
- $StrictMode || pipe+=" | grep ."
- pipe="${pipe# | }"
- test -z "$pipe" && pipe="cat" # possible with raw mode and dot wildcard
- debug -v pipe
- # pray and run
- #
- restore_shopt=$(shopt -p extglob)
- shopt -s extglob
- for ifile in "$@";
- do
- case "$ifile" in
- -) eval "$pipe" ;;
- *) <"$ifile" eval "$pipe" ;;
- esac
- done
- $restore_shopt
- }
-
- _inigrep__validate_ks() {
- #
- # Check $WantedSection ans $WantedKey for invalid chars and warn
- #
- case $WantedKey in
- *\\*) warn "invalid char '\\' in key name: $WantedKey"; return 1 ;;
- *[*) warn "invalid char '[' in key name: $WantedKey"; return 1 ;;
- *=*) warn "invalid char '=' in key name: $WantedKey"; return 1 ;;
- esac
- case $WantedSection in
- *]*) warn "invalid char ']' in section name: $WantedSection"; return 1 ;;
- esac
- }
-
- inigrep() {
- #
- # Query INI file(s) for data
- #
- # Usage:
- #
- # inigrep [-1] [section].[key] [file]...
- # inigrep [-1] -e|--basic [section].[key] [file]...
- # inigrep [-1] -r|--raw [section].[key] [file]...
- # inigrep [-1] -K|--lskey section [file]...
- # inigrep [-1] -S|--lssct [file]...
- # inigrep [-1] -P|--lspth [file]...
- #
- # First form implies the basic engine.
- #
- # Option *-r* switches to raw mode, in which comments,
- # empty lines and whitespace are all preserved in applicable
- # contexts.
- #
- # Key *keypath* consists of section name and key name delimited
- # by dot. Note that keypath may contain dots but key may not.
- #
- # If *file* is not given or is a single dash, standard input
- # is read. Note that standard input is not duplicated in case
- # of multiple dashes.
- #
- # If suitable key is found at multiple lines, all values
- # are printed, which allows for creating multi-line values.
- # Providing *-1* argument, however, always prints only one
- # line.
- #
- # Options -K, -S and -P can be used for inspecting file structure;
- # -K needs an argument of section name and will list all keys from
- # that section. -S will list all sections and -P will list all
- # existing keypaths.
- #
- #
- # #### Examples ####
- #
- # Having INI file such as
- #
- # [foo]
- # bar=baz
- # quux=qux
- # quux=qux2
- # quux=qux3
- #
- # * `inigrep foo.bar ./file.ini` gives "bar".
- # * `inigrep foo.quux ./file.ini` gives three lines "qux", "qux2"
- # and "qux3".
- # * `inigrep -P ./file.ini` gives two lines "foo.bar" and "foo.quux".
- # * `inigrep -K foo ./file.ini` gives two lines "bar" and "quux".
- # * `inigrep -S ./file.ini` gives "foo".
- #
- local kpath="" # desired key path
- local one_line=false # limit to single line
- local Engine=ini # engine: ini,raw,lspth,lssct,lskey
- local WantedSection="" # desired section name (in case of listing)
- local WantedKey="" # desired key name
- while true; do case $1 in
- -1|--one-line) one_line=true; shift ;;
- -e|--basic) Engine=ini; kpath="$2"; shift; break ;;
- -r|--raw) Engine=raw; kpath="$2"; shift; break ;;
- -K|--lskey) Engine=lskey; kpath="$2."; shift; break ;;
- -P|--lspth) Engine=lspth; kpath="."; break ;;
- -S|--lssct) Engine=lssct; kpath="."; break ;;
- *) break ;;
- esac done
- test -n "$kpath" || kpath="$1" && shift
- test -z "$*" && set -- -
- debug -v one_line Engine kpath
- debug "\$*='$*'"
- WantedKey="${kpath##*.}"
- WantedSection="${kpath%.$WantedKey}"
- _inigrep__validate_ks || return 2
- debug -v WantedKey WantedSection
- if $one_line;
- then
- _inigrep__query "$@" | head -1
- else
- _inigrep__query "$@"
- fi
- }
-
- #shellfu module-version=__MKIT_PROJ_VERSION__
|