-
-
Save dualbus/7555479 to your computer and use it in GitHub Desktop.
INI file parser in bash
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
shopt -s extglob | |
function isValidIdentifier { | |
typeset identifier=$1 identifierPattern='+([[:alpha:]_])+([[:alnum:]_])' | |
[[ $identifier = $identifierPattern ]] | |
} | |
function getPairKey { | |
typeset token=$1 | |
printf %s "${token%%:*}" | |
} | |
function getPairValue { | |
typeset token=$1 | |
printf %s "${token#*:}" | |
} | |
function emitPair { | |
printf '%s:%s\0' "$1" "$2"; | |
} | |
function __ini__getStateHandler { | |
typeset state=$1 | |
printf '__ini__state_%s' "$state" | |
} | |
function __ini__state_base { | |
typeset character=$1 | |
case $character in | |
[[]|\"|\;) | |
emitPair 'clearBuffer';; | |
[=:]) : ;; | |
*) | |
emitPair 'buffer' \ | |
"$character";; | |
esac | |
case $character in | |
[[:blank:]]) | |
emitPair 'state' \ | |
horizontalSpace;; | |
[[:space:]]) | |
emitPair 'state' \ | |
'verticalSpace';; | |
[[]) | |
emitPair 'state' \ | |
'section';; | |
\") | |
emitPair 'state' \ | |
'quotedString';; | |
\;) | |
emitPair 'state' \ | |
'comment';; | |
[=:]) | |
emitPair 'state' \ | |
'base'; | |
emitPair 'pushToken' \ | |
'assignment:=';; | |
*) | |
emitPair 'state' \ | |
'string';; | |
esac | |
} | |
function __ini__state_section { | |
typeset character=$1 buffer=$2 | |
if [[ $character = ']' ]]; then | |
emitPair 'state' \ | |
'base' | |
emitPair 'pushToken' \ | |
"section:$buffer" | |
emitPair 'clearBuffer' | |
else | |
emitPair 'buffer' \ | |
"$character" | |
fi | |
} | |
function __ini__state_horizontalSpace { | |
typeset character=$1 buffer=$2 | |
if [[ $character = [[:blank:]] ]]; then | |
emitPair 'buffer' \ | |
"$character" | |
else | |
emitPair 'state' \ | |
'base' | |
emitPair 'pushToken' \ | |
"horizontalSpace:$buffer" | |
emitPair 'backtrack' | |
fi | |
} | |
function __ini__state_verticalSpace { | |
typeset character=$1 buffer=$2 | |
case $character in | |
[[:blank:]]) | |
emitPair 'state' \ | |
'horizontalSpace' | |
emitPair 'pushToken' \ | |
"verticalSpace:$buffer" | |
emitPair 'buffer' \ | |
"$character";; | |
[[:space:]]) | |
emitPair 'buffer' \ | |
"$character";; | |
*) | |
emitPair 'state' \ | |
'base' | |
emitPair 'pushToken' \ | |
"verticalSpace:$buffer" | |
emitPair 'backtrack';; | |
esac | |
} | |
function __ini__state_quotedString { | |
typeset character=$1 buffer=$2 | |
if [[ $character = \" ]]; then | |
emitPair 'state' \ | |
'base' | |
emitPair 'pushToken' \ | |
"quotedString:$buffer" | |
emitPair 'clearBuffer' | |
else | |
emitPair 'buffer' \ | |
"$character" | |
fi | |
} | |
function __ini__state_comment { | |
typeset character=$1 buffer=$2 | |
case $character in | |
[[:blank:]]) | |
emitPair 'buffer' \ | |
"$character";; | |
[[:space:]]) | |
emitPair 'state' \ | |
'base' | |
emitPair 'pushToken' \ | |
"comment:$buffer" | |
emitPair 'pushToken' \ | |
"verticalSpace:$buffer";; | |
*) | |
emitPair 'buffer' \ | |
"$character";; | |
esac | |
} | |
function __ini__state_string { | |
typeset character=$1 buffer=$2 | |
if [[ $character = [[:space:]\"\;:=] ]]; then | |
emitPair 'state' \ | |
'base' | |
emitPair 'pushToken' \ | |
"string:$buffer" | |
emitPair 'clearBuffer' | |
emitPair 'backtrack' | |
else | |
emitPair 'buffer' \ | |
"$character" | |
fi | |
} | |
function tokenizeIniStream { | |
typeset state=base | |
typeset line lengthOfLine charPosition buffer character pair | |
typeset -a tokenStream=() | |
while IFS= read -r line; do | |
lengthOfLine=${#line} | |
for ((charPosition = 0; charPosition <= lengthOfLine; charPosition++)); do | |
if ((charPosition == lengthOfLine)); then | |
character=$'\n' | |
else | |
character=${line:charPosition:1} | |
fi | |
while IFS= read -rd '' pair; do | |
case $(getPairKey "$pair") in | |
state) | |
state=$(getPairValue "$pair");; | |
pushToken) | |
tokenStream+=("$(getPairValue "$pair")");; | |
buffer) | |
buffer+=$(getPairValue "$pair");; | |
clearBuffer) | |
buffer=;; | |
backtrack) | |
((charPosition--));; | |
esac | |
done < <("$(__ini__getStateHandler "$state")" "$character" "$buffer") | |
done | |
done | |
"$@" -- "${tokenStream[@]}" | |
} | |
function __ini__removeCommentsFromTokenStream { | |
typeset inputToken | |
typeset -a outputTokenStream=() | |
for inputToken; do | |
[[ "$(getPairKey "$inputToken")" = "comment" ]] && continue | |
outputTokenStream+=("$inputToken") | |
done | |
printf '%s\0' "${outputTokenStream[@]}" | |
} | |
function __ini__removeExtraWhitespaceFromTokenStream { | |
typeset inputToken whitespaceTokenCount=0 | |
for inputToken; do | |
[[ "$(getPairKey "$inputToken")" = *"Space" ]] || break | |
((whitespaceTokenCount++)) | |
done | |
shift "$whitespaceTokenCount" | |
printf '%s\0' "$@" | |
} | |
function __ini__parseIniSection { | |
__ini__section=$(getPairValue "$1") | |
__ini__shift=1 | |
} | |
function __ini__isArrayInsertName { | |
typeset name=$1 identifier insertNamePattern='*\[+([[:digit:]])\]' | |
identifier=${name%\[*} | |
isValidIdentifier "$identifier" || return 1 | |
[[ $name = $insertNamePattern ]] | |
} | |
function __ini__arrayInsertName { | |
typeset name=$1 value=$2 | |
typeset identifier position | |
__ini__isArrayInsertName "$name" || return 1 | |
IFS='[]' read -r identifier position <<< "$name" | |
printf '%s[%d]=%q' "$identifier" "$index" "$value" | |
} | |
function __ini__isArrayPushName { | |
typeset name=$1 identifier pushNamePattern='*\[\]' | |
identifier=${name%\[*} | |
isValidIdentifier "$identifier" || return 1 | |
[[ $name = $pushNamePattern ]] | |
} | |
function __ini__arrayPushName { | |
typeset name=$1 value=$2 | |
typeset identifier | |
__ini__isArrayPushName "$name" || return 1 | |
identifier=${name%\[*} | |
printf '%s+=(%q)' "$identifier" "$value" | |
} | |
function __ini__declareVariable { | |
typeset prefix=$1 section=$2 name=$3 value=$4 | |
typeset identifier | |
if ! { | |
isValidIdentifier "$section" && \ | |
isValidIdentifier "$prefix" | |
}; then | |
return 1 | |
fi | |
printf -v format '%s__%s_%%s' "$prefix" "$section" | |
if __ini__isArrayInsertName "$name"; then | |
printf -v declaration "$format" \ | |
"$(__ini__arrayInsertName "$name" "$value")" | |
elif __ini__isArrayPushName "$name"; then | |
printf -v declaration "$format" \ | |
"$(__ini__arrayPushName "$name" "$value")" | |
elif isValidIdentifier "$name"; then | |
printf -v declaration "$format" \ | |
"$(printf '%s=%q' "$name" "$value")" | |
else | |
return 1 | |
fi | |
eval "$declaration" | |
} | |
function __ini__parseIniPair { | |
typeset declaration= | |
typeset tokenName=$(getPairValue "$1") tokenValue=; shift 1 | |
__ini__shift=0 | |
[[ "$(getPairKey "$1")" = "horizontalSpace" ]] && \ | |
{ shift 1; (( __ini__shift++ )); } | |
[[ "$(getPairKey "$1")" = "assignment" ]] || \ | |
return 1 | |
{ shift 1; (( __ini__shift++ )); } | |
while [[ "$(getPairKey "$1")" = "horizontalSpace" ]] && \ | |
(( $# )); do | |
{ shift 1; (( __ini__shift++ )); } | |
done | |
while [[ "$(getPairKey "$1")" != "verticalSpace" ]] && \ | |
(( $# )); do | |
if [[ "$(getPairKey "$1")" = "horizontalSpace" ]] && \ | |
[[ "$(getPairKey "$2")" = "verticalSpace" ]]; then | |
{ shift 1; (( __ini__shift++ )); } | |
break | |
fi | |
tokenValue+=$(getPairValue "$1") | |
{ shift 1; (( __ini__shift++ )); } | |
done | |
__ini__declareVariable "$__ini__prefix" "$__ini__section" \ | |
"$tokenName" "$tokenValue" | |
{ shift 1; (( __ini__shift++ )); } | |
} | |
function parseIniFile { | |
typeset prefix=$1 token= x=0 line arg_shift=0; shift 1 | |
typeset -a t=() tokenStream=() | |
__ini__prefix=$prefix | |
for arg; do | |
[[ $arg = -- ]] && ((arg_shift++)) | |
done | |
shift "$arg_shift" | |
t=("$@") tokenStream=() | |
while IFS= read -rd '' line; do | |
tokenStream+=("$line") | |
done < <(__ini__removeCommentsFromTokenStream "${t[@]}") | |
while ((${#tokenStream[@]} > 0)) && ((x++ < 50)); do | |
t=("${tokenStream[@]}") tokenStream=() | |
while IFS= read -rd '' line; do | |
tokenStream+=("$line") | |
done < <(__ini__removeExtraWhitespaceFromTokenStream "${t[@]}") | |
case $(getPairKey "$tokenStream") in | |
section) | |
__ini__shift=0 | |
__ini__parseIniSection "${tokenStream[@]}" | |
tokenStream=("${tokenStream[@]:__ini__shift}") | |
;; | |
string) | |
__ini__shift=0 | |
__ini__parseIniPair "${tokenStream[@]}" | |
tokenStream=("${tokenStream[@]:__ini__shift}") | |
;; | |
esac | |
done | |
} | |
function loadIniFile { | |
tokenizeIniStream parseIniFile "$@" | |
} | |
return 2>/dev/null |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment