How do I grab an INI value within a shell script?
Asked Answered
F

34

142

I have a parameters.ini file, such as:

[parameters.ini]
    database_user    = user
    database_version = 20110611142248

I want to read in and use the database version specified in the parameters.ini file from within a bash shell script so I can process it.

#!/bin/sh    
# Need to get database version from parameters.ini file to use in script    
php app/console doctrine:migrations:migrate $DATABASE_VERSION

How would I do this?

Feuillant answered 11/6, 2011 at 22:7 Comment(3)
Does any of these answers respect sections at all?Irrupt
Note that any script that starts with #!/bin/sh is a POSIX sh script, not a bash script. That's an important distinction, because sh is missing features like arrays and maps (which bash calls "associative arrays") that are very useful in constructing this kind of things.Viscountcy
I would write a small PHP script that extracts the value from the ini file (using parse_ini_file() and prints the desired value. The solutions using sed, awk or any other shell utility are not reliable because they process lines and do not understand the structure of an ini file.Nusku
T
104

How about grepping for that line then using awk

version=$(awk -F "=" '/database_version/ {print $2}' parameters.ini)
Taluk answered 11/6, 2011 at 22:15 Comment(10)
This will include spaces after '='.Corell
To trim spaces, add | tr -d ' ' at the end.Vander
This is not really a good solution. Think of having 2 [parameters.ini] sections with each having a ' database_version' variable. You get the value twice then.Kirin
yes please consider a specialized ini parser like crudini, as there are many edge cases not handled by the aboveBisayas
Still useful and quicker for basic ini files.Corny
Trims spaces, ignores comments: awk -F "=" '/^email/ {gsub(/[ \t]/, "", $2); print $2}' parameters.iniStaghound
Good solution. I would suggest using boundaries like /^database_version=/ or /\<database_version\>/ to exclude keys like database_version_major etc.Patella
the code in the answer ignoring the section and will return a value along with the spacesCartagena
cannot differentiate path1 and path10Adkisson
Use /\<database_version\>/ to avoid evaluating other keys including this name, e.g. primary_database_version or database_version_id.Doubletongued
V
86

You can use bash native parser to interpret ini values, by:

$ source <(grep = file.ini)

Sample file:

[section-a]
  var1=value1
  var2=value2
  IPS=( "1.2.3.4" "1.2.3.5" )

To access variables, you simply printing them: echo $var1. You may also use arrays as shown above (echo ${IPS[@]}).

If you only want a single value just grep for it:

source <(grep var1 file.ini)

For the demo, check this recording at asciinema.

It is simple as you don't need for any external library to parse the data, but it comes with some disadvantages. For example:

  • If you have spaces between = (variable name and value), then you've to trim the spaces first, e.g.

      $ source <(grep = file.ini | sed 's/ *= */=/g')
    

    Or if you don't care about the spaces (including in the middle), use:

      $ source <(grep = file.ini | tr -d ' ')
    
  • To support ; comments, replace them with #:

      $ sed "s/;/#/g" foo.ini | source /dev/stdin
    
  • The sections aren't supported (e.g. if you've [section-name], then you've to filter it out as shown above, e.g. grep =), the same for other unexpected errors.

    If you need to read specific value under specific section, use grep -A, sed, awk or ex).

    E.g.

      source <(grep = <(grep -A5 '\[section-b\]' file.ini))
    

    Note: Where -A5 is the number of rows to read in the section. Replace source with cat to debug.

  • If you've got any parsing errors, ignore them by adding: 2>/dev/null

See also:

Vander answered 1/3, 2015 at 14:59 Comment(4)
but... source <(grep = <(grep -A5 '\[section-b\]' file.ini)) this will not work for it: [sec a] a=1 b=2 c=3 [sec b] a=2 b=3 [sec c] a=0. where there is no definite rule with linesClarethaclaretta
I tried to use source, but when I echo the $var1 it returns nothing. Why?Cavanagh
@A.Gh I'm not sure, works for me. Make sure you're using Bash shell. See: asciinema.org/a/306481Vander
This would have been elegant, but failed to get it to work in OS X (Catalina). It works from command prompt in zsh (current default shell), but once I put it into a script, I get the error syntax error near unexpected token '('. With bash, it silently fails both from prompt and script.Tan
F
58

Sed one-liner, that takes sections into account. Example file:

[section1]
param1=123
param2=345
param3=678

[section2]
param1=abc
param2=def
param3=ghi

[section3]
param1=000
param2=111
param3=222

Say you want param2 from section2. Run the following:

sed -nr "/^\[section2\]/ { :l /^param2[ ]*=/ { s/[^=]*=[ ]*//; p; q;}; n; b l;}" ./file.ini

will give you

def
Faviolafavonian answered 24/11, 2016 at 4:26 Comment(5)
sed -nr "/^\[SECTION2\]/ { :l /^\s*[^#].*/ p; n; /^\[/ q; b l; }" file.conf # to get whole section without comments for a .conf style file with [SECTION2] and # hash-style comment lines. Then grep for paramname if you just want one parameter.Timm
better use sed range addresses than read next lines: "/^\[section2\]/,/^\[/{...}"Equidistant
if on a mac: brew install gnu-sed and then use gsed (otherwise: sed: illegal option -- r)Dillard
Can anyone please explain how the sed -nr "/^\[SECTION2\]/ { :l /^\s*[^#].*/ p; n; /^\[/ q; b l; }" expression works? thank youPushball
adding up on @basin: sed -nr '/^\[section2\]/,/^\[/{/^param2\s*=/{s/[^=]+\s*=\s*//;L}}'Imposing
I
37

