#!/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__