├── .gitignore ├── README ├── build └── extract_readme.sh ├── ini_parser.sh └── test ├── t1.ini └── t2.ini /.gitignore: -------------------------------------------------------------------------------- 1 | .hg 2 | *.swp 3 | *~ 4 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | 2 | Introduction 3 | ------------ 4 | 5 | Functions for parsing INI-style file natively in bash. 6 | 7 | The INI file format supports the following features: 8 | 9 | Sections: [section] 10 | Properties: name=value 11 | Comments: ; comment 12 | # comment 13 | 14 | Blank lines and trailing writespace are ignored as is whitespace around 15 | the '=' - ie. 16 | 17 | name = value 18 | 19 | is equivalent to 20 | 21 | name=value 22 | 23 | Whitespace and quotes within a value are preserved (though values don't 24 | in general need to be quoted and will not be subject to shell parameter 25 | splitting) 26 | 27 | Values can be continuted onto subsequent lines if these are prefixed with 28 | whitespace - ie. 29 | 30 | name=line1 31 | line2 32 | 33 | is equivalent to: 34 | 35 | name = line1 line2 36 | 37 | Properties are stored in the global _CONFIG array as ( k1 v1 k2 v2 ... ) with 38 | keys in the format "
." (properties without an associated 39 | section are stored as ".". In most cases this is transparent as the 40 | list/get commands can be used to query the data 41 | 42 | The functionality is more or less equivalent to the Python ConfigParser 43 | module with the following exceptions 44 | 45 | - Properties are allowed outside sections 46 | - Multi-line properties are joined with ' ' rather than '\n' (due to shell 47 | quoting issues) 48 | 49 | Usage 50 | ----- 51 | 52 | Given the following ini file (test.ini) - 53 | 54 | ; A test ini file 55 | global = a global value 56 | [section1] 57 | abc = def ; a comment 58 | ghi = jkl 59 | [section2] 60 | xyz = abc ; extends over two lines 61 | def 62 | 63 | Parse config file - 64 | 65 | $ parseIniFile < test/t2.ini 66 | 67 | $ listKeys 68 | .global 69 | section1.abc 70 | section1.ghi 71 | section2.xyz 72 | 73 | $ listAll 74 | .global a global value 75 | section1.abc def 76 | section1.ghi jkl 77 | section2.xyz abc def 78 | 79 | $ listSection section1 80 | section1.abc def 81 | section1.ghi jkl 82 | 83 | $ getProperty global 84 | a global value 85 | 86 | $ getProperty section2.xyz 87 | abc def 88 | 89 | $ getPropertyVar XYZ section2.xyz && echo OK 90 | OK 91 | 92 | $ echo ">${XYZ}<" 93 | >abc def< 94 | 95 | Commands 96 | -------- 97 | 98 | parseIniFile < file 99 | 100 | Parse ini file (reads from stdin) and saves data to global _CONFIG var 101 | 102 | listKeys 103 | 104 | List keys present in config file in format "
." 105 | 106 | listAll 107 | 108 | List keys and data in format "
. " 109 | 110 | listSection
111 | 112 | List keys and data for given section (sepcified as $1) in format 113 | " " 114 | 115 | getProperty [name|section.name] 116 | 117 | Print value for given property (sepcified as $1) 118 | Properties without a section can be queried directly as 119 | "name" (rather than ".name") 120 | 121 | Returns 0 (true) if property found otherwise 1 (false) 122 | 123 | getPropertyVar [name|section.name] 124 | 125 | Save value for given property (sepcified as $2) 126 | into shell variable (specified as $1) 127 | Properties without a section can be queried directly as 128 | "name" (rather than ".name") 129 | 130 | Returns 0 (true) if property found otherwise 1 (false) 131 | 132 | 133 | -------------------------------------------------------------------------------- /build/extract_readme.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | sed -n -e '/^#!/d' -e '/^#$/s/#//p' -e '/^# /s/# //p' < ini_parser.sh > README 4 | -------------------------------------------------------------------------------- /ini_parser.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Introduction 4 | # ------------ 5 | # 6 | # Functions for parsing INI-style file natively in bash. 7 | # 8 | # The INI file format supports the following features: 9 | # 10 | # Sections: [section] 11 | # Properties: name=value 12 | # Comments: ; comment 13 | # # comment 14 | # 15 | # Blank lines and trailing writespace are ignored as is whitespace around 16 | # the '=' - ie. 17 | # 18 | # name = value 19 | # 20 | # is equivalent to 21 | # 22 | # name=value 23 | # 24 | # Whitespace and quotes within a value are preserved (though values don't 25 | # in general need to be quoted and will not be subject to shell parameter 26 | # splitting) 27 | # 28 | # Values can be continuted onto subsequent lines if these are prefixed with 29 | # whitespace - ie. 30 | # 31 | # name=line1 32 | # line2 33 | # 34 | # is equivalent to: 35 | # 36 | # name = line1 line2 37 | # 38 | # Properties are stored in the global _CONFIG array as ( k1 v1 k2 v2 ... ) with 39 | # keys in the format "
." (properties without an associated 40 | # section are stored as ".". In most cases this is transparent as the 41 | # list/get commands can be used to query the data 42 | # 43 | # The functionality is more or less equivalent to the Python ConfigParser 44 | # module with the following exceptions 45 | # 46 | # - Properties are allowed outside sections 47 | # - Multi-line properties are joined with ' ' rather than '\n' (due to shell 48 | # quoting issues) 49 | # 50 | # Usage 51 | # ----- 52 | # 53 | # Given the following ini file (test.ini) - 54 | # 55 | # ; A test ini file 56 | # global = a global value 57 | # [section1] 58 | # abc = def ; a comment 59 | # ghi = jkl 60 | # [section2] 61 | # xyz = abc ; extends over two lines 62 | # def 63 | # 64 | # Parse config file - 65 | # 66 | # $ parseIniFile < test/t2.ini 67 | # 68 | # $ listKeys 69 | # .global 70 | # section1.abc 71 | # section1.ghi 72 | # section2.xyz 73 | # 74 | # $ listAll 75 | # .global a global value 76 | # section1.abc def 77 | # section1.ghi jkl 78 | # section2.xyz abc def 79 | # 80 | # $ listSection section1 81 | # section1.abc def 82 | # section1.ghi jkl 83 | # 84 | # $ getProperty global 85 | # a global value 86 | # 87 | # $ getProperty section2.xyz 88 | # abc def 89 | # 90 | # $ getPropertyVar XYZ section2.xyz && echo OK 91 | # OK 92 | # 93 | # $ echo ">${XYZ}<" 94 | # >abc def< 95 | # 96 | 97 | shopt -s extglob 98 | 99 | _CONFIG=() 100 | 101 | # Commands 102 | # -------- 103 | # 104 | # parseIniFile < file 105 | # 106 | # Parse ini file (reads from stdin) and saves data to global _CONFIG var 107 | # 108 | 109 | function parseIniFile() { 110 | 111 | local LINE="" 112 | local SECTION="" 113 | local KEY="" 114 | local VALUE="" 115 | 116 | local IFS="" 117 | 118 | while read LINE 119 | do 120 | LINE=${LINE%%[;#]*} # Strip comments 121 | LINE=${LINE%%*( )} # Strip trailing whitespace 122 | 123 | if [[ -n $KEY && $LINE =~ ^[[:space:]]+(.+) ]] # Continuation - append value 124 | then 125 | VALUE+=" ${BASH_REMATCH[1]}" 126 | else 127 | if [[ -n $KEY ]] # No continuation 128 | then 129 | _CONFIG=(${_CONFIG[@]} "${SECTION}.${KEY}" "${VALUE}") 130 | KEY="" 131 | VALUE="" 132 | fi 133 | 134 | if [[ $LINE =~ ^\[([[:alnum:]]+)\] ]] # Section 135 | then 136 | SECTION=${BASH_REMATCH[1]} 137 | KEY="" 138 | elif [[ $LINE =~ ^([^[:space:]]+)[[:space:]]*=[[:space:]]*(.+) ]] # Property 139 | then 140 | KEY=${BASH_REMATCH[1]} 141 | VALUE="${BASH_REMATCH[2]}" 142 | fi 143 | fi 144 | done 145 | 146 | if [[ -n $KEY ]] 147 | then 148 | _CONFIG=(${_CONFIG[@]} "${SECTION}.${KEY}" "${VALUE}") 149 | fi 150 | } 151 | 152 | # listKeys 153 | # 154 | # List keys present in config file in format "
." 155 | # 156 | 157 | function listKeys() { 158 | local -i i 159 | for ((i=0; i<${#_CONFIG[@]}; i+=2)) 160 | do 161 | echo ${_CONFIG[i]} 162 | done 163 | } 164 | 165 | # listAll 166 | # 167 | # List keys and data in format "
. " 168 | # 169 | 170 | function listAll() { 171 | local -i i 172 | for ((i=0; i<${#_CONFIG[@]}; i+=2)) 173 | do 174 | echo ${_CONFIG[i]} ${_CONFIG[((i+1))]} 175 | done 176 | } 177 | 178 | # listSection
179 | # 180 | # List keys and data for given section (sepcified as $1) in format 181 | # " " 182 | # 183 | 184 | function listSection() { 185 | local -i i 186 | local SECTION=$1 187 | for ((i=0; i<${#_CONFIG[@]}; i+=2)) 188 | do 189 | if [[ ${_CONFIG[$i]} =~ ^${SECTION}\.(.+) ]] 190 | then 191 | echo ${_CONFIG[i]} ${_CONFIG[((i+1))]} 192 | fi 193 | done 194 | } 195 | 196 | # getProperty [name|section.name] 197 | # 198 | # Print value for given property (sepcified as $1) 199 | # Properties without a section can be queried directly as 200 | # "name" (rather than ".name") 201 | # 202 | # Returns 0 (true) if property found otherwise 1 (false) 203 | # 204 | 205 | function getProperty() { 206 | local -i i 207 | local KEY=$1 208 | for ((i=0; i<${#_CONFIG[@]}; i+=2)) 209 | do 210 | if [[ ${_CONFIG[$i]} =~ ^\.?${KEY} ]] 211 | then 212 | echo ${_CONFIG[((i+1))]} 213 | return 0 214 | fi 215 | done 216 | return 1 217 | } 218 | 219 | # getPropertyVar [name|section.name] 220 | # 221 | # Save value for given property (sepcified as $2) 222 | # into shell variable (specified as $1) 223 | # Properties without a section can be queried directly as 224 | # "name" (rather than ".name") 225 | # 226 | # Returns 0 (true) if property found otherwise 1 (false) 227 | # 228 | 229 | function getPropertyVar() { 230 | local -i i 231 | local VAR=$1 232 | local KEY=$2 233 | for ((i=0; i<${#_CONFIG[@]}; i+=2)) 234 | do 235 | if [[ ${_CONFIG[$i]} =~ ^\.?${KEY} ]] 236 | then 237 | local VAL=${_CONFIG[((i+1))]} 238 | eval ${VAR}="\${VAL}" 239 | return 0 240 | fi 241 | done 242 | return 1 243 | } 244 | 245 | function testIniParser() { 246 | echo $ parseIniFile \< test/t2.ini 247 | parseIniFile < test/t2.ini 248 | echo $ listKeys 249 | listKeys 250 | echo $ listAll 251 | listAll 252 | echo $ listSection section1 253 | listSection section1 254 | echo $ getProperty global 255 | getProperty global 256 | echo $ getProperty section2.xyz 257 | getProperty section2.xyz 258 | echo $ getPropertyVar XYZ section2.xyz \&\& echo OK 259 | getPropertyVar XYZ section2.xyz && echo OK 260 | echo $ echo ">\${XYZ}<" 261 | echo ">${XYZ}<" 262 | } 263 | 264 | -------------------------------------------------------------------------------- /test/t1.ini: -------------------------------------------------------------------------------- 1 | # last modified 1 April 2001 by John Doe 2 | default = Something 3 | 4 | [owner] 5 | name=John Doe 6 | organization=Acme Widgets Inc. 7 | 8 | [database] 9 | server=192.0.2.62 ; use IP address in case network name resolution is not working 10 | port=143 11 | file = "payroll.dat" 12 | stuff = some "stuff" here 13 | which ${xxx} is quite long 14 | banmbdnabamd 15 | [stuff] 16 | trailing_whitespace = hdsjkhksd 17 | tab = hello there 18 | xxxx = abc def 19 | jfdkl 20 | jfdkl 21 | jfdkl 22 | -------------------------------------------------------------------------------- /test/t2.ini: -------------------------------------------------------------------------------- 1 | ; A test ini file 2 | global = a global value 3 | [section1] 4 | abc = def ; a comment 5 | ghi = jkl 6 | [section2] 7 | xyz = abc ; extends over two lines 8 | def 9 | --------------------------------------------------------------------------------