Bash does not provide a parser for these files. Obviously you can use an awk command or a couple of sed calls, but if you are bash-priest and don't want to use any other shell, then you can try the following obscure code:

#!/usr/bin/env bash
cfg_parser ()
{
    ini="$(<$1)"                # read the file
    ini="${ini//[/\[}"          # escape [
    ini="${ini//]/\]}"          # escape ]
    IFS=$'\n' && ini=( ${ini} ) # convert to line-array
    ini=( ${ini[*]//;*/} )      # remove comments with ;
    ini=( ${ini[*]/\    =/=} )  # remove tabs before =
    ini=( ${ini[*]/=\   /=} )   # remove tabs after =
    ini=( ${ini[*]/\ =\ /=} )   # remove anything with a space around =
    ini=( ${ini[*]/#\\[/\}$'\n'cfg.section.} ) # set section prefix
    ini=( ${ini[*]/%\\]/ \(} )    # convert text2function (1)
    ini=( ${ini[*]/=/=\( } )    # convert item to array
    ini=( ${ini[*]/%/ \)} )     # close array parenthesis
    ini=( ${ini[*]/%\\ \)/ \\} ) # the multiline trick
    ini=( ${ini[*]/%\( \)/\(\) \{} ) # convert text2function (2)
    ini=( ${ini[*]/%\} \)/\}} ) # remove extra parenthesis
    ini[0]="" # remove first element
    ini[${#ini[*]} + 1]='}'    # add the last brace
    eval "$(echo "${ini[*]}")" # eval the result
}

cfg_writer ()
{
    IFS=' '$'\n'
    fun="$(declare -F)"
    fun="${fun//declare -f/}"
    for f in $fun; do
        [ "${f#cfg.section}" == "${f}" ] && continue
        item="$(declare -f ${f})"
        item="${item##*\{}"
        item="${item%\}}"
        item="${item//=*;/}"
        vars="${item//=*/}"
        eval $f
        echo "[${f#cfg.section.}]"
        for var in $vars; do
            echo $var=\"${!var}\"
        done
    done
}

Usage:

# parse the config file called 'myfile.ini', with the following
# contents::
#   [sec2]
#   var2='something'
cfg.parser 'myfile.ini'

# enable section called 'sec2' (in the file [sec2]) for reading
cfg.section.sec2

# read the content of the variable called 'var2' (in the file
# var2=XXX). If your var2 is an array, then you can use
# ${var[index]}
echo "$var2"

Bash ini-parser can be found at The Old School DevOps blog site.

Intaglio answered 11/6, 2011 at 22:29 Comment(9)
While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes.Argyle
I'm normally the one giving comments like this; all I can say is that I was young and stupid :-)Intaglio
If you like this snippet, there is an enhacement on github.com/albfan/bash-ini-parserJudkins
To work correctly, need to use cfg_parser instead of cfg.parserGonroff
TYPO : "cfg.parser" should be "cfg_parser".Ial
Works nice, please add resetting of IFS so the rest of the script isnt dependent on de IFS setting from cfg_parser. First line OIFS=$IFS, last line IFS=$OIFS.Zsazsa
I love the phrase "bash priest", that makes this snippet easy to remember lolWashtub
@Zsazsa - Since $IFS is set inside a function, it can be declared with local, which will cause it to go away automatically once the function returns.Licking
cfg_parser somehow resembles a christmas tree.Glaring
C
24

Just include your .ini file into bash body:

File example.ini:

DBNAME=test
DBUSER=scott
DBPASSWORD=tiger

File example.sh

#!/bin/bash
#Including .ini file
. example.ini
#Test
echo "${DBNAME}   ${DBUSER}  ${DBPASSWORD}"
Chihli answered 8/5, 2015 at 18:27 Comment(7)
This should be the selected answer. It does work with file.properties and is fault tolerant (file with empty line inside). ThanksAutoroute
does not handle the [section] part of INI files.Ial
this is the best answer!Adsorb
Hopefully nobody ever adds a "rm -rf /" to the ini file :(Storer
Much safer in sub-shell: $(. example.ini; echo $DBNAME)Disesteem
This is no good INI file example, dot (or "source") does not inherit ini files, but just files with configuration variables. So the things that make INI syntax special do not get included when doing this. So, this is by far NOT the best answer, according to OP question.Bernadine
This looks more like an ENV file. INI typically have sections, but I agree this is a really good answer.Derange
B
21

You may use crudini tool to get ini values, e.g.:

DATABASE_VERSION=$(crudini --get parameters.ini '' database_version)
Bisayas answered 26/8, 2014 at 19:21 Comment(3)
Note it's based on Python, so may not be suitable for e.g. embedded Linux applications.Scandalmonger
This is part of the standard Fedora repos (tested with 31). yum install crudiniImmodest
Finally, a proper solution that handles INI file groups as a proper parser interface. Empty argument for no group.Phyletic
L
16

All of the solutions I've seen so far also hit on commented out lines. This one didn't, if the comment code is ;:

awk -F '=' '{if (! ($0 ~ /^;/) && $0 ~ /database_version/) print $2}' file.ini
Lithograph answered 25/1, 2013 at 18:14 Comment(5)
This should be the accepted answer since a) It handles commented out lines b) simple :)Mir
This is terrific, ty @PenguinLust! Usage: 1.Full-line Comments allowed with semicolon prefix (no inline end of line comments allowed); 2.Whitespace is not elided from the result (so if ini file has 'a = 1', then the script's search for 'a' evaluates to ' 1').Sheepshearing
To trim spaces, add | tr -d ' ' at the end.Vander
This has the same problem as the suggested answer; it searches for every instance of "database_version"Unstriped
This is indeed better, although it doesn't handle the # comments (used a lot in linux conf files) and the fact that only the last value is the valid one, so maybe addin trim would be: awk -F '=' '{if (! ($0 ~ /^;/) && ! ($0 ~ /^#/) && $0 ~ /ca_db/) print $2}' sssd.conf | tr -d ' ' | tail -n1Brigitte
J
14

one of more possible solutions

dbver=$(sed -n 's/.*database_version *= *\([^ ]*.*\)/\1/p' < parameters.ini)
echo $dbver
Jhelum answered 11/6, 2011 at 23:50 Comment(0)
F
11

Similar to the other Python answers, you can do this using the -c flag to execute a sequence of Python statements given on the command line:

$ python3 -c "import configparser; c = configparser.ConfigParser(); c.read('parameters.ini'); print(c['parameters.ini']['database_version'])"
20110611142248

This has the advantage of requiring only the Python standard library and the advantage of not writing a separate script file.

Or use a here document for better readability, thusly:

#!/bin/bash
python << EOI
import configparser
c = configparser.ConfigParser()
c.read('params.txt')
print c['chassis']['serialNumber']
EOI

serialNumber=$(python << EOI
import configparser
c = configparser.ConfigParser()
c.read('params.txt')
print c['chassis']['serialNumber']
EOI
)

echo $serialNumber
Faro answered 29/9, 2017 at 20:38 Comment(1)
What if i want to grab a whole section as Array using this command?Corydon
M
10

Display the value of my_key in an ini-style my_file:

sed -n -e 's/^\s*my_key\s*=\s*//p' my_file
  • -n -- do not print anything by default
  • -e -- execute the expression
  • s/PATTERN//p -- display anything following this pattern In the pattern:
  • ^ -- pattern begins at the beginning of the line
  • \s -- whitespace character
  • * -- zero or many (whitespace characters)

Example:

$ cat my_file
# Example INI file
something   = foo
my_key      = bar
not_my_key  = baz
my_key_2    = bing

$ sed -n -e 's/^\s*my_key\s*=\s*//p' my_file
bar

So:

Find a pattern where the line begins with zero or many whitespace characters, followed by the string my_key, followed by zero or many whitespace characters, an equal sign, then zero or many whitespace characters again. Display the rest of the content on that line following that pattern.

Morion answered 14/9, 2014 at 14:11 Comment(1)
Your example doesn't work (not bar printed out), at least on Unix/OSX.Vander
V
8

sed

You can use sed to parse the ini configuration file, especially when you've section names like:

# last modified 1 April 2001 by John Doe
[owner]
name=John Doe
organization=Acme Widgets Inc.

[database]
# use IP address in case network name resolution is not working
server=192.0.2.62
port=143
file=payroll.dat

so you can use the following sed script to parse above data:

# Configuration bindings found outside any section are given to
# to the default section.
1 {
  x
  s/^/default/
  x
}

# Lines starting with a #-character are comments.
/#/n

# Sections are unpacked and stored in the hold space.
/\[/ {
  s/\[\(.*\)\]/\1/
  x
  b
}

# Bindings are unpacked and decorated with the section
# they belong to, before being printed.
/=/ {
  s/^[[:space:]]*//
  s/[[:space:]]*=[[:space:]]*/|/
  G
  s/\(.*\)\n\(.*\)/\2|\1/
  p
}

this will convert the ini data into this flat format:

owner|name|John Doe
owner|organization|Acme Widgets Inc.
database|server|192.0.2.62
database|port|143
database|file|payroll.dat

so it'll be easier to parse using sed, awk or read by having section names in every line.

Credits & source: Configuration files for shell scripts, Michael Grünewald


Alternatively, you can use this project: chilladx/config-parser, a configuration parser using sed.

Vander answered 15/7, 2016 at 10:15 Comment(1)
This is great! I was thinking about flattening it like that but this is miles beyond what I was about to hack together!Annihilator
D
5

For people (like me) looking to read INI files from shell scripts (read shell, not bash) - I've knocked up the a little helper library which tries to do exactly that:

https://github.com/wallyhall/shini (MIT license, do with it as you please. I've linked above including it inline as the code is quite lengthy.)

It's somewhat more "complicated" than the simple sed lines suggested above - but works on a very similar basis.

Function reads in a file line-by-line - looking for section markers ([section]) and key/value declarations (key=value).

Ultimately you get a callback to your own function - section, key and value.

Dordogne answered 15/9, 2014 at 19:51 Comment(2)
@CraigMcQueen - I've added some very alpha-quality write support tonight. It's not "complete" by any stretch of the imagination!Dordogne
Brilliant! :-) MajorShulamith
P
4

Here is my version, which parses sections and populates a global associative array g_iniProperties with it. Note that this works only with bash v4.2 and higher.

function parseIniFile() { #accepts the name of the file to parse as argument ($1)
    #declare syntax below (-gA) only works with bash 4.2 and higher
    unset g_iniProperties
    declare -gA g_iniProperties
    currentSection=""
    while read -r line
    do
        if [[ $line = [*  ]] ; then
            if [[ $line = [* ]] ; then 
                currentSection=$(echo $line | sed -e 's/\r//g' | tr -d "[]")  
            fi
        else
            if [[ $line = *=*  ]] ; then
                cleanLine=$(echo $line | sed -e 's/\r//g')
                key=$currentSection.$(echo $cleanLine | awk -F: '{ st = index($0,"=");print  substr($0,0,st-1)}')
                value=$(echo $cleanLine | awk -F: '{ st = index($0,"=");print  substr($0,st+1)}')
                g_iniProperties[$key]=$value
            fi
        fi;
    done < $1
}

And here is a sample code using the function above:

parseIniFile "/path/to/myFile.ini"
for key in "${!g_iniProperties[@]}"; do
    echo "Found key/value $key = ${g_iniProperties[$key]}"
done
Pinniped answered 28/12, 2017 at 20:28 Comment(0)
I
4

Yet another implementation using awk with a little more flexibility.

function parse_ini() {
  cat /dev/stdin | awk -v section="$1" -v key="$2" '
    BEGIN {
      if (length(key) > 0) { params=2 }
      else if (length(section) > 0) { params=1 }
      else { params=0 }
    }
    match($0,/#/) { next }
    match($0,/^\[(.+)\]$/){
      current=substr($0, RSTART+1, RLENGTH-2)
      found=current==section
      if (params==0) { print current }
    }
    match($0,/(.+)=(.+)/) {
       if (found) {
         if (params==2 && key==$1) { print $3 }
         if (params==1) { printf "%s=%s\n",$1,$3 }
       }
    }'
}

You can use calling passing between 0 and 2 params:

cat myfile1.ini myfile2.ini | parse_ini # List section names

cat myfile1.ini myfile2.ini | parse_ini 'my-section' # Prints keys and values from a section

cat myfile1.ini myfile2.ini | parse_ini 'my-section' 'my-key' # Print a single value
Impower answered 29/1, 2021 at 3:54 Comment(2)
When source is used the second parameter is not picked up, when directly accessing the script instead, its picking up the "key" second parameter, but the params=2 line never triggers because $1 contains key=value. and tries to match it against key ubuntu 20.04 bash. Add to this, the line params=1 has %s=%s for me it prints the line and adds an extra = at the end, the second %s does nothing at all.Colt
the params=1 line, "%s\n" fixed the line, I still have no fix for the earlier mentions.Colt
E
3

complex simplicity

ini file

test.ini

[section1]
name1=value1
name2=value2
[section2]
name1=value_1
  name2  =  value_2

bash script with read and execute

/bin/parseini

#!/bin/bash

set +a
while read p; do
  reSec='^\[(.*)\]$'
  #reNV='[ ]*([^ ]*)+[ ]*=(.*)'     #Remove only spaces around name
  reNV='[ ]*([^ ]*)+[ ]*=[ ]*(.*)'  #Remove spaces around name and spaces before value
  if [[ $p =~ $reSec ]]; then
      section=${BASH_REMATCH[1]}
  elif [[ $p =~ $reNV ]]; then
    sNm=${section}_${BASH_REMATCH[1]}
    sVa=${BASH_REMATCH[2]}
    set -a
    eval "$(echo "$sNm"=\""$sVa"\")"
    set +a
  fi
done < $1

then in another script I source the results of the command and can use any variables within

test.sh

#!/bin/bash

source parseini test.ini

echo $section2_name2

finally from command line the output is thus

# ./test.sh 
value_2
Exercitation answered 1/11, 2017 at 3:1 Comment(1)
Great solution! Thanks!Colonialism
W
2

I wrote a quick and easy python script to include in my bash script.

For example, your ini file is called food.ini and in the file you can have some sections and some lines:

[FRUIT]
Oranges = 14
Apples = 6

Copy this small 6 line Python script and save it as configparser.py

#!/usr/bin/python
import configparser
import sys
config = configparser.ConfigParser()
config.read(sys.argv[1])
print config.get(sys.argv[2],sys.argv[3])

Now, in your bash script you could do this for example.

OrangeQty=$(python configparser.py food.ini FRUIT Oranges)

or

ApplesQty=$(python configparser.py food.ini FRUIT Apples)
echo $ApplesQty

This presupposes:

  1. you have Python installed
  2. you have the configparser library installed (this should come with a std python installation)

Hope it helps :¬)

Washing answered 11/9, 2016 at 17:15 Comment(1)
I was looking for something that did just this so I followed the example and it works just fine. I forgot I wrote this!!!! I tried to vote for myself but, alas, i cant vote for myself!!! ha haWashing
B
2

Some of the answers don't respect comments. Some don't respect sections. Some recognize only one syntax (only ":" or only "="). Some Python answers fail on my machine because of differing captialization or failing to import the sys module. All are a bit too terse for me.

So I wrote my own, and if you have a modern Python, you can probably call this from your Bash shell. It has the advantage of adhering to some of the common Python coding conventions, and even provides sensible error messages and help. To use it, name it something like myconfig.py (do NOT call it configparser.py or it may try to import itself,) make it executable, and call it like

value=$(myconfig.py something.ini sectionname value)

Here's my code for Python 3.5 on Linux:

#!/usr/bin/env python3
# Last Modified: Thu Aug  3 13:58:50 PDT 2017
"""A program that Bash can call to parse an .ini file"""

import sys
import configparser
import argparse

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description="A program that Bash can call to parse an .ini file")
    parser.add_argument("inifile", help="name of the .ini file")
    parser.add_argument("section", help="name of the section in the .ini file")
    parser.add_argument("itemname", help="name of the desired value")
    args = parser.parse_args()

    config = configparser.ConfigParser()
    config.read(args.inifile)
    print(config.get(args.section, args.itemname))
Bellinzona answered 3/8, 2017 at 21:30 Comment(0)
B
2

The explanation to the answer for the one-liner sed.

[section1]
param1=123
param2=345
param3=678

[section2]
param1=abc
param2=def
param3=ghi

[section3]
param1=000
param2=111
param3=222
sed -nr "/^\[section2\]/ { :l /^\s*[^#].*/ p; n; /^\[/ q; b l; }" ./file.ini

To understand, it will be easier to format the line like this:

sed -nr "
      # start processing when we found the word \"section2\"
      /^\[section2\]/  { #the set of commands inside { } will be executed
          #create a label \"l\"  (https://www.grymoire.com/Unix/Sed.html#uh-58)
          :l /^\s*[^#].*/ p; 
          # move on to the next line. For the first run it is the \"param1=abc\"
          n; 
          # check if this line is beginning of new section. If yes - then exit.
          /^\[/ q
          #otherwise jump to the label \"l\"
          b l
          }

" file.ini
Bashaw answered 6/11, 2020 at 21:23 Comment(0)
N
1

This script will get parameters as follow :

meaning that if your ini has :

pars_ini.ksh < path to ini file > < name of Sector in Ini file > < the name in name=value to return >

eg. how to call it :


[ environment ]

a=x

[ DataBase_Sector ]

DSN = something


Then calling :

pars_ini.ksh /users/bubu_user/parameters.ini DataBase_Sector DSN

this will retrieve the following "something"

the script "pars_ini.ksh" :

\#!/bin/ksh

\#INI_FILE=path/to/file.ini

\#INI_SECTION=TheSection

\# BEGIN parse-ini-file.sh

\# SET UP THE MINIMUM VARS FIRST

alias sed=/usr/local/bin/sed

INI_FILE=$1

INI_SECTION=$2

INI_NAME=$3

INI_VALUE=""


eval `sed -e 's/[[:space:]]*\=[[:space:]]*/=/g' \

    -e 's/;.*$//' \

    -e 's/[[:space:]]*$//' \

    -e 's/^[[:space:]]*//' \

    -e "s/^\(.*\)=\([^\"']*\)$/\1=\"\2\"/" \

   < $INI_FILE  \

    | sed -n -e "/^\[$INI_SECTION\]/,/^\s*\[/{/^[^;].*\=.*/p;}"`


TEMP_VALUE=`echo "$"$INI_NAME`

echo `eval echo $TEMP_VALUE`
Nestle answered 12/2, 2013 at 15:6 Comment(0)
E
1

This implementation uses awk and has the following advantages:

  1. Will only return the first matching entry
  2. Ignores lines that start with a ;
  3. Trims leading and trailing whitespace, but not internal whitespace

Formatted version:

awk -F '=' '/^\s*database_version\s*=/ {
            sub(/^ +/, "", $2);
            sub(/ +$/, "", $2);
            print $2;
            exit;
          }' parameters.ini

One-liner:

awk -F '=' '/^\s*database_version\s*=/ { sub(/^ +/, "", $2); sub(/ +$/, "", $2); print $2; exit; }' parameters.ini
Extine answered 23/6, 2016 at 16:25 Comment(0)
S
1

You can use a CSV parser xsv as parsing INI data.

cargo install xsv
$ cat /etc/*release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
$ xsv select -d "=" - <<< "$( cat /etc/*release )" | xsv search --no-headers --select 1 "DISTRIB_CODENAME" | xsv select 2
xenial

or from a file.

$ xsv select -d "=" - file.ini | xsv search --no-headers --select 1 "DISTRIB_CODENAME" | xsv select 2
Syncopated answered 21/8, 2019 at 11:17 Comment(0)
P
1

If using sections, this will do the job :

Example raw output :

$ ./settings
[section]
SETTING_ONE=this is setting one
SETTING_TWO=This is the second setting
ANOTHER_SETTING=This is another setting

Regexp parsing :

$ ./settings | sed -n -E "/^\[.*\]/{s/\[(.*)\]/\1/;h;n;};/^[a-zA-Z]/{s/#.*//;G;s/([^ ]*) *= *(.*)\n(.*)/\3_\1='\2'/;p;}"
section_SETTING_ONE='this is setting one'
section_SETTING_TWO='This is the second setting'
section_ANOTHER_SETTING='This is another setting'

Now all together :

$ eval "$(./settings | sed -n -E "/^\[.*\]/{s/\[(.*)\]/\1/;h;n;};/^[a-zA-Z]/{s/#.*//;G;s/([^ ]*) *= *(.*)\n(.*)/\3_\1='\2'/;p;}")"
$ echo $section_SETTING_TWO
This is the second setting
Potato answered 20/4, 2020 at 18:5 Comment(3)
At Regexp parsing command, executing ./settings resulted in Permission denied, perhaps missing a $(cat ...) or something?Lew
Nearly there, I have dashes in sections which are invalid characters for env names. Struggling a bit to modify this sed command.Kwei
Ok, got it. The response with zero votes should be chosen answer :D source <(sed -n -E "/^\[.*\]/{s/\[(.*)\]/\1/;s/-/_/g;h;n;};/^[a-zA-Z]/{s/#.*//;G;s/([^ ]*) *= *(.*)\n(.*)/\3_\1='\2'/;p;};s/([^=]*)/-/g" ~/.aws/credentials)Kwei
D
1

I have nice one-liner (assuimng you have php and jq installed):

cat file.ini | php -r "echo json_encode(parse_ini_string(file_get_contents('php://stdin'), true, INI_SCANNER_RAW));" | jq '.section.key'
Doggery answered 30/7, 2020 at 8:30 Comment(1)
Nice solution but JSON and jq do not need to be involved. PHP can do all the needed processing.Nusku
L
0

My version of the one-liner

#!/bin/bash
#Reader for MS Windows 3.1 Ini-files
#Usage: inireader.sh

# e.g.: inireader.sh win.ini ERRORS DISABLE
# would return value "no" from the section of win.ini
#[ERRORS]
#DISABLE=no
INIFILE=$1
SECTION=$2
ITEM=$3
cat $INIFILE | sed -n /^\[$SECTION\]/,/^\[.*\]/p | grep "^[:space:]*$ITEM[:space:]*=" | sed s/.*=[:space:]*//
Leger answered 3/9, 2015 at 11:45 Comment(0)
S
0

When I use a password in base64, I put the separator ":" because the base64 string may has "=". For example (I use ksh):

> echo "Abc123" | base64
QWJjMTIzCg==

In parameters.ini put the line pass:QWJjMTIzCg==, and finally:

> PASS=`awk -F":" '/pass/ {print $2 }' parameters.ini | base64 --decode`
> echo "$PASS"
Abc123

If the line has spaces like "pass : QWJjMTIzCg== " add | tr -d ' ' to trim them:

> PASS=`awk -F":" '/pass/ {print $2 }' parameters.ini | tr -d ' ' | base64 --decode`
> echo "[$PASS]"
[Abc123]
Superannuated answered 10/9, 2015 at 16:28 Comment(0)
C
0

Just finished writing my own parser. I tried to use various parser found here, none seems to work with both ksh93 (AIX) and bash (Linux).

It's old programming style - parsing line by line. Pretty fast since it used few external commands. A bit slower because of all the eval required for dynamic name of the array.

The ini support 3 special syntaxs:

  • includefile=ini file --> Load an additionnal ini file. Useful for splitting ini in multiple files, or re-use some piece of configuration
  • includedir=directory --> Same as includefile, but include a complete directory
  • includesection=section --> Copy an existing section to the current section.

I used all thoses syntax to have pretty complex, re-usable ini file. Useful to install products when installing a new OS - we do that a lot.

Values can be accessed with ${ini[$section.$item]}. The array MUST be defined before calling this.

Have fun. Hope it's useful for someone else!

function Show_Debug {
    [[ $DEBUG = YES ]] && echo "DEBUG $@"
    }

function Fatal {
    echo "$@. Script aborted"
    exit 2
    }
#-------------------------------------------------------------------------------
# This function load an ini file in the array "ini"
# The "ini" array must be defined in the calling program (typeset -A ini)
#
# It could be any array name, the default array name is "ini".
#
# There is heavy usage of "eval" since ksh and bash do not support
# reference variable. The name of the ini is passed as variable, and must
# be "eval" at run-time to work. Very specific syntax was used and must be
# understood before making any modifications.
#
# It complexify greatly the program, but add flexibility.
#-------------------------------------------------------------------------------

function Load_Ini {
    Show_Debug "$0($@)"
    typeset ini_file="$1"
# Name of the array to fill. By default, it's "ini"
    typeset ini_array_name="${2:-ini}"
    typeset section variable value line my_section file subsection value_array include_directory all_index index sections pre_parse
    typeset LF="
"
    if [[ ! -s $ini_file ]]; then
        Fatal "The ini file is empty or absent in $0 [$ini_file]"
    fi

    include_directory=$(dirname $ini_file)
    include_directory=${include_directory:-$(pwd)}

    Show_Debug "include_directory=$include_directory"

    section=""
# Since this code support both bash and ksh93, you cannot use
# the syntax "echo xyz|while read line". bash doesn't work like
# that.
# It forces the use of "<<<", introduced in bash and ksh93.

    Show_Debug "Reading file $ini_file and putting the results in array $ini_array_name"
    pre_parse="$(sed 's/^ *//g;s/#.*//g;s/ *$//g' <$ini_file | egrep -v '^$')"
    while read line; do
        if [[ ${line:0:1} = "[" ]]; then # Is the line starting with "["?
# Replace [section_name] to section_name by removing the first and last character
            section="${line:1}"
            section="${section%\]}"
            eval "sections=\${$ini_array_name[sections_list]}"
            sections="$sections${sections:+ }$section"
            eval "$ini_array_name[sections_list]=\"$sections\""
            Show_Debug "$ini_array_name[sections_list]=\"$sections\""
            eval "$ini_array_name[$section.exist]=YES"
            Show_Debug "$ini_array_name[$section.exist]='YES'"
        else
            variable=${line%%=*}   # content before the =
            value=${line#*=}       # content after the =

            if [[ $variable = includefile ]]; then
# Include a single file
                Load_Ini "$include_directory/$value" "$ini_array_name"
                continue
            elif [[ $variable = includedir ]]; then
# Include a directory
# If the value doesn't start with a /, add the calculated include_directory
                if [[ $value != /* ]]; then
                    value="$include_directory/$value"
                fi
# go thru each file
                for file in $(ls $value/*.ini 2>/dev/null); do
                    if [[ $file != *.ini ]]; then continue; fi
# Load a single file
                    Load_Ini "$file" "$ini_array_name"
                done
                continue
            elif [[ $variable = includesection ]]; then
# Copy an existing section into the current section
                eval "all_index=\"\${!$ini_array_name[@]}\""
# It's not necessarily fast. Need to go thru all the array
                for index in $all_index; do
# Only if it is the requested section
                    if [[ $index = $value.* ]]; then
# Evaluate the subsection [section.subsection] --> subsection
                        subsection=${index#*.}
# Get the current value (source section)
                        eval "value_array=\"\${$ini_array_name[$index]}\""
# Assign the value to the current section
# The $value_array must be resolved on the second pass of the eval, so make sure the
# first pass doesn't resolve it (\$value_array instead of $value_array).
# It must be evaluated on the second pass in case there is special character like $1,
# or ' or " in it (code).
                        eval "$ini_array_name[$section.$subsection]=\"\$value_array\""
                        Show_Debug "$ini_array_name[$section.$subsection]=\"$value_array\""
                    fi
                done
            fi

# Add the value to the array
            eval "current_value=\"\${$ini_array_name[$section.$variable]}\""
# If there's already something for this field, add it with the current
# content separated by a LF (line_feed)
            new_value="$current_value${current_value:+$LF}$value"
# Assign the content
# The $new_value must be resolved on the second pass of the eval, so make sure the
# first pass doesn't resolve it (\$new_value instead of $new_value).
# It must be evaluated on the second pass in case there is special character like $1,
# or ' or " in it (code).
            eval "$ini_array_name[$section.$variable]=\"\$new_value\""
            Show_Debug "$ini_array_name[$section.$variable]=\"$new_value\""
        fi
    done  <<< "$pre_parse"
    Show_Debug "exit $0($@)\n"
    }
Cracow answered 17/3, 2016 at 2:5 Comment(0)
F
0

This uses the system perl and clean regular expressions:

cat parameters.ini | perl -0777ne 'print "$1" if /\[\s*parameters\.ini\s*\][\s\S]*?\sdatabase_version\s*=\s*(.*)/'
Fiji answered 21/6, 2018 at 1:6 Comment(0)
S
0

The answer of "Karen Gabrielyan" among another answers was the best but in some environments we dont have awk, like typical busybox, i changed the answer by below code.

trim()
{
    local trimmed="$1"

    # Strip leading space.
    trimmed="${trimmed## }"
    # Strip trailing space.
    trimmed="${trimmed%% }"

    echo "$trimmed"
}


  function parseIniFile() { #accepts the name of the file to parse as argument ($1)
        #declare syntax below (-gA) only works with bash 4.2 and higher
        unset g_iniProperties
        declare -gA g_iniProperties
        currentSection=""
        while read -r line
        do
            if [[ $line = [*  ]] ; then
                if [[ $line = [* ]] ; then 
                    currentSection=$(echo $line | sed -e 's/\r//g' | tr -d "[]")  
                fi
            else
                if [[ $line = *=*  ]] ; then
                    cleanLine=$(echo $line | sed -e 's/\r//g')
                    key=$(trim $currentSection.$(echo $cleanLine | cut -d'=' -f1'))
                    value=$(trim $(echo $cleanLine | cut -d'=' -f2))
                    g_iniProperties[$key]=$value
                fi
            fi;
        done < $1
    }
Scrumptious answered 22/9, 2018 at 12:19 Comment(5)
I am not entirely sure how likely it is that awk is missing, but sed, cut and relatively more advanced bash like syntax are available.Annetteannex
Most initial root file systems implement /linuxrc or /init as a shell script and thus include a minimal shell (usually /bin/ash) along with some essential user-space utilitiesScrumptious
Sure. I am just a little surprised you'd build your busybox without awk, but still with sed, cut and support for various "bashisms". Not that it wouldn't be possible, just makes me wonder. ;)Annetteannex
Other tools is more lightwight than awk. if you write script into initramfs with initramfs-tools in ubuntu distro, you will find that you dont have awk and also other tools like sed, grep ... are in minimal operation.Scrumptious
Sure, I am not talking about GNU awk or other full blow awk, just wondering how much one saves by configuring busybox to not include awk support (esp. given the other bits mentioned are not stripped out of that config). Could be that *buntu initrd has one just like that. Just wondering about the combo/choice that's all.Annetteannex
H
0

If Python is available, the following will read all the sections, keys and values and save them in variables with their names following the format "[section]_[key]". Python can read .ini files properly, so we make use of it.

#!/bin/bash

eval $(python3 << EOP
from configparser import SafeConfigParser

config = SafeConfigParser()
config.read("config.ini"))

for section in config.sections():
    for (key, val) in config.items(section):
        print(section + "_" + key + "=\"" + val + "\"")
EOP
)

echo "Environment_type:  ${Environment_type}"
echo "Environment_name:  ${Environment_name}"

config.ini

[Environment]
  type                = DEV
  name                = D01
Hoe answered 6/2, 2019 at 19:49 Comment(0)
F
0

This thread does not have enough solutions to choose from, thus here my solution, it does not require tools like sed or awk :

grep '^\[section\]' -A 999 config.ini | tail -n +2  | grep -B 999 '^\[' | head -n -1 | grep '^key' | cut -d '=' -f 2 

If your are to expect sections with more than 999 lines, feel free to adapt the example above. Note that you may want to trim the resulting value, to remove spaces or a comment string after the value. Remove the ^ if you need to match keys that do not start at the beginning of the line, as in the example of the question. Better, match explicitly for white spaces and tabs, in such a case.

If you have multiple values in a given section you want to read, but want to avoid reading the file multiple times:

CONFIG_SECTION=$(grep '^\[section\]' -A 999 config.ini | tail -n +2  | grep -B 999 '^\[' | head -n -1)

KEY1=$(echo ${CONFIG_SECTION} | tr ' ' '\n' | grep key1 | cut -d '=' -f 2)
echo "KEY1=${KEY1}"
KEY2=$(echo ${CONFIG_SECTION} | tr ' ' '\n' | grep key2 | cut -d '=' -f 2)
echo "KEY2=${KEY2}"
Faceplate answered 24/11, 2020 at 19:55 Comment(0)
C
0

This is based on Juarez Rudsatz answer, so thank you very much for setting me in the right direction.

I did have a little trouble with it though in Ubuntu 20.04 bash.

function parse_ini() {
    cat /dev/stdin | awk -v section="$1" -v key="$2" '
    BEGIN {
    if (length(key) > 0) { params=2 }
    else if (length(section) > 0) { params=1 }
    else { params=0 }
    }
    match($0,/;/) { next }
    match($0,/#/) { next }
    match($0,/^\[(.+)\]$/){
    current=substr($0, RSTART+1, RLENGTH-2)
    found=current==section
    if (params==0) { print current }
    }
    match($0,/(.+)=(.+)/) {
    if (found) {
        if (params==2 && key==substr($1, 0, length(key))) { print substr($0, length(key)+2) }
        if (params==1) { printf "%s\n",$1,$3 }
    }
    }'
}

The differences here are the substr in the last block, Juarez example was adding an extra = when showing a sections parameters. It was also failing to show anything when outputting individual options.

To remedy the extra = I removed =%s from the params==1 line. To remedy the missing values, I reworked the params==2 line almost completely. I added in a match to exclude ; commented lines also.

This example will work with INI files such as

[default]
opt=1
option=option
option1=This is just another example
example=this wasn't working before
#commented=I will not be shown
;commentedtoo=Neither Will I.

[two]
opt='default' 'opt'just doesnt get me! but 'two' 'opt' does :)
iam=not just for show

To use this script it is no different to Juarez's example.

cat options.ini | parse_ini                     # Show Sections
cat options.ini | parse_ini 'default'           # Show Options with values
cat options.ini | parse_ini 'default' 'option'  # Show Option Value
cat options.ini | parse_ini 'two' 'iam'         # Same as last but from another section

This ofcourse is functioning as expected. So what else, ah yes, you might want a functions file to declutter your script, and you probably want to access the values. Here is a quick example.

app="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" # get the path to your program
source $app/functions.script # could be in a subdirectory.
somevar=$(cat $app/options.ini | parse_ini 'section' 'option') # Now its in a variable.
Colt answered 28/8, 2021 at 1:29 Comment(1)
See wiki.bash-hackers.org/scripting/obsolete re: the function keyword. Just parse_ini() { is more portable.Viscountcy
B
0

I took the function that Chris wrote and turned it into a shell script for personal use. Feel free to use yourself. Thanks Chris!

https://github.com/tyler-hansen/general_use

I provided an executable binary and source code and how to use them.

read-ini [arg1] [arg2] [arg3]
Arguments:
         arg1 = config.ini file
         arg2 = section (defined in ini file with [])
         arg3 = variable key (within the section)

Example:

 $ read-ini config.ini FANTOM path  
 /data/hodges_lab/ATAC-STARR_B-cells/data/hansen-fong/fantom
Belva answered 20/1, 2022 at 17:32 Comment(0)
P
0

Print all ini bloks

awk '/^\[.*]/{print}' /etc/xrdp/sesman.ini


[Globals]
...
[Xvnc]
...

Print single ini blok

    i=Xvnc;awk -v X=\\\\[$i] '{if($0~/\[.*]/)p=0;if($0~X)p=1;if(1==p){print}}' /etc/xrdp/sesman.ini

[Xvnc]
param=Xvnc
param=-bs
param=-nolisten
param=tcp
param=-localhost
param=-dpi
param=96

if need 2 and more ini blok

awk '{if($0~/^\[.*]/)p=0;if($0~/^\[Xvnc]|^\[Globals]/)p=1;if(1==p){print}}' /etc/xrdp/sesman.ini
Pilgrimage answered 1/3 at 10:21 Comment(0)
N
0

Bash script, sed, awk and grep are not the right tool for this job because they are line oriented and parsing a structured file with them, even possible to some extent, is tedious and requires a lot of code (and it's difficult to handle the corner cases.)

Given that PHP is available, a two-lines PHP script solves the problem. Write this short script in a file named read-ini.php:

<?php
$ini = parse_ini_file($argv[1], true, INI_SCANNER_RAW);
echo($ini['parameters.ini']['database_version']."\n");

Usage:

DATABASE_VERSION=$(php ./read-ini.php parameters.ini)
php app/console doctrine:migrations:migrate "$DATABASE_VERSION"

The script is a quick and dirty solution, not production-ready, but it should do the job. It requires several more lines to validate the number of arguments, check if the input file exists, etc.

If there are multiple values that need to be read from the INI file, expand the PHP script to accept as arguments the file name, the section name and the entry name:

<?php
$inifile = $argv[1];
$section = $argv[2];
$key = $argv[3];
$ini = parse_ini_file($inifile, true, INI_SCANNER_RAW);
echo($ini[$section][$key]."\n");

Again, it needs validation of the arguments, check if the section and key exists, etc. For brevity, these are not presented here.

Now it can be used to extract different values from the INI file:

DATABASE_VERSION=$(php ./read-ini.php parameters.ini parameters.ini database_version)
php app/console doctrine:migrations:migrate "$DATABASE_VERSION"

DATABASE_USER=$(php ./read-ini.php parameters.ini parameters.ini database_user)
echo "User: $DATABASE_USER"
Nusku answered 1/3 at 15:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.