惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

I
Intezer
云风的 BLOG
云风的 BLOG
罗磊的独立博客
Recent Announcements
Recent Announcements
L
LangChain Blog
T
Tailwind CSS Blog
Y
Y Combinator Blog
月光博客
月光博客
阮一峰的网络日志
阮一峰的网络日志
The Register - Security
The Register - Security
The Cloudflare Blog
Blog — PlanetScale
Blog — PlanetScale
博客园 - 司徒正美
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - 聂微东
博客园_首页
N
Netflix TechBlog - Medium
S
SegmentFault 最新的问题
宝玉的分享
宝玉的分享
爱范儿
爱范儿
WordPress大学
WordPress大学
腾讯CDC
MongoDB | Blog
MongoDB | Blog
D
Docker
V
V2EX
Engineering at Meta
Engineering at Meta
人人都是产品经理
人人都是产品经理
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
F
Full Disclosure
I
InfoQ
D
DataBreaches.Net
Martin Fowler
Martin Fowler
T
The Blog of Author Tim Ferriss
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
雷峰网
雷峰网
G
Google Developers Blog
B
Blog RSS Feed
F
Fortinet All Blogs
GbyAI
GbyAI
MyScale Blog
MyScale Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
M
MIT News - Artificial intelligence
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Recorded Future
Recorded Future
O
OpenAI News
Cloudbric
Cloudbric
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Microsoft Security Blog
Microsoft Security Blog
Help Net Security
Help Net Security
V
Visual Studio Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
goini: Stop Writing Bash to Parse .ini Files
Andrei Merle · 2026-04-30 · via DEV Community

I built goenv because I was tired of writing fragile shell one-liners to work with .env files. The same problem existed with .ini files — and goini is the same answer applied there.

If you've ever written something like this in a pipeline:

grep -A 5 "^\[default\]" config.ini | grep "^user" | cut -d= -f2 | tr -d ' '

Enter fullscreen mode Exit fullscreen mode

...you know exactly why this tool exists. That breaks the moment someone adds a comment, changes indentation, or reorders keys. It's archaeology, not automation.

goini is a compiled CLI binary that lets you read, write, validate and export .ini files from your pipeline scripts. Every operation either succeeds at exit code 0 or fails at exit code 1. That makes it composable. You can gate deployments on it. You can use it in an if statement. It behaves like a Unix citizen.

The source is at github.com/andreimerlescu/goini.


Installation

go install github.com/andreimerlescu/goini@latest

Enter fullscreen mode Exit fullscreen mode

Or grab the binary directly:

curl -sL https://github.com/andreimerlescu/goini/releases/download/v1.0.0/goini-linux-amd64 \
     --output /tmp/goini
chmod +x /tmp/goini
sudo mv /tmp/goini /usr/local/bin/goini
which goini

Enter fullscreen mode Exit fullscreen mode


The Sample File

Every example below operates against this sample.ini:

[default]
user = Yeshua
key = 369
port = 1776
country = ISRAEL

[extra]
ssh_key = ~/.ssh/id_rsa
ssh_key_pub = ~/.ssh/id_rsa.pub

Enter fullscreen mode Exit fullscreen mode

Two sections. A handful of keys. Simple on purpose — the point is showing what goini can do with it, not the data.


Validating With Exit Codes

This is the core use case for pipeline work. You don't need stdout. You need a binary answer: does this thing exist or not, and did it match or not.

Does a Section Have a Key?

goini -ini sample.ini -section default -has-section-key user
echo $?  # 0 — key 'user' exists in 'default'

goini -ini sample.ini -section default -has-section-key non_existent_key
echo $?  # 1 — key doesn't exist

Enter fullscreen mode Exit fullscreen mode

Does a Key Have a Specific Value?

goini -ini sample.ini -section default -key user -has-section-key-value Yeshua
echo $?  # 0

goini -ini sample.ini -section default -key user -has-section-key-value No
echo $?  # 1

Enter fullscreen mode Exit fullscreen mode

Are Multiple Sections All Present?

goini -ini sample.ini -are-sections-present default,extra
echo $?  # 0 — both sections exist

goini -ini sample.ini -are-sections-present default,non_existent_section
echo $?  # 1 — one is missing

Enter fullscreen mode Exit fullscreen mode

Does a Section Exist?

goini -ini sample.ini -has-section default
echo $?  # 0

goini -ini sample.ini -has-section ghost
echo $?  # 1

Enter fullscreen mode Exit fullscreen mode


Reading With STDOUT

When you need to pull data out of the file and feed it downstream, these are your commands.

List All Sections

