{"id":25077968,"url":"https://github.com/egberts/bash-ini-file","last_synced_at":"2025-06-14T02:32:55.481Z","repository":{"id":89061954,"uuid":"466486981","full_name":"egberts/bash-ini-file","owner":"egberts","description":"Get keyvalues by its section/keyword from an INI-format (v1.4) file in bash (also works with systemd, NetworkManager config files)","archived":false,"fork":false,"pushed_at":"2022-10-12T17:44:38.000Z","size":69,"stargazers_count":14,"open_issues_count":3,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-04-22T04:48:41.613Z","etag":null,"topics":["bash","bash-script","bash-scripting","ini-config","ini-file","ini-parser","ini-reader","networkmanager-scripts","regex","regex-pattern","systemd-script"],"latest_commit_sha":null,"homepage":"","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/egberts.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-03-05T15:10:00.000Z","updated_at":"2024-06-01T01:35:25.000Z","dependencies_parsed_at":"2023-06-13T18:45:42.615Z","dependency_job_id":null,"html_url":"https://github.com/egberts/bash-ini-file","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/egberts/bash-ini-file","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/egberts%2Fbash-ini-file","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/egberts%2Fbash-ini-file/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/egberts%2Fbash-ini-file/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/egberts%2Fbash-ini-file/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/egberts","download_url":"https://codeload.github.com/egberts/bash-ini-file/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/egberts%2Fbash-ini-file/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259748994,"owners_count":22905633,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bash","bash-script","bash-scripting","ini-config","ini-file","ini-parser","ini-reader","networkmanager-scripts","regex","regex-pattern","systemd-script"],"created_at":"2025-02-07T02:40:26.958Z","updated_at":"2025-06-14T02:32:55.214Z","avatar_url":"https://github.com/egberts.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"Get that keyvalue from INI!\n\n# bash-ini-file\nFrom an [INI-format (v1.4)](https://cloanto.com/specs/ini/#escapesequences) file, be able to extract any keyvalue by its section/keyword ... in `bash`.\n\nYou got an INI file, I've got the bash script in which to get your settings from with.\n\nWorks with:\n\n* systemd configuration file\n* Python configuration file\n* NetworkManager configuration file\n* ifup/down configuration file\n* PHP configuration file\n* Windows .INI\n\nTreats a no-section (any `keyword=keyvalue` before a `[section]`) as a '`[Default]`';  reads both no-section and `[Default]` together as `[Default]`.\n\nAlso correctly finds the last keyvalue of the desired section/keyword before extracting its keyvalue, despite its multiply-defined/multiple-reused interspersed/alternating section blocks.\n\nDetails\n=======\n\nFile format: .INI\nSupported version: 1.4 (2009)\n\nFeatures\n----\nCurrently supported features are:\n\n* loads all settings into a multi-line bash string (no need for variable array)\n* Treats no-section as '`[Default]`';  reads both sections together as Default.\n* Check the section name and keyword name for valid character set.\n* Nested quotes also works alongside with inline comment (except for '//' inline comment support)\n* Supports and ignores inline comment using semicolon '`;`', hashmark '`#`'; But the double-slash '`//`' regex has been properly defined but not yet integrated as `bash` yet.  See [Issue 1](https://github.com/egberts/bash-ini-file/issues/1).\n* Some 30,000 keyvalue lookups per second, no it is more like 20/second; well, like performance really matters here anyway.\n\nHOW I DID THIS\n==============\n\nThe secret sauce is to convert the entire INI file into a parsable syntax format just with this one `awk` programming:\n\n```awk\n/^\\[.*\\]$/{obj=$0}/=/{print obj $0}'\n```\nso a bash line was born:\n```bash\nini_buffer=\"$(print \"%s\" \"$raw_buffer\" | awk '/^\\[.*\\]$/{obj=$0}/=/{print obj $0}')\"\n```\n\nStandardized INI Table Format\n--------------------------\nNext is to standardize the INI to a common syntax format:\n```\n[section]keyword=keyvalue\n```\n\nAn example INI file might look like this:\n```ini\nloneSetting=0\n\n[Network]\nDNS=1.1.1.1\n\n[Default]\nFirstDefaultKeyword=1\n\n```\nget turned into this:\n```ini\n[Default]loneSetting=0\n[Network]DNS=1.1.1.1\n[Default]FirstDefaultKeyword=1\n```\n\nNote:  Notice that we treated the 'no-section' as `[Default]`?\n\nParsable Galore!\n=======\nWith the usage of a common `[section]keyword=keyvalue` format, it now becomes easily possible to work with INI line-records in a faster manner using `sed`, `awk` and `tail` or even `grep`.\n\n\nAPIs, APIs, Lots of API; well, just a few.\n====\nSimply source the lone script file: `bash-ini-parser.sh`\nand start calling APIs such as:\n\n| API | `$?` | `STDOUT` | Description |\n| ---- | ---- | ---- | ---- |\n| `ini_read_file` | `-` | multi-line | Converts an INI-format file content into a variable containing an INI table |\n| `ini_section_name_normalize` | 0/1 | string | Normalize the section name into an acceptable form of INI-compliant name. |\n| `ini_section_list` | 0/1 | string | Outputs a list of section name(s) found in the INI table |\n| `ini_section_extract` | `-` | multi-line | Extract one or more INI table records having this matching 'section' name |\n| `ini_keyword_name_normalize` | 0/1 | string |  Normalize the keyword name into an acceptable form of INI-compliant name. |\n| `ini_keyword_valid` | 0/1 | `-` | Assert that the keyword is valid for use in a INI file. |\n| `ini_keyword_list` | 0/1 | string | Outputs a list of keyword name(s) found by a specified section in INI table |\n| `ini_keyword_extract` | `-` | multi-line | Extracts one or more INI records having matching keyword from an INI table |\n| `ini_keyvalue_get` | `-` | multi-line | Get the key value based on given section name and keyword name (most useful with `systemd`, `NetworkManager`. |\n| `ini_keyvalue_get_last` | `-` | string | Get the LAST key value encountered given a section name and a keyword name. (most useful if only interested by matched keyword for the last `keyword=keyvalue` to obtain its overridden keyvalue. |\n\nDemo\n====\nA nice bash script can be either my `example-usage.sh` script or below:\n\n```bash\nsource bash-ini-parser.sh\nread -rd '' raw_data \u003c \u003c(cat \"/etc/systemd/system/display-manager.service\")\nread -rd '' ini_service_section \u003c \u003c(ini_file_read \"$raw_data\")\nini_keyvalue_get \"$ini_service_section\" \"Service\" \"ExecStart\"\n# outputs the keyvalue\n```\n\nOr with `example-usage.sh`, this script will try to read systemd config file and determine which Display Manager that you are using:\n\n```console\n$ bash example-usage.sh \nFile    : /etc/systemd/system/display-manager.service\nKeyword : ExecStart\nKeyvalue: /usr/bin/sddm  # \u003c--- your section/keyword/keyvalue answer\n\nCame from all that below:\n\"[Unit]Description=Simple Desktop Display Manager\n[Unit]Documentation=man:sddm(1) man:sddm.conf(5)\n[Unit]Conflicts=getty@tty1.service getty@tty7.service\n[Unit]After=getty@tty1.service getty@tty7.service\n[Unit]After=systemd-user-sessions.service systemd-logind.service\n[Unit]After=haveged.service\n[Service]ExecStart=/usr/bin/sddm\n[Service]Restart=always\n[Service]RestartSec=1s\n[Service]EnvironmentFile=-/etc/default/locale\n[Install]Alias=display-manager.service\"\n\nDone.\n```\n\nUnit Test\n=========\nThe accompanied `tests` subdirectory performs the comprehensive unit testing, in case you have decided to tweaked it to your normative scenario; hopefully, this will find any errors of yours.\n\nTo exercise a specific unit test, your modified `bash-ini-parser.sh` must reside above the `tests` directory as all the unit tests will perform and find your modified script 'above':\n\n```bash\n#!/bin/bash\n# Title: my script file\n\nsource ../bash-ini-parser.sh\n\n...\n```\n\nSupreme Unit Testing\n---\nTo start the global unit test, execute:\n\n```bash\ncd tests\n./test-all.sh\n```\nand the output is long, very long, very very long.\n\nSelective Unit Test\n---\n\nTo perform a specific unit test, for example, `ini_keyvalue_get()`, execute:\n\n```console\n$ bash test-ini-keyvalue-get.sh \nassert_keyvalue_get([Default]DNS=): pass # same keyword, 'Default' section\nassert_keyvalue_get([Resolve]DNS=): pass # same keyword, 'Resolve' section\nassert_keyvalue_get([Default]DNS=): pass # empty ini_file\nassert_keyvalue_get([Default]DNS=): pass # new line\nassert_keyvalue_get([Default]DNS=): pass # hash mark no-comment\nassert_keyvalue_get([Default]DNS=): pass # semicolon no-comment\nassert_keyvalue_get([Default]DNS=): pass # slash-slash no-comment\nassert_keyvalue_get([Default]DNS=): pass # hash mark comment\nassert_keyvalue_get([Default]DNS=): pass # semicolon comment\nassert_keyvalue_get([Default]DNS=): pass # slash-slash comment\nassert_keyvalue_get([NoSuchSection]DNS=): pass # same keyword, 'NoSuchSection' section\nassert_keyvalue_get([]=): pass # unused keyword\nassert_keyvalue_get([]DNS=): pass # unused keyword, 'no-section default\nassert_keyvalue_get([Resolve]DNS=): pass # unused keyword, 'Resolve' section\nassert_keyvalue_get([NoSuchSection]DNS=): pass # unused keyword, noSuchSection\nassert_keyvalue_get([]=): pass # unused keyword\nassert_keyvalue_get([]DNS=): pass # unused keyword, 'no-section default\nassert_keyvalue_get([Resolve]DNS=): pass # unused keyword, 'Resolve' section\nassert_keyvalue_get([NoSuchSection]DNS=): pass # unused keyword, noSuchSection\nassert_keyvalue_get([]=): pass # unused keyword\nassert_keyvalue_get([]DNS=): pass # unused keyword, 'no-section default\nassert_keyvalue_get([Default]FallbackDNS=): pass # standard\nassert_keyvalue_get([Resolve]DNS=): pass # incomplete but matching keyword, 'Resolve' section\nassert_keyvalue_get([Resolve]DNS_Server1=): pass # incomplete but matching keyword, 'Resolve' section, NULL answer\nassert_keyvalue_get([NoSuchSection]DNS=): pass # unused keyword, noSuchSection\nassert_keyvalue_get([]=): pass # unused keyword\nassert_keyvalue_get([]DNS=): pass # unused keyword, 'no-section default\nassert_keyvalue_get([Gateway]Hidden_DNS_Master=): pass # unique section, underscored keyword\nassert_keyvalue_get([NoSuchSection]DNS=): pass # unique section, unused keyword, noSuchSection\nassert_keyvalue_get([]=): pass # unused keyword\nassert_keyvalue_get([]DNS=): pass # unused keyword, 'no-section default\nassert_keyvalue_get([Resolve]DNS=): pass # keyword 2 of 2, 'Resolve' section\nassert_keyvalue_get([NoSuchSection]DNS=): pass # unused keyword, noSuchSection\nassert_keyvalue_get([]=): pass # unused keyword\nassert_keyvalue_get([]DNS=): pass # unused keyword, 'no-section default\nassert_keyvalue_get([NoSuchSection]DNS=): pass # unused keyword, noSuchSection\nassert_keyvalue_get([Default]FallbackDNS=): pass # standard\nassert_keyvalue_get([Resolve]DNS_Server1=): pass # standard\nassert_keyvalue_get([DifferentSection]DNS=): pass # standard\nassert_keyvalue_get([Resolve]DNS_Server2=): pass # standard\nassert_keyvalue_get([DifferentSection2]DNS_2=): pass # standard\nassert_keyvalue_get([Resolve]DNS=): pass # standard\nassert_keyvalue_get([]=): pass # unused keyword\nassert_keyvalue_get([]DNS=): pass # unused keyword, 'no-section default\nassert_keyvalue_get([NoSuchSection]DNS=): pass # unused keyword, noSuchSection\nassert_keyvalue_get([Default]FallbackDNS=): pass # standard\nassert_keyvalue_get([Resolve]DNS_Server1=): pass # standard\nassert_keyvalue_get([DifferentSection]DNS=): pass # standard\nassert_keyvalue_get([Resolve]DNS_Server2=): pass # standard\nassert_keyvalue_get([DifferentSection2]DNS_2=): pass # standard\nassert_keyvalue_get([Resolve]DNS=): pass # standard\nassert_keyvalue_get([Gateway]Hidden_DNS_Master=): pass # standard\nassert_keyvalue_get([]=): pass # unused keyword\nassert_keyvalue_get([]DNS=): pass # unused keyword, 'no-section default\nassert_keyvalue_get([NoSuchSection]DNS=): pass # unused keyword, noSuchSection\nassert_keyvalue_get([Default]FallbackDNS=): pass # standard\nassert_keyvalue_get([Resolve]DNS_Server1=): pass # standard\nassert_keyvalue_get([DifferentSection]DNS=): pass # standard\nassert_keyvalue_get([Resolve]DNS_Server2=): pass # standard\nassert_keyvalue_get([DifferentSection2]DNS_2=): pass # standard\nassert_keyvalue_get([Resolve]DNS=): pass # standard\nassert_keyvalue_get([Gateway]Hidden_DNS_Master=): pass # standard\nassert_keyvalue_get([Gateway]Hidden_DNS_Master2=): pass # standard\nassert_keyvalue_get([]=): pass # no section, no keyword\nassert_keyvalue_get([]DNS=): pass # no-section, unused keyword\nassert_keyvalue_get([NoSuchSection]DNS=): pass # unused section, unused keyword\nassert_keyvalue_get([Default]FallbackDNS=): pass # # inside double-quote\nassert_keyvalue_get([Resolve]DNS_Server1=): pass # ; inside double-quote\nassert_keyvalue_get([DifferentSection]DNS=): pass # // inside double-quote\nassert_keyvalue_get([Resolve]DNS_Server2=): pass # ; inside LHS double-quote\nassert_keyvalue_get([DifferentSection2]DNS_2=): pass # // inside LHS double-quote\nassert_keyvalue_get([Resolve]DNS=): pass # ; inside RHS double-quote\nassert_keyvalue_get([Gateway]Hidden_DNS_Master=): pass # # inside RHS double-quote\nassert_keyvalue_get([Gateway]Hidden_DNS_Master2=): pass # // inside RHS double-quote\nassert_keyvalue_get([Default]FallbackDNS=): pass # # inside double-quote and outside\nassert_keyvalue_get([Resolve]DNS_Server1=): pass # ; inside quote and outside\nassert_keyvalue_get([Resolve]DNS_Server2=): pass # ; inside LHS double-quote and outside\nassert_keyvalue_get([Resolve]DNS=): pass # ; inside RHS double-quote and outside\nassert_keyvalue_get([Gateway]Hidden_DNS_Master2=): failed # // inside RHS double-quote and outside\n  expected: '\"78.78.78.78//\"'\n  actual  : '\"78.78.78.78//\"  // inline '/' '/' RHS double-quote'\n```\n\nOh, please disregard the `failed` at the last line for I have filed [`Issue 1`](https://github.com/egberts/bash-ini-file/issues/1).\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fegberts%2Fbash-ini-file","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fegberts%2Fbash-ini-file","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fegberts%2Fbash-ini-file/lists"}