Skip to content

Instantly share code, notes, and snippets.

@dualbus
Created November 20, 2013 00:48
Show Gist options
  • Save dualbus/7555479 to your computer and use it in GitHub Desktop.
Save dualbus/7555479 to your computer and use it in GitHub Desktop.
INI file parser in bash
#!/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