goini -ini sample.ini -sections
# default
# extra

goini -ini sample.ini -sections -csv
# default,extra

goini -ini sample.ini -sections -json
# [
#   "default",
#   "extra"
# ]

goini -ini sample.ini -sections -yaml
# - default
# - extra

Enter fullscreen mode Exit fullscreen mode

List Keys in a Section

goini -ini sample.ini -section default -list-keys
# user
# key
# port
# country

goini -ini sample.ini -section default -list-keys -json
# [
#   "user",
#   "key",
#   "port",
#   "country"
# ]

Enter fullscreen mode Exit fullscreen mode

List Key-Value Pairs in a Section

goini -ini sample.ini -section default -list-key-values
# user = Yeshua
# key = 369
# port = 1776
# country = ISRAEL

goini -ini sample.ini -section default -list-key-values -json
# {
#   "country": "ISRAEL",
#   "key": "369",
#   "port": "1776",
#   "user": "Yeshua"
# }

Enter fullscreen mode Exit fullscreen mode


Writing to the File

Add a New Section

goini -ini sample.ini -add-section new_section
echo $?  # 0 — section added

# Try to add the same section again
goini -ini sample.ini -add-section new_section
echo $?  # 1 — already exists, refused

Enter fullscreen mode Exit fullscreen mode

This is idempotency by design. goini won't silently create a duplicate. It fails loudly so your pipeline knows something unexpected happened.

Add a Key to a Section

goini -ini sample.ini -section default -key new_setting -value 123 -add-key
echo $?  # 0 — new_setting=123 added to [default]

# Try to add a key that already exists
goini -ini sample.ini -section default -key user -value something -add-key
echo $?  # 1 — key already exists, refused

Enter fullscreen mode Exit fullscreen mode

Again — it won't overwrite. If you need to change an existing value, that's what -modify-key is for.

Modify an Existing Key

goini -ini sample.ini -section default -key user -value NewUserValue -modify-key
echo $?  # 0 — user updated to NewUserValue in [default]

# Try to modify a key that doesn't exist
goini -ini sample.ini -section default -key non_existent_key -value some_value -modify-key
echo $?  # 1 — key doesn't exist, refused

Enter fullscreen mode Exit fullscreen mode

The distinction between -add-key and -modify-key is intentional and important. You can't accidentally overwrite with add. You can't accidentally create with modify. They're separate operations with separate failure modes.


Real Pipeline Examples

Validating a Service Configuration Before Deploy

#!/bin/bash
set -euo pipefail

CONFIG="infra/service.ini"

echo "==> Validating ${CONFIG}..."

# Required sections must exist
goini -ini "${CONFIG}" -are-sections-present database,cache,api \
    || { echo "ERROR: missing required sections in ${CONFIG}"; exit 1; }

# Required keys must exist in each section
goini -ini "${CONFIG}" -section database -has-section-key host \
    || { echo "ERROR: database.host is required"; exit 1; }

goini -ini "${CONFIG}" -section database -has-section-key port \
    || { echo "ERROR: database.port is required"; exit 1; }

goini -ini "${CONFIG}" -section api -has-section-key base_url \
    || { echo "ERROR: api.base_url is required"; exit 1; }

# Assert environment is correct before touching production
goini -ini "${CONFIG}" -section api -key environment -has-section-key-value staging \
    || { echo "ERROR: api.environment must be 'staging'"; exit 1; }

echo "==> Validation passed."

Enter fullscreen mode Exit fullscreen mode

Bootstrapping a Fresh Config File

#!/bin/bash
set -euo pipefail

CONFIG="deploy.ini"

# Add sections — each will fail if already present, which is fine
# because set -euo pipefail would catch it; use || true if idempotency is needed
goini -ini "${CONFIG}" -add-section app    || true
goini -ini "${CONFIG}" -add-section database || true
goini -ini "${CONFIG}" -add-section cache  || true

# Populate app section
goini -ini "${CONFIG}" -section app -key name    -value "payments"   -add-key || true
goini -ini "${CONFIG}" -section app -key version -value "$(cat VERSION)" -add-key || true
goini -ini "${CONFIG}" -section app -key env     -value "staging"    -add-key || true

# Populate database section
goini -ini "${CONFIG}" -section database -key host    -value "db.internal" -add-key || true
goini -ini "${CONFIG}" -section database -key port    -value "5432"         -add-key || true
goini -ini "${CONFIG}" -section database -key name    -value "payments_db"  -add-key || true

# Verify it all landed
goini -ini "${CONFIG}" -section app -list-key-values
goini -ini "${CONFIG}" -section database -list-key-values

Enter fullscreen mode Exit fullscreen mode

Promoting Config From Staging to Production

#!/bin/bash
set -euo pipefail

STAGING="config.staging.ini"
PROD="config.production.ini"

echo "==> Promoting ${STAGING} → ${PROD}..."

# Verify staging has what we expect before promoting
goini -ini "${STAGING}" -section app -key env -has-section-key-value staging \
    || { echo "ERROR: source must be staging"; exit 1; }

# Flip the environment value in production
goini -ini "${PROD}" -section app -key env -value production -modify-key \
    || { echo "ERROR: failed to set env=production"; exit 1; }

# Confirm
goini -ini "${PROD}" -section app -key env -has-section-key-value production \
    && echo "==> Promotion complete." \
    || { echo "ERROR: production env value not confirmed"; exit 1; }

Enter fullscreen mode Exit fullscreen mode

Extracting Values for Use in Other Scripts

#!/bin/bash
set -euo pipefail

CONFIG="service.ini"

# Pull values out and assign to shell variables
DB_HOST=$(goini -ini "${CONFIG}" -section database -list-key-values -json \
    | python3 -c "import sys,json; print(json.load(sys.stdin)['host'])")

DB_PORT=$(goini -ini "${CONFIG}" -section database -list-key-values -json \
    | python3 -c "import sys,json; print(json.load(sys.stdin)['port'])")

echo "Connecting to ${DB_HOST}:${DB_PORT}"

Enter fullscreen mode Exit fullscreen mode

GitHub Actions

name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install goini
        run: |
          curl -sL https://github.com/andreimerlescu/goini/releases/download/v1.0.0/goini-linux-amd64 \
               --output /usr/local/bin/goini
          chmod +x /usr/local/bin/goini

      - name: Validate config
        run: |
          goini -ini config/service.ini -are-sections-present app,database,cache || exit 1
          goini -ini config/service.ini -section app -has-section-key version     || exit 1
          goini -ini config/service.ini -section app -key env -has-section-key-value staging || exit 1

      - name: Deploy
        run: ./scripts/deploy.sh

Enter fullscreen mode Exit fullscreen mode

GitLab CI

stages:
  - validate
  - deploy

validate_config:
  stage: validate
  image: golang:1.22
  before_script:
    - curl -sL https://github.com/andreimerlescu/goini/releases/download/v1.0.0/goini-linux-amd64
           --output /usr/local/bin/goini
    - chmod +x /usr/local/bin/goini
  script:
    - goini -ini config/service.ini -are-sections-present app,database || exit 1
    - goini -ini config/service.ini -section database -has-section-key host || exit 1
    - goini -ini config/service.ini -section app -key env -has-section-key-value staging || exit 1

Enter fullscreen mode Exit fullscreen mode


Complete Flag Reference

Flag Type Description
-ini string Required. Path to the .ini file
-section string Section name for scoped operations
-sections bool List all sections in the file
-add-section string Add a new section. Fails if already present
-has-section string Exit 0 if section exists, 1 if not
-has-section-key string Exit 0 if key exists in -section
-has-section-key-value string Exit 0 if key in -section equals this value. Requires -key
-are-sections-present string Comma-separated list. Exit 0 if all present
-key string Key name for value operations
-value string Value for write or comparison operations
-add-key bool Add -key=-value to -section. Fails if key exists
-modify-key bool Update existing -key in -section. Fails if key absent
-list-keys bool Print all keys in -section to stdout
-list-key-values bool Print all key=value pairs in -section to stdout
-csv bool Output as CSV
-json bool Output as JSON
-yaml bool Output as YAML

A Few Things Worth Knowing

-add-key and -modify-key are intentionally separate. You can't accidentally overwrite an existing value with -add-key — it refuses. You can't accidentally create a new key with -modify-key — it refuses. They each have one job and one failure mode. That's the point.

-add-section is idempotent-safe. Adding a section that already exists exits with code 1. In a pipeline where you need true idempotency, pipe it with || true. In a pipeline where you want to catch unexpected state, let it fail.

-are-sections-present takes a comma-separated list. All sections must be present for exit code 0. One missing section fails the whole check.

Output format flags work with read operations. -csv, -json, and -yaml apply to -sections, -list-keys, and -list-key-values. They don't apply to write operations — those just use the exit code.

Every write operation reads the file, modifies the result in memory, and writes it back. There's no in-place line editing. This means the file that comes out is clean and predictable regardless of what formatting the original had.


The source and releases are at github.com/andreimerlescu/goini. Apache 2.0.