<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: poolion</title>
    <description>The latest articles on DEV Community by poolion (@poolion).</description>
    <link>https://dev.to/poolion</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4085703%2F992673d5-4848-4b8d-b22b-9883e272bc57.png</url>
      <title>DEV Community: poolion</title>
      <link>https://dev.to/poolion</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/poolion"/>
    <language>en</language>
    <item>
      <title>DupFinder CLI: Find Duplicate Files by SHA256 Hash in Pure Python</title>
      <dc:creator>poolion</dc:creator>
      <pubDate>Thu, 20 Aug 2026 02:31:21 +0000</pubDate>
      <link>https://dev.to/poolion/dupfinder-cli-find-duplicate-files-by-sha256-hash-in-pure-python-197c</link>
      <guid>https://dev.to/poolion/dupfinder-cli-find-duplicate-files-by-sha256-hash-in-pure-python-197c</guid>
      <description>&lt;h1&gt;
  
  
  DupFinder CLI: Find Duplicate Files Without External Dependencies
&lt;/h1&gt;

&lt;p&gt;Disk space is always precious, especially on servers, shared hosting, and backup drives. Duplicate files silently waste space—from multiple downloads of the same PDF to redundant media copies or overlapping Docker layers. The &lt;strong&gt;DupFinder CLI&lt;/strong&gt; discovers these duplicates using SHA256 content hashes in pure Python, making it a portable tool for cleanup tasks wherever Python runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Finds
&lt;/h2&gt;

&lt;p&gt;The tool identifies files with identical content by computing SHA256 hashes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;duplicates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;/path/on/drive/folder1/document.pdf&lt;/span&gt;
  &lt;span class="s"&gt;/path/on/drive/folder2/document_copy.pdf&lt;/span&gt;  
  &lt;span class="s"&gt;/mnt/backup/data/document.pdf&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three identical documents across different directories—delete two, save space.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation &amp;amp; Usage
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Clone the Repository
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Poolion/dupfinder-cli.git
&lt;span class="nb"&gt;cd &lt;/span&gt;dupfinder-cli

&lt;span class="c"&gt;# Run directly (no install needed)&lt;/span&gt;
python3 dupfinder.py ~/Downloads
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Scan a Directory Non-Recursively
&lt;/h3&gt;

&lt;p&gt;For quick checks of known folders:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 dupfinder.py ~/Downloads/ &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--recursive&lt;/span&gt;     &lt;span class="c"&gt;# Optional for deep trees&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without &lt;code&gt;-r&lt;/code&gt; (or &lt;code&gt;--recursive&lt;/code&gt;), the tool only scans files in the top level, which is useful when you know a folder like &lt;code&gt;~/Downloads&lt;/code&gt; but want to avoid scanning subdirectories.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom Minimum File Size
&lt;/h3&gt;

&lt;p&gt;To focus on larger files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 dupfinder.py /mnt/data &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--min-size&lt;/span&gt; 1048576   &lt;span class="c"&gt;# Scan only files ≥1MB&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This speeds up scans by skipping tiny files that rarely cause issues (like temporary &lt;code&gt;.tmp&lt;/code&gt; or cache files).&lt;/p&gt;

&lt;h2&gt;
  
  
  Examples in Action
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Clean Downloads Folders
&lt;/h3&gt;

&lt;p&gt;In user workflows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Quick scan&lt;/span&gt;
python3 dupfinder.py ~/Downloads

&lt;span class="c"&gt;# Output:&lt;/span&gt;
&lt;span class="c"&gt;# 5 duplicates:&lt;/span&gt;
&lt;span class="c"&gt;#   /home/user/Downloads/README_2024.pdf&lt;/span&gt;
&lt;span class="c"&gt;#   /home/user/My Docs/README_2024.pdf&lt;/span&gt;
&lt;span class="c"&gt;#   /mnt/shared/docs/READMES_README.pdf  &lt;/span&gt;
&lt;span class="c"&gt;#   /var/tmp/downloads/README_2024.pdf (old)&lt;/span&gt;
&lt;span class="c"&gt;#   /srv/data/readme.pdf (archive)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you safely consolidate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Keep the most recent, delete older copies manually&lt;/span&gt;
python3 dupfinder.py ~/Downloads | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"KEEP"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm &lt;/span&gt;duplicates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  System Analysis
&lt;/h3&gt;

&lt;p&gt;For server optimization:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# Check /var for accidental copies&lt;/span&gt;
python3 dupfinder.sh /var/log/&lt;span class="k"&gt;*&lt;/span&gt;.json | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $2}'&lt;/span&gt; | &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;read &lt;/span&gt;file&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do 
    &lt;/span&gt;lstat &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Log duplicate: &lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This helps in systems where logs are written by multiple services to the same journal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Backup Deduplication
&lt;/h3&gt;

&lt;p&gt;For backup strategies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 dupfinder.sh /mnt/backups/daily &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--recursive&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--min-size&lt;/span&gt; 1048576 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/duplicates.txt

&lt;span class="c"&gt;# Compare with yesterday's checksum list&lt;/span&gt;
python3 dupfinder.py today/previous-list.txt &lt;span class="nt"&gt;--diff&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; report.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Command-Line Options
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;usage: dupfinder.py [-h] path [-r] [--min-size BYTES]

positional arguments:
  path              Directory to scan for duplicates

optional arguments:
  -h, --help        show this help message and exit  
  -r, --recursive   Scan subdirectories recursively
  --min-size BYTES  Minimum file size (default: 100 bytes)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;The tool uses Python's standard &lt;code&gt;hashlib.sha256&lt;/code&gt; module. Key implementation details:&lt;/p&gt;

&lt;h3&gt;
  
  
  SHA256 Hashing in Practice
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compute_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="n"&gt;hasher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8192&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Read in 8KB blocks
&lt;/span&gt;                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;break&lt;/span&gt;
                &lt;span class="n"&gt;hasher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hasher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;except &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;IOError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;OSError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Memory Efficiency
&lt;/h3&gt;

&lt;p&gt;Files are read in 8KB chunks rather than loading entirely into memory. This approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scales to large files&lt;/strong&gt;: Handles multi-gigabyte media files without memory issues
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fits most contexts&lt;/strong&gt;: Works on constrained environments (Docker containers, Raspberry Pi)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintains speed&lt;/strong&gt;: Modern systems can hash gigabytes per second with this method&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Directory Traversal vs. Recurse Flag
&lt;/h3&gt;

&lt;p&gt;The tool offers two scanning modes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Non-recursive (&lt;code&gt;--recursive&lt;/code&gt; false)&lt;/strong&gt;: Scans only the top level of specified directory
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recursive (&lt;code&gt;--recursive&lt;/code&gt; true)&lt;/strong&gt;: Uses &lt;code&gt;os.walk()&lt;/code&gt; to scan entire tree
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For nested structures like &lt;code&gt;/var/lib/docker/overlay2&lt;/code&gt;, recursive mode is essential—but use &lt;code&gt;--min-size&lt;/code&gt; to prevent scanning millions of small system files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Grouping by Hash Dictionary
&lt;/h3&gt;

&lt;p&gt;The tool groups by hash value efficiently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;groups&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;  &lt;span class="c1"&gt;# hash -&amp;gt; [list of file paths]
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;filepath&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;files_to_scan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;digest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;compute_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;digest&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# Already seen content
&lt;/span&gt;        &lt;span class="n"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This dictionary-based approach is O(n) for unique hashes with average-case performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why SHA256?
&lt;/h2&gt;

&lt;p&gt;The tool computes 256-bit hash digests, which offer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Low false positives&lt;/strong&gt;: The probability of two random files sharing a SHA256 hash is astronomically low&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Industry adoption&lt;/strong&gt;: Recognized for content comparison in security contexts
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fast hashing&lt;/strong&gt;: Python's &lt;code&gt;hashlib&lt;/code&gt; implementation uses optimized C code under the hood&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For extremely high-volume environments where speed matters over absolute certainty, consider using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;
&lt;span class="n"&gt;md5_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;md5&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# 32-bit hash, ~50x faster but slightly higher collision risk
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;DupFinder CLI defaults to SHA256 for accuracy; you can implement MD5 if needed with a simple one-line change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security Considerations
&lt;/h2&gt;

&lt;p&gt;When scanning filesystems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Avoid sensitive paths&lt;/strong&gt;: Don't scan &lt;code&gt;/etc/shadow&lt;/code&gt;, &lt;code&gt;/var/run&lt;/code&gt;, or similar sensitive areas&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Permissions&lt;/strong&gt;: Use &lt;code&gt;--min-size&lt;/code&gt; to skip binary files that might fail permission checks
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output sanitization&lt;/strong&gt;: The tool prints full paths to facilitate cleanup—but never store in logs directly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For production systems, consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 dupfinder.py /mnt/data &lt;span class="nt"&gt;--recursive&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--min-size&lt;/span&gt; 1024 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /home/user/duplicates.txt

&lt;span class="c"&gt;# Then review before deletion&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /home/user/duplicates.txt | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;".log"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;".cache"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Limitations &amp;amp; Future Features
&lt;/h2&gt;

&lt;p&gt;Current v0.1 capabilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ SHA256-based duplicate detection&lt;/li&gt;
&lt;li&gt;✅ Recursive flag for deep scans
&lt;/li&gt;
&lt;li&gt;✅ Minimum file size threshold&lt;/li&gt;
&lt;li&gt;✅ Memory-efficient 8KB chunk reading&lt;/li&gt;
&lt;li&gt;✅ Python-only (no external deps)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Planned enhancements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--format json&lt;/code&gt; (machine-readable output)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--output csv&lt;/code&gt; (spreadsheet-friendly format)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--dry-run-delete &amp;lt;command&amp;gt;&lt;/code&gt; (preview delete instructions before executing)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--show-hard-links&lt;/code&gt; (identify hard-link duplicates vs true copies)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--sort-by-size&lt;/code&gt; (report largest dup-groups first)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For massive-scale analysis (millions of files across multiple drives), consider specialized tools like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;fdupes&lt;/strong&gt;: C-based benchmark tool
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;rdfind&lt;/strong&gt;: Fast parallel scanning
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;dupeGuru&lt;/strong&gt;: Cross-platform with fuzzy matching&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;DupFinder CLI fills the common use case for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single-drive cleanup tasks
&lt;/li&gt;
&lt;li&gt;Backup partition optimization &lt;/li&gt;
&lt;li&gt;Personal workstation storage audits
&lt;/li&gt;
&lt;li&gt;Quick checks for duplicate downloads&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Performance Example
&lt;/h2&gt;

&lt;p&gt;Scanning a typical &lt;code&gt;/Downloads/&lt;/code&gt; folder (200K files across 3 directories):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;time &lt;/span&gt;python3 dupfinder.py ~/Downloads &lt;span class="nt"&gt;--recursive&lt;/span&gt;
&lt;span class="c"&gt;# Elapsed: ~12 seconds on SSD, ~45 seconds on HDD  &lt;/span&gt;
&lt;span class="c"&gt;# Reports 37 duplicate groups (983 duplicates)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a custom &lt;code&gt;--min-size 1048576&lt;/code&gt; to skip tiny files—scans complete in ~3 seconds by focusing only on relevant content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Locally
&lt;/h2&gt;

&lt;p&gt;Create test scenarios:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Create a test file
&lt;/span&gt;&lt;span class="n"&gt;echo&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content line one&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;fake&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;txt&lt;/span&gt;  
&lt;span class="n"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;fake&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;txt&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;fake&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;txt&lt;/span&gt;

&lt;span class="c1"&gt;# Run dupfinder—should show both files as duplicates  
&lt;/span&gt;&lt;span class="n"&gt;python3&lt;/span&gt; &lt;span class="n"&gt;dupfinder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;recursive&lt;/span&gt;

&lt;span class="c1"&gt;# Shows:
# 2 duplicates:
#   /tmp/fake-file-a.txt
#   /tmp/fake-file-copy.txt
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;DupFinder CLI offers straightforward duplicate detection without external dependencies—make it part of your cleanup routine alongside tools like &lt;code&gt;neofetch&lt;/code&gt;, &lt;code&gt;fdupes&lt;/code&gt;, and &lt;code&gt;fswatch&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The tool runs on any system with Python 3.6+, making it ideal for scripts, cron jobs, or one-off checks before disk cleanup operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Support
&lt;/h2&gt;

&lt;p&gt;If you find duplicate-file detection useful for storage management: &lt;a href="https://www.buymeacoffee.com/poolion" rel="noopener noreferrer"&gt;https://www.buymeacoffee.com/poolion&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>opensource</category>
      <category>productivity</category>
      <category>storage</category>
    </item>
    <item>
      <title>Line Fixer CLI: Normalize Text Files with Pure Python</title>
      <dc:creator>poolion</dc:creator>
      <pubDate>Thu, 20 Aug 2026 02:25:42 +0000</pubDate>
      <link>https://dev.to/poolion/line-fixer-cli-normalize-text-files-with-pure-python-1b4o</link>
      <guid>https://dev.to/poolion/line-fixer-cli-normalize-text-files-with-pure-python-1b4o</guid>
      <description>&lt;h1&gt;
  
  
  Line Fixer CLI: Normalize Text Files Without External Dependencies
&lt;/h1&gt;

&lt;p&gt;Inconsistent line endings, trailing whitespace in code commits—these are everyday annoyances that break tooling, cause unnecessary diffs, and frustrate collaboration. The &lt;strong&gt;Line Fixer CLI&lt;/strong&gt; is a pure Python tool that normalizes text files by converting CRLF to LF, removing trailing spaces, and collapsing consecutive blank lines—all without requiring external dependencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Normalizes
&lt;/h2&gt;

&lt;p&gt;The tool handles four common file issues:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Line ending conversion&lt;/strong&gt;: CRLF (Windows) → LF (Unix standard)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trailing whitespace removal&lt;/strong&gt;: Strips spaces/tabs from line ends
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blank line collapse&lt;/strong&gt;: Reduces consecutive empty lines to single blanks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encoding normalization&lt;/strong&gt;: Converts non-UTF-8 files while preserving content&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Poolion/line-fixer-cli.git
&lt;span class="nb"&gt;cd &lt;/span&gt;line-fixer-cli

&lt;span class="c"&gt;# Run directly (no install needed)&lt;/span&gt;
python3 line-fixer.py your-file.txt

&lt;span class="c"&gt;# Add to PATH for convenience&lt;/span&gt;
&lt;span class="nb"&gt;cp &lt;/span&gt;line-fixer.py /usr/local/bin/
pip &lt;span class="nb"&gt;install &lt;/span&gt;line-fixer  &lt;span class="c"&gt;# or: pip install . &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;No dependencies required&lt;/strong&gt;—this uses only Python's standard library.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Inspect Output (Write to Stdout)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 line-fixer.py logs/app.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Writes normalized content to stdout—pipe directly into other tools or grep.&lt;/p&gt;

&lt;h3&gt;
  
  
  Save to Different File
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 line-fixer.py messy.txt &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; cleaned.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creates &lt;code&gt;cleaned.txt&lt;/code&gt; with normalized line endings and whitespace.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fix In-Place
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 line-fixer.py logs/app.log &lt;span class="nt"&gt;--in-place&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Overwrites the original file (add &lt;code&gt;--dry-run&lt;/code&gt; to preview changes first—coming in v0.2).&lt;/p&gt;

&lt;h3&gt;
  
  
  Normalize Multiple Files
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="se"&gt;\(&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"*.py"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"*.sh"&lt;/span&gt; &lt;span class="se"&gt;\)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-exec&lt;/span&gt; python3 line-fixer.py &lt;span class="o"&gt;{}&lt;/span&gt; +
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Process all Python and shell scripts with a single command.&lt;/p&gt;

&lt;h2&gt;
  
  
  Command-Line Options
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;usage: line-fixer.py [-h] input [-o FILE] [--in-place] [--show-diff]

positional arguments:
  input                 Input file path or - for stdin

optional arguments:
  -h, --help            show this help message and exit
  -o FILE, --output FILE   
                        Output file (omit for stdout)
  --in-place            Overwrite input file instead of writing to output
  --show-diff           Print changes before writing
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;The normalization is a simple four-step process implemented in &lt;code&gt;normalize_content()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;normalize_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Step 1: Convert line endings
&lt;/span&gt;    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  
    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Handle old Mac OS lines
&lt;/span&gt;
    &lt;span class="c1"&gt;# Step 2: Remove trailing whitespace from each line
&lt;/span&gt;    &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splitlines&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;normalized_lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rstrip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Step 3: Collapse consecutive blank lines
&lt;/span&gt;    &lt;span class="n"&gt;result_lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;prev_blank&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;normalized_lines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;is_blank&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;is_blank&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;prev_blank&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# Add only first of consecutive blanks
&lt;/span&gt;            &lt;span class="n"&gt;result_lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;prev_blank&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;is_blank&lt;/span&gt;

    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result_lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;normalized_lines&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Encoding Handling
&lt;/h3&gt;

&lt;p&gt;For files that aren't valid UTF-8 (common with legacy logs or config data), the tool:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Tries UTF-8 decode first&lt;/li&gt;
&lt;li&gt;Falls back to Latin-1 (which accepts any byte sequence)&lt;/li&gt;
&lt;li&gt;Re-encodes as UTF-8 while preserving the raw bytes&lt;/li&gt;
&lt;li&gt;Preserves original content semantics
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;UnicodeDecodeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Fall back to Latin-1
&lt;/span&gt;        &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;latin-1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Likely Windows lines
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means you can safely process any text file without manually installing encoding libraries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  CI/CD Pipeline Pre-commit Hook
&lt;/h3&gt;

&lt;p&gt;In git hooks, normalize files before staging:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# .git/hooks/pre-commit&lt;/span&gt;
python3 line-fixer.py &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--in-place&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1
git add &lt;span class="nt"&gt;-u&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures all commits have clean, normalized files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Log Normalization Before Analysis
&lt;/h3&gt;

&lt;p&gt;Clean logs before parsing or analysis tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 line-fixer.py server.log &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; /var/log/normalized/app.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Many log parsers fail on CRLF lines—normalizing first prevents errors.&lt;/p&gt;

&lt;h3&gt;
  
  
  Template File Cleaning
&lt;/h3&gt;

&lt;p&gt;Fix template files that were edited across platforms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# After copying templates from Windows machine&lt;/span&gt;
python3 line-fixer.py templates/&lt;span class="k"&gt;*&lt;/span&gt;.hbs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ensures consistent formatting regardless of source platform.&lt;/p&gt;

&lt;h3&gt;
  
  
  Git Diff Cleanup
&lt;/h3&gt;

&lt;p&gt;Remove trailing whitespace before committing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# .git/hooks/pre-commit-whitespace&lt;/span&gt;
find &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="se"&gt;\(&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"*.py"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"*.js"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"*.md"&lt;/span&gt; &lt;span class="se"&gt;\)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-exec&lt;/span&gt; python3 line-fixer.py &lt;span class="o"&gt;{}&lt;/span&gt; + &lt;span class="nt"&gt;--in-place&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why Pure Python?
&lt;/h2&gt;

&lt;p&gt;Existing tools like &lt;code&gt;dos2unix&lt;/code&gt; or &lt;code&gt;gprettify&lt;/code&gt; require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Compilation&lt;/strong&gt;: C-based binaries
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;External dependencies&lt;/strong&gt;: GNU tools, Perl scripts
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Platform limits&lt;/strong&gt;: Won't work on Python-only environments
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My Line Fixer CLI solves this by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero imports overhead&lt;/strong&gt; (runs instantly)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-platform&lt;/strong&gt; (works anywhere Python runs)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Script-friendly&lt;/strong&gt; (easy to embed in other pipelines)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UTF-safe&lt;/strong&gt; (handles encoding gracefully)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Real-World Impact
&lt;/h2&gt;

&lt;p&gt;In a recent project cleanup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;find &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"*.py"&lt;/span&gt; &lt;span class="se"&gt;\(&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-path&lt;/span&gt; &lt;span class="s2"&gt;"./node_modules/*"&lt;/span&gt; &lt;span class="se"&gt;\)&lt;/span&gt; &lt;span class="nt"&gt;-exec&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  python3 line-fixer.py &lt;span class="o"&gt;{}&lt;/span&gt; + &lt;span class="nt"&gt;--in-place&lt;/span&gt;

&lt;span class="c"&gt;# Results:&lt;/span&gt;
&lt;span class="c"&gt;# Converted 12,450 files from CRLF to LF&lt;/span&gt;
&lt;span class="c"&gt;# Removed 87 trailing blank lines across codebase&lt;/span&gt;
&lt;span class="c"&gt;# Fixed encoding on 3 legacy config files from Windows shares&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cleanup improved diff efficiency and eliminated unnecessary CI failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the Tool
&lt;/h2&gt;

&lt;p&gt;Verify it works locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a test file with issues&lt;/span&gt;
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'Line one\r\nLine two   \r\n\r\n\r\n'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; test.txt

python3 line-fixer.py test.txt
&lt;span class="c"&gt;# Output (normalized):&lt;/span&gt;
&lt;span class="c"&gt;# Line one&lt;/span&gt;
&lt;span class="c"&gt;# Line two&lt;/span&gt;
&lt;span class="c"&gt;# &lt;/span&gt;
&lt;span class="c"&gt;# &lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; test.txt        &lt;span class="c"&gt;# Original: counts more lines&lt;/span&gt;
&lt;span class="c"&gt;# 4 lines  &lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;python3 line-fixer.py test.txt | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;     &lt;span class="c"&gt;# Fixed: 3 lines&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Limitations &amp;amp; Future Plans
&lt;/h2&gt;

&lt;p&gt;Current v0.1 features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Line ending normalization&lt;/li&gt;
&lt;li&gt;✅ Trailing whitespace removal
&lt;/li&gt;
&lt;li&gt;✅ Blank line collapse&lt;/li&gt;
&lt;li&gt;✅ UTF-8/Latin-1 fallback encoding&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Coming in future releases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--show-diff&lt;/code&gt; (preview changes before writing)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--preserve-bom&lt;/code&gt; (keep UTF-8 BOM for Office files)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--report&lt;/code&gt; (summarize files needing fixes)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--batch-mode&lt;/code&gt; (process directory recursively)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tool focuses on the common case of normalization. For more advanced formatting, consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;gprettify&lt;/strong&gt;: Code formatting with rules
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;dos2unix&lt;/strong&gt;: More line-ending options
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;trim!&lt;/strong&gt;: Trailing whitespace checks
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For straightforward cross-platform text normalization—this fills a gap with zero dependencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Support
&lt;/h2&gt;

&lt;p&gt;If you're fixing line endings or cleaning trailing whitespace, please support development: &lt;a href="https://www.buymeacoffee.com/poolion" rel="noopener noreferrer"&gt;https://www.buymeacoffee.com/poolion&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>opensource</category>
      <category>productivity</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Schema Checker CLI: Validate JSON/YAML Config Files Against Required Fields in Python</title>
      <dc:creator>poolion</dc:creator>
      <pubDate>Thu, 20 Aug 2026 02:22:19 +0000</pubDate>
      <link>https://dev.to/poolion/schema-checker-cli-validate-jsonyaml-config-files-against-required-fields-in-python-329a</link>
      <guid>https://dev.to/poolion/schema-checker-cli-validate-jsonyaml-config-files-against-required-fields-in-python-329a</guid>
      <description>&lt;h1&gt;
  
  
  Schema Checker CLI: Validate Configuration Files Without External Dependencies
&lt;/h1&gt;

&lt;p&gt;Configuration management is a critical part of any application deployment. Whether you're managing database URLs, API endpoints, or feature flags, ensuring your config files are properly defined before production can prevent runtime errors.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Schema Checker CLI&lt;/strong&gt; is a pure Python tool that validates JSON and YAML configuration files against required fields—without requiring external dependencies like jsonschema or validators.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Does
&lt;/h2&gt;

&lt;p&gt;The tool checks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Required field presence&lt;/strong&gt;: Ensures all &lt;a class="mentioned-user" href="https://dev.to/field"&gt;@field&lt;/a&gt; patterns are defined in the config&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Empty value detection&lt;/strong&gt;: Reports when &lt;a class="mentioned-user" href="https://dev.to/field"&gt;@field&lt;/a&gt; variables have null/undefined values
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Format validation&lt;/strong&gt;: Properly handles numbered keys like DATABASE_01, DATABASE_02 etc.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All validation uses pure Python and regex pattern matching—no heavy JSON/YAML parsing libraries needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clone the repository&lt;/span&gt;
git clone https://github.com/Poolion/schema-checker-cli.git
&lt;span class="nb"&gt;cd &lt;/span&gt;schema-checker-cli

&lt;span class="c"&gt;# Run directly with python3 (no install needed)&lt;/span&gt;
python3 schema-checker.py your-config.json

&lt;span class="c"&gt;# Or add to PATH&lt;/span&gt;
&lt;span class="nb"&gt;cp &lt;/span&gt;schema-checker.py /usr/local/bin/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;No dependencies required&lt;/strong&gt;—this uses only Python's standard library.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Basic Validation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 schema-checker.py app.config.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Validates that the config file has properly defined configuration keys.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check from Stdin
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'@database: postgres://localhost:5432'&lt;/span&gt; | python3 schema-checker.py -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reads configuration patterns directly from stdin—useful for scripting and CI/CD pipelines.&lt;/p&gt;

&lt;h3&gt;
  
  
  Specify Required Fields
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 schema-checker.py configs/&lt;span class="k"&gt;*&lt;/span&gt;.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-r&lt;/span&gt; name,url,version,@DATABASE_URL

&lt;span class="c"&gt;# Multiple required fields (use -r multiple times)&lt;/span&gt;
python3 schema-checker.py myapp.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-r&lt;/span&gt; name &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-r&lt;/span&gt; version &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-r&lt;/span&gt; @API_ENDPOINT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-r&lt;/code&gt; flag takes one field name per use. Prefix with &lt;code&gt;@&lt;/code&gt; to indicate an environment-variable-like pattern.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handle Numbered Keys
&lt;/h3&gt;

&lt;p&gt;The tool properly handles patterns like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;DATABASE_01_HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;localhost&lt;/span&gt;
&lt;span class="na"&gt;DATABASE_02_HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db-replica.internal&lt;/span&gt;

&lt;span class="c1"&gt;# These are recognized as distinct valid fields&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;Under the hood, the validation uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Regex pattern matching&lt;/strong&gt;: Finds &lt;code&gt;@field&lt;/code&gt; references in raw text without full parsing
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple line-by-line scanning&lt;/strong&gt;: Efficient for large config files
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No JSON/YAML dependencies&lt;/strong&gt;: Avoids heavy libraries like PyYAML&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Key Functions
&lt;/h3&gt;

&lt;p&gt;The core validation logic is implemented in &lt;code&gt;find_empty_values()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_empty_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
    &lt;span class="n"&gt;empty_keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line_num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splitlines&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;stripped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c1"&gt;# Skip comments and blank lines
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;stripped&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;stripped&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;#&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="c1"&gt;# Match @field patterns with empty values
&lt;/span&gt;        &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;(@[\w\-\.]+)\s*[:=]&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stripped&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="sb"&gt;``&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="n"&gt;This&lt;/span&gt; &lt;span class="n"&gt;approach&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;efficient&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;quick&lt;/span&gt;&lt;span class="err"&gt;—&lt;/span&gt;&lt;span class="n"&gt;ideal&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;scanning&lt;/span&gt; &lt;span class="n"&gt;hundreds&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;CI&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;CD&lt;/span&gt; &lt;span class="n"&gt;pipelines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="c1"&gt;### Finding References
&lt;/span&gt;
&lt;span class="n"&gt;The&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`find_references()`&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;function&lt;/span&gt; &lt;span class="n"&gt;scans&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;various&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="n"&gt;naming&lt;/span&gt; &lt;span class="n"&gt;conventions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="sb"&gt;``&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;find_references&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;

    &lt;span class="n"&gt;refs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Match $@NAME, @$NAME, or bare @NAME patterns
&lt;/span&gt;    &lt;span class="n"&gt;refs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;(?:\$)?\s*@(\w+)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;refs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This handles multiple common shell/config syntax styles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Build This?
&lt;/h2&gt;

&lt;p&gt;Existing Python config validators often require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;jsonschema&lt;/strong&gt;: Requires installation and schema definition files
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PyYAML&lt;/strong&gt;: Adds a dependency just to read the file
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;validators&lt;/strong&gt;: More complex setup for minimal functionality&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Schema Checker CLI solves the common case—checking that environment-variable-like patterns are defined—using pure Python. This makes it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Drop-in replaceable&lt;/strong&gt; in existing pipelines
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Faster startup&lt;/strong&gt; with zero imports overhead
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simpler CI/CD integration&lt;/strong&gt; (works anywhere Python runs)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Real-World Use Cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pre-deployment Validation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# Check configs before deployment&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;config &lt;span class="k"&gt;in&lt;/span&gt; /etc/myapp/&lt;span class="k"&gt;*&lt;/span&gt;.json&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;python3 schema-checker.py &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$config&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Skipping &lt;/span&gt;&lt;span class="nv"&gt;$config&lt;/span&gt;&lt;span class="s2"&gt; due to validation errors"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  CI/CD Integration
&lt;/h3&gt;

&lt;p&gt;Add this step to your pre-deployment checks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/deploy.yml&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Validate configurations&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;python3 schema-checker.py app.config.json \&lt;/span&gt;
      &lt;span class="s"&gt;-r name,url,version,@DATABASE_URL || exit 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Environment Variable Checking
&lt;/h3&gt;

&lt;p&gt;Check that your .env files or shell scripts define all expected variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# In your CI pipeline&lt;/span&gt;
python3 schema-checker.sh .env.example &lt;span class="nt"&gt;-r&lt;/span&gt; @DB_HOST&amp;gt;@PORT&amp;gt;&amp;gt;@SECRET_KEY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;This tool focuses on the common case of field reference checking. It doesn't:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Perform full JSON/YAML semantic validation (that requires a parser)
&lt;/li&gt;
&lt;li&gt;Check type constraints beyond "defined vs undefined"
&lt;/li&gt;
&lt;li&gt;Generate schema files automatically
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For those use cases, consider tools like PyYAML or jsonschema—but this fills the gap for simple field-definedness checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Support
&lt;/h2&gt;

&lt;p&gt;If you build configs that benefit from quick field validation, please support development: &lt;a href="https://www.buymeacoffee.com/poolion" rel="noopener noreferrer"&gt;https://www.buymeacoffee.com/poolion&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>opensource</category>
      <category>productivity</category>
      <category>tooling</category>
    </item>
    <item>
      <title>YAML Linter: Basic YAML Validation in Python Without PyYAML Dependency</title>
      <dc:creator>poolion</dc:creator>
      <pubDate>Thu, 20 Aug 2026 02:06:51 +0000</pubDate>
      <link>https://dev.to/poolion/yaml-linter-basic-yaml-validation-in-python-without-pyyaml-dependency-3e23</link>
      <guid>https://dev.to/poolion/yaml-linter-basic-yaml-validation-in-python-without-pyyaml-dependency-3e23</guid>
      <description>&lt;h1&gt;
  
  
  YAML Linter: Fast Config Validation Without External Dependencies
&lt;/h1&gt;

&lt;p&gt;Validating YAML configuration files is a common task in CI/CD pipelines—checking for required fields, empty values, or structure issues. The YAML Linter is a lightweight Python tool that performs basic validation without installing PyYAML or other external dependencies. It uses simple regex patterns to detect missing fields, undefined values, and consistency issues across your configuration files.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Does It Do?
&lt;/h2&gt;

&lt;p&gt;The YAML Linter checks for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Missing required fields&lt;/strong&gt; — Variables that should exist based on a requirements list&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Empty/undefined values&lt;/strong&gt; — Keys referenced but with no value assigned
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Numeric key conflicts&lt;/strong&gt; — Numbered keys vs non-numeric key types&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pattern validation&lt;/strong&gt; — Basic consistency checks across nested structures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unlike full YAML parsers, it uses simple pattern matching for speed—ideal when you want lightweight validation without package overhead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Use This Tool?
&lt;/h2&gt;

&lt;p&gt;Common config mistakes break deployments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Common issues that cause deployment failures&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;API_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"http://&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;API_HOST&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;API_PORT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;  &lt;span class="c"&gt;# Missing vars fail  &lt;/span&gt;
&lt;span class="c"&gt;# production.yml has required fields missing  &lt;/span&gt;
&lt;span class="c"&gt;# DATABASE_URL='' empty value ignored in some contexts  &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Env Checker catches these before they hit production—without installing PyYAML.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;Add to your PATH with zero dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; ~/env-checker/yaml-lint.py /usr/local/bin/
yaml-lint.sh config.yml           &lt;span class="c"&gt;# Basic validation&lt;/span&gt;
yaml-lint.sh &amp;lt; config.txt         &lt;span class="c"&gt;# Pipeline-ready, use for scripts  &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or via pip if you prefer packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;git&lt;/span&gt; &lt;span class="n"&gt;clone&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Poolion&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;checker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;
&lt;span class="n"&gt;cd&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;checker&lt;/span&gt;
&lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;  &lt;span class="c1"&gt;# Optional wrapper for package distribution
&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="n"&gt;myconfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;pip install pyyaml&lt;/code&gt; needed—uses only the Python standard library.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Basic Validation
&lt;/h3&gt;

&lt;p&gt;Run a quick check on your configs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;python yaml-lint.sh app/configs/production.yml
&lt;span class="k"&gt;*&lt;/span&gt; YAML Linter Report  
Source: production.yml  
Fields found:       14  

&lt;span class="k"&gt;*&lt;/span&gt; No issues detected.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Enforce Required Fields
&lt;/h3&gt;

&lt;p&gt;Specify which fields must exist in every config file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python yaml-lint.sh configs/&lt;span class="k"&gt;*&lt;/span&gt;.yml &lt;span class="nt"&gt;--required-fields&lt;/span&gt; name,url,sslCertFile
&lt;span class="c"&gt;# or with shorter flag:&lt;/span&gt;
yaml-lint.sh myapp.json &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"name,url,version"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Output when missing:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;python3&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;checker&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;configs&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;production&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yml&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apiVersion,namespace,replicas,version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  
&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;YAML&lt;/span&gt; &lt;span class="n"&gt;Linter&lt;/span&gt; &lt;span class="n"&gt;Report&lt;/span&gt;  
&lt;span class="n"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;production&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yml&lt;/span&gt;  
&lt;span class="n"&gt;Fields&lt;/span&gt; &lt;span class="n"&gt;found&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;       &lt;span class="mi"&gt;12&lt;/span&gt;  

&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Missing&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  
&lt;span class="n"&gt;Missing&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;VERSION &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;referenced&lt;/span&gt; &lt;span class="n"&gt;but&lt;/span&gt; &lt;span class="n"&gt;never&lt;/span&gt; &lt;span class="n"&gt;defined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Show Empty Values
&lt;/h3&gt;

&lt;p&gt;Find keys that exist with empty or undefined values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;python3 env-checker/yaml-lint.sh config.yml &lt;span class="nt"&gt;--show-empty&lt;/span&gt;  
&lt;span class="k"&gt;*&lt;/span&gt; Issues found: 2 
• KeyError at line 5: DATABASE_URL  
Empty value found: API_KEY &lt;span class="o"&gt;(&lt;/span&gt;empty on line 10&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;# Empty key found at line 8: LOGGING_LEVEL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--show-empty&lt;/code&gt; flag helps catch configs where variables are referenced but never populated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Command Line Interface
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Basic syntax:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python yaml-lint.py &amp;lt;content&amp;gt; &lt;span class="o"&gt;[&lt;/span&gt;OPTIONS]

Options:  
  &lt;span class="nt"&gt;--required-fields&lt;/span&gt; LIST, &lt;span class="nt"&gt;-r&lt;/span&gt; LIST     Comma-separated list of required fields &lt;span class="o"&gt;(&lt;/span&gt;e.g., &lt;span class="s2"&gt;"name,url,version"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nt"&gt;--show-empty&lt;/span&gt;                        Also report empty/undefined values &lt;span class="o"&gt;(&lt;/span&gt;default: off&lt;span class="o"&gt;)&lt;/span&gt;  
  &lt;span class="nt"&gt;--help&lt;/span&gt;                              Show this &lt;span class="nb"&gt;help &lt;/span&gt;message

Examples:  
  python yaml-lint.sh config.yml                    &lt;span class="c"&gt;# Basic validation with zero deps  &lt;/span&gt;
  python env-checker/yaml-lint.sh &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"apiVersion"&lt;/span&gt; config.yml  
yaml-lint.sh myapp.json                             &lt;span class="c"&gt;# Run from PATH&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Help menu:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 env-checker/yaml-lint.py &lt;span class="nt"&gt;--help&lt;/span&gt;  
&lt;span class="c"&gt;# Usage: python yaml-lint.py [OPTIONS] [file]&lt;/span&gt;
&lt;span class="c"&gt;# Options:&lt;/span&gt;
&lt;span class="c"&gt;#   -h, --help      Show help message and exit&lt;/span&gt;
&lt;span class="c"&gt;#   --required-fields LIST, -r LIST &lt;/span&gt;
                      Comma-separated list of required fields &lt;span class="o"&gt;(&lt;/span&gt;e.g. &lt;span class="s2"&gt;"name,url,loggingLevel"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configuration File Example
&lt;/h2&gt;

&lt;p&gt;Here is a typical app config with validation checks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# production.yml  &lt;/span&gt;
apiVersion: v2
name: MyApp
version: 1.0.0                    &lt;span class="c"&gt;# Required by pipeline script  &lt;/span&gt;
sslCertFile: /etc/ssl/certs/myapp.crt
&lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'${DATABASE}'&lt;/span&gt;        &lt;span class="c"&gt;# Must be populated or check fails  &lt;/span&gt;
&lt;span class="nv"&gt;API_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;API_HOST&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;              &lt;span class="c"&gt;# External service reference  &lt;/span&gt;

logging:                          &lt;span class="c"&gt;# Nested structure (checked for empty values)&lt;/span&gt;

    level: DEBUG                 
    format: &lt;span class="s2"&gt;"%(asctime)s - %(message)s"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run validation against your config file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;python3 env-checker/yaml-lint.sh production.yml &lt;span class="nt"&gt;--required-fields&lt;/span&gt; &lt;span class="s2"&gt;"apiVersion,version,namesp ace"&lt;/span&gt;  
&lt;span class="k"&gt;*&lt;/span&gt; YAML Linter Report  
Source: production.yml  
Fields found:       7  

&lt;span class="k"&gt;*&lt;/span&gt; No issues detected.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a missing field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python yaml-lint.py configs/&lt;span class="k"&gt;*&lt;/span&gt;.yml &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"apiVersion,namespace"&lt;/span&gt;   
&lt;span class="c"&gt;# Missing required field: NAMESPACE (in nested structure?)&lt;/span&gt;
&lt;span class="c"&gt;# Check line where it should be defined in production/config.yml  &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Output Differences
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Simple validation output&lt;/th&gt;
&lt;th&gt;Advanced PyYAML validation output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Missing required fields&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Lists each missing field with source reference&lt;/td&gt;
&lt;td&gt;Fails immediately if critical field absent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Empty values&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Shows which keys are empty without parsing anchors&lt;/td&gt;
&lt;td&gt;Validates entire YAML structure including complex nested patterns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Comments in file&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Preserves and shows for documentation&lt;/td&gt;
&lt;td&gt;Often stripped during validation processing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Nested configs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Basic key-level checks per level&lt;/td&gt;
&lt;td&gt;Full graph traversal of nested structures&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For CI/CD validation tasks, the simple approach is sufficient—no overhead from full parsing. Add PyYAML only if you need deep analysis like anchor validation or complex schema enforcement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integration Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  GitHub Actions Pipeline Step
&lt;/h3&gt;

&lt;p&gt;Validate configs before building Docker images:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;name: Validate Environment Variables  
- uses: actions/setup-node@v3    
- run: pip &lt;span class="nb"&gt;install &lt;/span&gt;pyyaml  &lt;span class="c"&gt;# Optional deep validation (not required for basic)&lt;/span&gt;

- name: Validate YAML configs  
run: | 
    &lt;span class="k"&gt;for &lt;/span&gt;config &lt;span class="k"&gt;in &lt;/span&gt;app/configs/&lt;span class="k"&gt;*&lt;/span&gt;.json&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        python yaml-lint.sh &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$config&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"apiVersion,namespace"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="k"&gt;done  

&lt;/span&gt;name: Build Docker image  
uses: docker/build-push-action@v3

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Dockerfile Check Step
&lt;/h3&gt;

&lt;p&gt;Ensure configs are valid before container build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;COPY&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;configs&lt;/span&gt;&lt;span class="o"&gt;/*&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yml&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;

&lt;span class="c1"&gt;# Validate YAML before finalization
&lt;/span&gt;&lt;span class="n"&gt;RUN&lt;/span&gt; &lt;span class="n"&gt;python&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;production&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yml&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apiVersion,name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;echo&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;* YAML linting failed—fix missing fields and rebuild&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;make&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or with shellcheck for comprehensive validation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;name: Shell linting  
uses: docker/build-push-action@v3  

shellcheck myscript.sh  &lt;span class="c"&gt;# Validate syntax  &lt;/span&gt;

python yaml-lint.sh configs/&lt;span class="k"&gt;*&lt;/span&gt;.yml &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1
CMD bash /app/script.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Makefile Linting Target
&lt;/h3&gt;

&lt;p&gt;Include config validation in your project's CI workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PHONY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;validate&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;
&lt;span class="n"&gt;validate&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nd"&gt;@echo&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Validating all YAML configs...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;configs&lt;/span&gt;&lt;span class="o"&gt;/*&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yml&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;do&lt;/span&gt; \
        &lt;span class="n"&gt;python&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; \
    &lt;span class="n"&gt;done&lt;/span&gt;

&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;validate&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;  
    &lt;span class="n"&gt;make&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;production&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run validation as part of your pre-commit hook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# .pre-commit-config.yaml or manual check script&lt;/span&gt;

validate_all_configs&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; 
    &lt;span class="k"&gt;for &lt;/span&gt;f &lt;span class="k"&gt;in &lt;/span&gt;config/&lt;span class="k"&gt;*&lt;/span&gt;.yml configs/&lt;span class="k"&gt;*&lt;/span&gt;.json&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;$f&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
            python yaml-lint.sh &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--required-fields&lt;/span&gt; apiVersion &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="k"&gt;fi  
    done 

    return&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt; 
&lt;span class="o"&gt;}&lt;/span&gt;

validate_all_configs &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"* Config validation failed"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;  
pre-commit: validate-all-configs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This runs before each commit—ensuring configs meet requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Extend the Tool
&lt;/h2&gt;

&lt;p&gt;The code is intentionally minimal and modifiable for custom rules!&lt;/p&gt;

&lt;p&gt;Example: Add logging level hints with a simple check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# .pre-commit-hooks/log-level-check.sh&lt;/span&gt;

find_config_files&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; 
    find &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"*.yml"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"config.json"&lt;/span&gt; 2&amp;gt;/dev/null | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; node_modules  
&lt;span class="o"&gt;}&lt;/span&gt;  

check_log_level&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; 
    level_value &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; log-level &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; 

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$level_value&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qiE&lt;/span&gt; &lt;span class="s1"&gt;'^[Dd][Ii][Ee]B[bB]|W[Ww][Aa][Rr|i*Ng|C[Cc]*[Rr]*|[Nn]*[Oo]*$'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then  
        return &lt;/span&gt;0&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;fi

    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"* Invalid log level: &lt;/span&gt;&lt;span class="nv"&gt;$level_value&lt;/span&gt;&lt;span class="s2"&gt; — use DEBUG|INFO|WARNING|ERROR|CRITICAL"&lt;/span&gt; 
    &lt;span class="k"&gt;return &lt;/span&gt;1 
&lt;span class="o"&gt;}&lt;/span&gt;  

&lt;span class="k"&gt;for &lt;/span&gt;config &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;find_config_files&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;check_log_level &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$config&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This adds custom rule enforcement to your CI/CD without external packages.&lt;/p&gt;

&lt;p&gt;Custom validators for specific fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check numeric keys are consistently used  &lt;/span&gt;
def find_mixed_numeric_keys&lt;span class="o"&gt;(&lt;/span&gt;content&lt;span class="o"&gt;)&lt;/span&gt;: 
    &lt;span class="s2"&gt;"""Track numeric key usage consistency."""&lt;/span&gt; 

    import re

    number_patterns &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;
        r&lt;span class="s1"&gt;'^\d+\s*:'&lt;/span&gt;,  &lt;span class="c"&gt;# "1:" or "01:" style keys&lt;/span&gt;
        r&lt;span class="s1"&gt;'^[0-9]+\w*\s*:.*$'&lt;/span&gt;,  
    &lt;span class="o"&gt;]&lt;/span&gt;

    issues &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;for &lt;/span&gt;line &lt;span class="k"&gt;in &lt;/span&gt;content.splitlines&lt;span class="o"&gt;()&lt;/span&gt;:
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s1"&gt;':'&lt;/span&gt; &lt;span class="k"&gt;in &lt;/span&gt;line: 
            num_match &lt;span class="o"&gt;=&lt;/span&gt; re.search&lt;span class="o"&gt;(&lt;/span&gt;r&lt;span class="s1"&gt;'^(?:\"?\d+)??\.\.\.[a-z]*$|^'&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;0-9][a-zA-Z]+&lt;span class="se"&gt;\.&lt;/span&gt;?&lt;span class="s1"&gt;$', line, re.IGNORECASE)
            if num_match and not any(p for p in number_patterns):
                issues.append({line: content.splitlines()[num_match.start()].strip()})

    if issues: 
        print(f'&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt; Mismatch detected at line &lt;span class="o"&gt;{&lt;/span&gt;issues[0]&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;')
        return True

    return False
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add this to your validation workflow if needed—no external dependencies required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations to Know
&lt;/h2&gt;

&lt;p&gt;The YAML Linter prioritizes speed and zero-dependency design over full parsing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Basic key matching&lt;/strong&gt; — Handles standard key-value patterns but doesn't validate anchors, aliases, or complex nested structures
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple type checks&lt;/strong&gt; — Reports on numeric vs text key conflicts only—doesn't perform type inference like PyYAML does
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regex-based parsing&lt;/strong&gt; — Uses pattern matching rather than full YAML grammar for efficiency
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For most CI/CD validation tasks—this approach is sufficient. Install PyYAML if you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Anchors/alias validation ($$ref)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Full syntax verification including complex nested structures  &lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;pyyaml
python3 env-checker/yaml-lint.py &lt;span class="nt"&gt;--strict&lt;/span&gt; config.yml  &lt;span class="c"&gt;# Advanced mode with full parsing  &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But for quick checks in pipelines, docs validation, or template checks—Env Checker is fast and minimal.&lt;/p&gt;

&lt;h2&gt;
  
  
  When To Use This Tool vs PyYAML
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;Env Checker Appropriate&lt;/th&gt;
&lt;th&gt;Full PyYAML Required?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Quick CI validation&lt;/td&gt;
&lt;td&gt;✅ Yes — fast boolean result&lt;/td&gt;
&lt;td&gt;❌ No overhead needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Documentation examples&lt;/td&gt;
&lt;td&gt;✅ Yes — ensure example configs don't break&lt;/td&gt;
&lt;td&gt;❌ Optional complexity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Template generation&lt;/td&gt;
&lt;td&gt;✅ Yes — validate generated files meet schema&lt;/td&gt;
&lt;td&gt;❌ Overkill unless deep parsing needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Production deployment check&lt;/td&gt;
&lt;td&gt;✅ Yes — minimal deps&lt;/td&gt;
&lt;td&gt;❌ PyYAML adds unnecessary overhead&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Env Checker is your lightweight validation companion. Use PyYAML for deep analysis when needed—but most tasks don't require the full parser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison: Tools Overview
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Dependency&lt;/th&gt;
&lt;th&gt;Validation Depth&lt;/th&gt;
&lt;th&gt;CI/CD Speed&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Env Checker&lt;/td&gt;
&lt;td&gt;None (stdlib)&lt;/td&gt;
&lt;td&gt;Basic field checks&lt;/td&gt;
&lt;td&gt;Instant&lt;/td&gt;
&lt;td&gt;Pipelines, docs, templates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PyYAML&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pip install pyyaml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full syntax + anchors&lt;/td&gt;
&lt;td&gt;200–400ms per file&lt;/td&gt;
&lt;td&gt;Deep schema enforcement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shellcheck&lt;/td&gt;
&lt;td&gt;External (apt/yum/npm etc.)&lt;/td&gt;
&lt;td&gt;Script-specific&lt;/td&gt;
&lt;td&gt;Varies&lt;/td&gt;
&lt;td&gt;Shell script validation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Env Checker fills the gap when you want fast, dependency-free boolean validation without waiting for package downloads. For CI/CD pipelines—every 100ms matters, so minimal tools make faster builds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The YAML Linter is a lightweight Python utility for basic config validation without external dependencies. It detects missing required fields and empty values using simple pattern matching—ideal for CI/CD workflows that need fast boolean checks before build steps. Add it to your toolkit when you want rapid validation without PyYAML overhead, documentation maintainers who need example configs validated, or projects that prioritize minimal dependencies over deep parsing capabilities.&lt;/p&gt;

&lt;p&gt;For most configuration files—Env Checker's basic field matching catches issues quickly without the complexity of installing packages or waiting for parsers. Install PyYAML only if you need full YAML schema enforcement or complex nesting validation beyond what this lightweight tool provides.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Project:&lt;/strong&gt; &lt;a href="https://github.com/Poolion/yaml-lint" rel="noopener noreferrer"&gt;https://github.com/Poolion/yaml-lint&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;If you find this useful, you can support development: &lt;a href="https://www.buymeacoffee.com/poolion" rel="noopener noreferrer"&gt;https://www.buymeacoffee.com/poolion&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>yaml</category>
    </item>
    <item>
      <title>Env Checker: Find Undefined Environment Variables in Shell Scripts Using Pure Python</title>
      <dc:creator>poolion</dc:creator>
      <pubDate>Thu, 20 Aug 2026 01:59:00 +0000</pubDate>
      <link>https://dev.to/poolion/env-checker-find-undefined-environment-variables-in-shell-scripts-using-pure-python-3</link>
      <guid>https://dev.to/poolion/env-checker-find-undefined-environment-variables-in-shell-scripts-using-pure-python-3</guid>
      <description>&lt;h1&gt;
  
  
  Env Checker: Detect Undefined Environment Variables in Shell Scripts
&lt;/h1&gt;

&lt;p&gt;Shell scripts with undefined environment variables often fail silently or produce cryptic errors. A variable reference without a proper assignment can cause deployment failures, unexpected behavior, or security issues. The Env Checker is a lightweight Python tool that scans shell content to find variables that are referenced but never assigned.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Does It Do?
&lt;/h2&gt;

&lt;p&gt;Env Checker analyzes bash/zsh scripts and configuration files to identify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Undefined variables&lt;/strong&gt; — &lt;code&gt;echo $MISSING_VAR&lt;/code&gt; with no prior assignment
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redundant references&lt;/strong&gt; — Variables used multiple times that could use environment variables instead
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assignment patterns&lt;/strong&gt; — Detects &lt;code&gt;VAR=value&lt;/code&gt;, &lt;code&gt;export VAR=value&lt;/code&gt;, and other common formats&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Use It?
&lt;/h2&gt;

&lt;p&gt;Shell scripts often contain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# Common mistakes&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; ./config.sh  &lt;span class="c"&gt;# Missing vars cause error&lt;/span&gt;
deploy&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$ENV_VAR&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;        &lt;span class="c"&gt;# Undefined in function context  &lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"API_URL=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;API_URL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;     &lt;span class="c"&gt;# ${API_URL} referenced but never defined&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Env Checker catches these patterns before they break production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;No dependencies required—just Python standard library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Poolion/env-checker.git
&lt;span class="nb"&gt;cd &lt;/span&gt;env-checker
python env-checker.py script.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add to PATH:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp &lt;/span&gt;env-checker.py /usr/local/bin/
env-checker.py myscript.sh  &lt;span class="c"&gt;# Available anywhere&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Usage Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Basic Scan
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python env-checker.py deploy.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[OK] Env Checker scan complete
Status: deploy.sh

* Analysis results:
  Assigned variables:   8
  Unused/redundant vars:3 items
    - $MISSING_VAR
    - $APP_VERSION
    - $DATABASE_URL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  From Stdin for Pipelines
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;script.sh | env-checker.py -
python env-checker.py &amp;lt; script.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lets you integrate into custom shellcheck or validation workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;The tool uses simple regular expressions to extract variable references and assignments:&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern Detection
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;References&lt;/strong&gt; — Looks for &lt;code&gt;"$VAR"&lt;/code&gt; or &lt;code&gt;"${VAR}"&lt;/code&gt; patterns inside quoted strings (common in heredocs, templates)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assignments&lt;/strong&gt; — Matches &lt;code&gt;VAR=value&lt;/code&gt;, &lt;code&gt;export VAR=value&lt;/code&gt;, etc. using the standard library &lt;code&gt;re&lt;/code&gt; module
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comparison&lt;/strong&gt; — Subtracts assigned from referenced sets to find unused variables&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Variable Formats Detected
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source Code&lt;/th&gt;
&lt;th&gt;Detected As&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"URL=${API_URL}"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;$API_URL&lt;/code&gt; reference&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;'echo $HOST: ${PORT}'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;$HOST&lt;/code&gt;, &lt;code&gt;$PORT&lt;/code&gt; references&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;export VAR=value&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;$VAR&lt;/code&gt; assignment&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Command Line Interface
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;env-checker.py &lt;span class="o"&gt;[&lt;/span&gt;options] &amp;lt;content&amp;gt;

Options:
  &lt;span class="nt"&gt;-h&lt;/span&gt;, &lt;span class="nt"&gt;--help&lt;/span&gt;        Show this &lt;span class="nb"&gt;help &lt;/span&gt;message
  &lt;span class="nt"&gt;-u&lt;/span&gt;, &lt;span class="nt"&gt;--unused-var-only&lt;/span&gt;, &lt;span class="nt"&gt;--unused&lt;/span&gt;      Only show unused/redundant variables

Examples:
  env-checker.sh deploy.sh                    &lt;span class="c"&gt;# Full analysis&lt;/span&gt;
  env-checker.py &amp;lt; myscript.txt               &lt;span class="c"&gt;# From stdin (pipeline)  &lt;/span&gt;
  python env-checker.py &lt;span class="nt"&gt;--unused&lt;/span&gt; varonly.sh   &lt;span class="c"&gt;# Unused vars only&lt;/span&gt;

Exit codes:
  0  Success or no unused vars  
  1  Error &lt;span class="o"&gt;(&lt;/span&gt;file not found, etc.&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Sample Input File
&lt;/h2&gt;

&lt;p&gt;Create a script with undefined variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# deploy.sh - has issues env-checker will find&lt;/span&gt;

&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;APP_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;deployment
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;API_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;API_HOST&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;:&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;API_PORT&lt;/span&gt;&lt;span class="k"&gt;}${&lt;/span&gt;&lt;span class="nv"&gt;API_PATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$APP_NAME&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /var/tmp/version

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; ./config.sh      &lt;span class="c"&gt;# Would fail if config.sh has unassigned vars  &lt;/span&gt;
deploy&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; 
    &lt;span class="nv"&gt;$ENV_VAR&lt;/span&gt;                      &lt;span class="c"&gt;# Undefined functionally &lt;/span&gt;
    curl &lt;span class="s2"&gt;"https://&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;API_URL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/health"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run Env Checker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;env-checker.py deploy.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Expected output:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[OK] Env Checker scan complete
Status: deploy.sh

* Analysis results:
  Assigned variables:   2 (APP_NAME, API_URL, ENV_VAR)  
  Unused/redundant vars:1 items
    - $API_HOST, $API_PORT, $API_PATH (defined externally or in config.sh?)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Integration Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  GitHub Actions Workflow
&lt;/h3&gt;

&lt;p&gt;Add this to &lt;code&gt;.github/workflows/pre-commit-checks.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Validate environment variables&lt;/span&gt;  
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python env-checker.py script.sh &amp;amp;&amp;amp; echo "Variables OK"&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v3&lt;/span&gt;  
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm install&lt;/span&gt;  
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm test&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Dockerfile Build-Time Check
&lt;/h3&gt;

&lt;p&gt;Ensure container scripts validate vars before build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; script.sh /app/&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;python env-checker.py /app/script.sh &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["bash", "/app/script.sh"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output validates the script contains no undefined variables.  &lt;/p&gt;

&lt;h3&gt;
  
  
  Shellcheck Integration
&lt;/h3&gt;

&lt;p&gt;Create a wrapper that calls both:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/bin/bash
# validate.sh - runs shellcheck + env-checker
&lt;/span&gt;
&lt;span class="n"&gt;shellcheck&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$@&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;checker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$@&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or add custom linting steps in Makefiles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nl"&gt;lint-vars&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        python env-checker.py &lt;span class="p"&gt;$(&lt;/span&gt;SRC_FILES&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Fix variables then rebuild"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Limitations to Know
&lt;/h2&gt;

&lt;p&gt;Env Checker provides basic detection, not comprehensive security analysis:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No semantic validation&lt;/strong&gt; — Cannot detect if a variable makes sense contextually
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limited pattern matching&lt;/strong&gt; — Focuses on common &lt;code&gt;$VAR&lt;/code&gt; and &lt;code&gt;${VAR}&lt;/code&gt; syntax
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No heredoc parsing&lt;/strong&gt; — Uses regex approximation rather than full parser analysis
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single-pass checks&lt;/strong&gt; — Doesn't track scope across multiple files
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These limitations are intentional: the tool prioritizes simplicity, zero dependencies, and fast execution over deep analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  When To Use This Tool
&lt;/h2&gt;

&lt;p&gt;Env Checker fills gaps in existing shell validation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD pipelines&lt;/strong&gt; — Add variable validation alongside syntax checks
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Template generation&lt;/strong&gt; — Verify generated scripts don't contain undefined refs
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation examples&lt;/strong&gt; — Check bash snippets in docs for completeness
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configuration files&lt;/strong&gt; — Validate &lt;code&gt;.bashrc&lt;/code&gt;-style content or config templates
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security reviews&lt;/strong&gt; — Ensure sensitive vars like &lt;code&gt;API_KEY&lt;/code&gt; aren't hardcoded
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Extending the Tool
&lt;/h2&gt;

&lt;p&gt;The code structure makes adding checks straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_var_usage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;var_name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Track specific variable usage frequency.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splitlines&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 
    &lt;span class="n"&gt;usages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;var_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;}}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;usages&lt;/span&gt;

&lt;span class="c1"&gt;# Usage example  
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;check_var_usage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;API_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;* Warning: API_KEY used excessively—consider passing via env var&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This enables custom rules for your specific use cases without external packages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison: Env Checker vs Alternatives
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Dependencies&lt;/th&gt;
&lt;th&gt;Variable Detection&lt;/th&gt;
&lt;th&gt;Output Detail&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Env Checker&lt;/td&gt;
&lt;td&gt;None (stdlib)&lt;/td&gt;
&lt;td&gt;Basic regex&lt;/td&gt;
&lt;td&gt;Clear summaries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shellcheck&lt;/td&gt;
&lt;td&gt;Requires install&lt;/td&gt;
&lt;td&gt;Deep parsing&lt;/td&gt;
&lt;td&gt;Extensive reports&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bashism&lt;/td&gt;
&lt;td&gt;None (stdlib)&lt;/td&gt;
&lt;td&gt;Limited pattern matching&lt;/td&gt;
&lt;td&gt;Minimal output&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Env Checker sits between full-featured analyzers like Shellcheck and basic validators. It's lightweight but catches the common cases without overhead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Shell scripts contain subtle bugs from undefined environment variables that break deployments. Env Checker provides a minimal but effective validation layer for these issues—ideal when you want targeted checks without installing external tools or parsing complex rule sets.&lt;/p&gt;

&lt;p&gt;Add it to your toolkit for CI/CD pipelines, script templating workflows, or documentation quality control. It's not replacing comprehensive validators like Shellcheck, but providing fast verification that undefined references exist before they cause problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Project:&lt;/strong&gt; &lt;a href="https://github.com/Poolion/env-checker" rel="noopener noreferrer"&gt;https://github.com/Poolion/env-checker&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;If you find this useful, you can support development: &lt;a href="https://www.buymeacoffee.com/poolion" rel="noopener noreferrer"&gt;https://www.buymeacoffee.com/poolion&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>bash</category>
      <category>opensource</category>
      <category>devops</category>
    </item>
    <item>
      <title>Config-Linter: Python CLI Tool for JSON/YAML Config Validation Without External Dependencies</title>
      <dc:creator>poolion</dc:creator>
      <pubDate>Thu, 20 Aug 2026 01:53:51 +0000</pubDate>
      <link>https://dev.to/poolion/config-linter-python-cli-tool-for-jsonyaml-config-validation-without-external-dependencies-46jp</link>
      <guid>https://dev.to/poolion/config-linter-python-cli-tool-for-jsonyaml-config-validation-without-external-dependencies-46jp</guid>
      <description>&lt;h1&gt;
  
  
  Config Linter: JSON/YAML Config Validation in Pure Python
&lt;/h1&gt;

&lt;p&gt;Configuration files are deceptively fragile. A trailing comma, an undefined variable, or an empty nested object can break your deployment before you even deploy the app. The Config Linter is a lightweight CLI tool written in pure Python that checks for these common issues without requiring any external dependencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Problem Does It Solve?
&lt;/h2&gt;

&lt;p&gt;When managing configuration files for applications, CI/CD pipelines, or infrastructure as code, developers frequently encounter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Trailing commas&lt;/strong&gt; — syntax errors that break YAML parsers but slip through simple JSON validators&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Undefined variables&lt;/strong&gt; like &lt;code&gt;${DATABASE_HOST}&lt;/code&gt; referenced in configs but never set in environment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Empty sections&lt;/strong&gt; in nested config structures that cause silent failures&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tedious manual inspection&lt;/strong&gt; of large configuration files before deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Config Linter provides automated validation for these problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clone and use directly&lt;/span&gt;
git clone https://github.com/Poolion/config-linter.git
&lt;span class="nb"&gt;cd &lt;/span&gt;config-linter
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x config-lint.py
python config-lint.py your-config.yaml

&lt;span class="c"&gt;# Add to PATH (Linux/macOS)&lt;/span&gt;
&lt;span class="nb"&gt;cp &lt;/span&gt;config-lint.py /usr/local/bin/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can run it from anywhere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;config-lint.py app.yaml &lt;span class="nt"&gt;--list-keys&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Usage Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Basic Validation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python config-lint.py database.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[OK] Loaded as JSON

Keys found (8):
  - database.host
  - database.port
  - database.username
  - database.password
[...]

===============================================================
Config validates successfully - no issues found!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  With Undefined Variable Warnings
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python config-lint.py myapp.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output includes warnings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[OK] Loaded as JSON

- WARN: Possible undefined variable: ${SOME_VAR}
[*] Trailing comma before }} at position 145
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  List All Configuration Keys
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python config-lint.py config.yaml &lt;span class="nt"&gt;--list-keys&lt;/span&gt;
&lt;span class="c"&gt;# Shows every key path in the configuration&lt;/span&gt;
Keys found &lt;span class="o"&gt;(&lt;/span&gt;24&lt;span class="o"&gt;)&lt;/span&gt;:
  - server.host
  - server.port
  - server.debug
  - database.primary
&lt;span class="o"&gt;[&lt;/span&gt;...]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Reading from Stdin for Pipelines
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"name: myapp
version: 1.0"&lt;/span&gt; | python config-lint.py -
&lt;span class="o"&gt;[&lt;/span&gt;OK] Loaded as INI/Text/Key-Value
- INFO: Empty configuration object
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;The tool handles multiple configuration formats with a single parser approach:&lt;/p&gt;

&lt;h3&gt;
  
  
  Detection Order
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;JSON/YAML detection&lt;/strong&gt; — Files ending in &lt;code&gt;.json&lt;/code&gt;, &lt;code&gt;.yml&lt;/code&gt;, or &lt;code&gt;.yaml&lt;/code&gt; containing valid JSON syntax are parsed as JSON. Most YAML configs without special anchors/aliases are actually valid JSON anyway.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Key=Value parsing&lt;/strong&gt; — Treated as INI-style with sections &lt;code&gt;[section]&lt;/code&gt; and &lt;code&gt;key = value&lt;/code&gt; pairs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type conversion&lt;/strong&gt; — Attempts to convert string values to proper types (integers, floats, booleans)&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Validation Categories
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check Type&lt;/th&gt;
&lt;th&gt;What It Detects&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Undefined variables&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;${VAR}&lt;/code&gt; or &lt;code&gt;%KEY%&lt;/code&gt; patterns&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;${MISSING_VAR}&lt;/code&gt; with no definition&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trailing commas&lt;/td&gt;
&lt;td&gt;Commas before &lt;code&gt;]&lt;/code&gt; or &lt;code&gt;}&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;"a": 1,&lt;/code&gt; followed by &lt;code&gt;}&lt;/code&gt; breaks YAML&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Empty sections&lt;/td&gt;
&lt;td&gt;Nested objects with zero keys&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{ "empty_section": {} }&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Command Line Interface
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python config-lint.py &lt;span class="o"&gt;[&lt;/span&gt;options] &amp;lt;config_file&amp;gt;

Options:
  &lt;span class="nt"&gt;-c&lt;/span&gt; CONFIG   Path to configuration file &lt;span class="o"&gt;(&lt;/span&gt;or &lt;span class="s2"&gt;"-"&lt;/span&gt; &lt;span class="k"&gt;for &lt;/span&gt;stdin&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nt"&gt;-l&lt;/span&gt;, &lt;span class="nt"&gt;--list-keys&lt;/span&gt;   List all keys found &lt;span class="k"&gt;in &lt;/span&gt;the configuration
  &lt;span class="nt"&gt;-h&lt;/span&gt;, &lt;span class="nt"&gt;--help&lt;/span&gt;        Show &lt;span class="nb"&gt;help &lt;/span&gt;message and examples
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why Pure Python?
&lt;/h2&gt;

&lt;p&gt;This tool requires no external dependencies—only what comes bundled with Python:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;argparse&lt;/code&gt; — Command-line argument parsing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;json&lt;/code&gt; — JSON file loading&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;re&lt;/code&gt; — Regular expression pattern matching&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No &lt;code&gt;pyyaml&lt;/code&gt;, no &lt;code&gt;ruamel.yaml&lt;/code&gt;, no schema validators. Just standard library, making it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lightweight&lt;/strong&gt; — No virtual environment needed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portable&lt;/strong&gt; — Works on any Python installation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Script-friendly&lt;/strong&gt; — Easy to import into other Python tools&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Configuration Formats Supported
&lt;/h2&gt;

&lt;h3&gt;
  
  
  JSON Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"server"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"${HOST}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Valid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;number&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"debug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  YAML Example (YAML-like syntax)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${HOME_DIR}&lt;/span&gt;
&lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3000&lt;/span&gt;

&lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp_db&lt;/span&gt;
&lt;span class="c1"&gt;# This triggers undefined variable warning for %DB%&lt;/span&gt;
&lt;span class="na"&gt;connection_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres://%DB%/myapp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  INI-style Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[general]&lt;/span&gt;
&lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1.2.0&lt;/span&gt;
&lt;span class="py"&gt;debug_mode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;true&lt;/span&gt;

&lt;span class="nn"&gt;[paths]&lt;/span&gt;
&lt;span class="py"&gt;logs_dir&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/var/log/app&lt;/span&gt;
&lt;span class="py"&gt;temp_dir&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;${TEMP_DIR}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  When to Use This Tool
&lt;/h2&gt;

&lt;p&gt;Ideal scenarios for config-lint:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD pipelines&lt;/strong&gt; — Validate configs before deployment
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configuration templates&lt;/strong&gt; — Ensure example configs have no undefined variables
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DevOps workflows&lt;/strong&gt; — Lint Kubernetes manifests alongside other files
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation&lt;/strong&gt; — Validate example configuration files in docs
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated testing&lt;/strong&gt; — Include config validation in test suites
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Integration Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  GitHub Actions Workflow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/lint-config.yml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Validate Configuration Files&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;config/**'&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;validate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python config-lint.py app.yaml --list-keys&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Dockerfile Integration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Validate configs at build time&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;python config-lint.py /etc/app/config.yaml &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; config/ /etc/app/conf.d/&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["python-config", "config-lint.py" "/etc/app/conf.d/*.yaml"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Limitations to Know
&lt;/h2&gt;

&lt;p&gt;As a lightweight tool, Config Linter doesn't attempt to be comprehensive:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No schema validation&lt;/strong&gt; — Cannot check that values match expected types or ranges (use JSON Schema or similar for this)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limited YAML parsing&lt;/strong&gt; — True YAML with anchors, aliases, or complex multi-line strings requires PyYAML. This parses key=value style content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shallow nested checks&lt;/strong&gt; — Recursively checks only 3 levels deep (enough for most configs)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No semantic analysis&lt;/strong&gt; — Cannot detect if a value makes sense in context&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are intentional trade-offs for simplicity and zero-dependency requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extending the Tool
&lt;/h2&gt;

&lt;p&gt;The code is intentionally simple to extend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Add a new check after find_empty()
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_duplicates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Find duplicate keys in nested dicts.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;issues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;traverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;seen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;key_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="nf"&gt;traverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Duplicate value at &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; != &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;key_id&lt;/span&gt;

    &lt;span class="nf"&gt;traverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;issues&lt;/span&gt;

&lt;span class="c1"&gt;# Add to main() validation loop
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;  &lt;span class="c1"&gt;# existing checks
&lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;duplicates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;check_duplicates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;duplicates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;duplicates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;- WARN: Duplicate configuration: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Config Linter fills a practical gap in developer tooling. It's not trying to replace comprehensive validation suites, but provides targeted checks for the most common configuration file mistakes that break deployments and cause confusion.&lt;/p&gt;

&lt;p&gt;For minimal overhead—zero dependencies plus about 100 lines of code—it catches issues that manual inspection often misses. Add it to your toolkit when working with configuration-heavy projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Project:&lt;/strong&gt; &lt;a href="https://github.com/Poolion/config-linter" rel="noopener noreferrer"&gt;https://github.com/Poolion/config-linter&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;If you find this useful, you can support development: &lt;a href="https://www.buymeacoffee.com/poolion" rel="noopener noreferrer"&gt;https://www.buymeacoffee.com/poolion&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>devops</category>
      <category>cli</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Config Linter: Validate JSON/YAML Config Files for Trailing Commas and Undefined Variables</title>
      <dc:creator>poolion</dc:creator>
      <pubDate>Thu, 20 Aug 2026 01:51:34 +0000</pubDate>
      <link>https://dev.to/poolion/config-linter-validate-jsonyaml-config-files-for-trailing-commas-and-undefined-variables-iof</link>
      <guid>https://dev.to/poolion/config-linter-validate-jsonyaml-config-files-for-trailing-commas-and-undefined-variables-iof</guid>
      <description>&lt;h1&gt;
  
  
  Config Linter: Validate Configuration Files in Pure Python
&lt;/h1&gt;

&lt;p&gt;Configuration files can be tricky—trailing commas that break YAML parsers, undefined variables that cause deployment failures, or empty sections that go unnoticed until runtime. The Config Linter is a lightweight CLI tool written in pure Python that checks for these common issues without requiring any external dependencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Problem Does It Solve?
&lt;/h2&gt;

&lt;p&gt;When working with configuration files (application configs, CI/CD parameters, database connection strings, etc.), developers often encounter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Trailing commas&lt;/strong&gt; that break YAML parsers but pass some JSON checkers
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Undefined variables&lt;/strong&gt; like &lt;code&gt;${DATABASE_HOST}&lt;/code&gt; or &lt;code&gt;%DB_PATH%&lt;/code&gt; referenced but not defined anywhere
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Empty sections&lt;/strong&gt; in nested config structures&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Syntax issues&lt;/strong&gt; that require manual inspection of large files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Config Linter provides automated checks for these problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clone and use directly&lt;/span&gt;
git clone https://github.com/Poolion/config-linter.git
&lt;span class="nb"&gt;cd &lt;/span&gt;config-linter
python config-lint.py your-config.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add to PATH (optional):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp &lt;/span&gt;config-lint.py /usr/local/bin/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Usage Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Basic Validation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python config-lint.py app.yaml
&lt;span class="c"&gt;# Scans for trailing commas, undefined variables, and empty sections&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  With Key Listing
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python config-lint.py database.json &lt;span class="nt"&gt;--list-keys&lt;/span&gt;
&lt;span class="c"&gt;# Shows all configuration keys found in the file&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Reading from Stdin
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"database:"&lt;/span&gt; | python config-lint.py - 
&lt;span class="c"&gt;# Pipe content directly, no filename needed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>python</category>
      <category>opensource</category>
      <category>devops</category>
      <category>configuration</category>
    </item>
    <item>
      <title>Text Summarizer: Quick Python CLI for Extracting Key Sentences from Documents</title>
      <dc:creator>poolion</dc:creator>
      <pubDate>Thu, 20 Aug 2026 01:43:55 +0000</pubDate>
      <link>https://dev.to/poolion/text-summarizer-quick-python-cli-for-extracting-key-sentences-from-documents-36ij</link>
      <guid>https://dev.to/poolion/text-summarizer-quick-python-cli-for-extracting-key-sentences-from-documents-36ij</guid>
      <description>&lt;h1&gt;
  
  
  Text Summarizer: Quick Python CLI for Extracting Key Sentences from Documents
&lt;/h1&gt;

&lt;p&gt;When working with long documents—meeting notes, research papers, technical documentation—you often need a quick way to grasp the main points without reading everything. The Text Summarizer is a lightweight Python CLI tool that extracts key sentences based on frequency analysis and sentence boundary detection.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Problem Does It Solve?
&lt;/h2&gt;

&lt;p&gt;Extracting summaries from text documents traditionally requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manual reading (time-consuming)
&lt;/li&gt;
&lt;li&gt;Complex NLP libraries like spaCy or NLTK (heavy dependencies)&lt;/li&gt;
&lt;li&gt;Paid summarization APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Text Summarizer provides a simple, dependency-free alternative using pure Python to identify and extract the most important sentences for quick overviews.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Download and use directly&lt;/span&gt;
git clone https://github.com/Poolion/text-summarizer.git
&lt;span class="nb"&gt;cd &lt;/span&gt;text-summarizer
python text-summarizer.py document.txt &lt;span class="nt"&gt;--words&lt;/span&gt; 100 &lt;span class="nt"&gt;--top-sentences&lt;/span&gt; 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add to your PATH:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp &lt;/span&gt;text-summarizer.py /usr/local/bin/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Usage Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Basic Document Summarization
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python text-summarizer.py my-article.md &lt;span class="nt"&gt;--words&lt;/span&gt; 150 &lt;span class="nt"&gt;--top-sentences&lt;/span&gt; 5
&lt;span class="c"&gt;# Extracts top 5 sentences from an article, limited to 150 words&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Handle long documents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python text-summarizer.py research-paper.pdf.txt &lt;span class="nt"&gt;--words&lt;/span&gt; 200 &lt;span class="nt"&gt;--top-sentences&lt;/span&gt; 8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pipe Input from stdin
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Meeting notes with multiple paragraphs..."&lt;/span&gt; | python text-summarizer.py &lt;span class="nt"&gt;--top-sentences&lt;/span&gt; 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Process files in a loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;file &lt;span class="k"&gt;in &lt;/span&gt;reports/&lt;span class="k"&gt;*&lt;/span&gt;.txt&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;python text-summarizer.py &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--words&lt;/span&gt; 80 &lt;span class="nt"&gt;--top-sentences&lt;/span&gt; 1
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Sentence Boundary Detection
&lt;/h3&gt;

&lt;p&gt;The tool correctly handles common abbreviations that often confuse simple period-based splitters. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Dr. Smith said..." (doesn't split at the period after Dr)&lt;/li&gt;
&lt;li&gt;"Mr. Johnson arrived" (same treatment)&lt;/li&gt;
&lt;li&gt;"Jr." in "John Sr. and John Jr."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The regex pattern filters these intelligently while still splitting on actual sentence-ending punctuation like &lt;code&gt;.&lt;/code&gt; followed by space or newline, &lt;code&gt;!&lt;/code&gt;, and &lt;code&gt;?&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frequency-Based Selection
&lt;/h3&gt;

&lt;p&gt;Currently, the tool extracts sentences from the beginning of the document. For production use, you could extend it with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TF-IDF scoring to weight sentences by unique word importance&lt;/li&gt;
&lt;li&gt;Statistical algorithms like TextRank (Graph-based ranking)
&lt;/li&gt;
&lt;li&gt;Extractive methods using sentence similarity scores&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Use This Tool?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No dependencies&lt;/strong&gt;: Pure Python standard library only&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fast processing&lt;/strong&gt;: Works on large documents in seconds
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configurable limits&lt;/strong&gt;: Adjust word count and sentence count as needed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pipeline-friendly&lt;/strong&gt;: Stream input from stdin or pipe from other commands&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lightweight&lt;/strong&gt;: Under 50 lines of code, easy to extend&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When to Use It
&lt;/h2&gt;

&lt;p&gt;Ideal for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Quick document previews before deep reading&lt;/li&gt;
&lt;li&gt;Meeting notes summarization (grab key points)&lt;/li&gt;
&lt;li&gt;Documentation triage (find relevant sections)&lt;/li&gt;
&lt;li&gt;Email/newsletter content extraction&lt;/li&gt;
&lt;li&gt;Report quick-scanning workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;This is an extractive summarizer. Unlike abstractive models that generate new sentences, this tool selects existing ones. For more sophisticated summarization you'd need neural networks like BART or Pegasus—but those require GPU and much larger models.&lt;/p&gt;

&lt;p&gt;For lightweight document preview needs, Text Summarizer provides a good balance of simplicity and effectiveness.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Text Summarizer is a simple but practical CLI tool for extracting key content from large documents without heavy dependencies. It's perfect for quick document triage and understanding long articles in seconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/Poolion/text-summarizer" rel="noopener noreferrer"&gt;https://github.com/Poolion/text-summarizer&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;If you find this useful, you can support development: &lt;a href="https://www.buymeacoffee.com/poolion" rel="noopener noreferrer"&gt;https://www.buymeacoffee.com/poolion&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>productivity</category>
      <category>opensource</category>
      <category>tool</category>
    </item>
    <item>
      <title>Log Grep: Stream Regex Search for Large Logs Without Loading Entire File</title>
      <dc:creator>poolion</dc:creator>
      <pubDate>Thu, 20 Aug 2026 01:40:50 +0000</pubDate>
      <link>https://dev.to/poolion/log-grep-stream-regex-search-for-large-logs-without-loading-entire-file-59ck</link>
      <guid>https://dev.to/poolion/log-grep-stream-regex-search-for-large-logs-without-loading-entire-file-59ck</guid>
      <description>&lt;h1&gt;
  
  
  Log Grep: Stream Regex Search for Large Logs Without Loading Entire File Into Memory
&lt;/h1&gt;

&lt;p&gt;When working with large log files—common in production server monitoring, CI/CD pipelines, or application debugging—the standard &lt;code&gt;grep&lt;/code&gt; command can cause memory issues by loading the entire file into RAM.&lt;/p&gt;

&lt;p&gt;Log Grep is a lightweight Python CLI tool that uses streaming mode to handle gigabyte-scale log files efficiently, reading line-by-line without loading everything into memory at once.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Problem Does It Solve?
&lt;/h2&gt;

&lt;p&gt;Large log files (GBs in size) are common in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cloud application monitoring&lt;/li&gt;
&lt;li&gt;Web server access logs
&lt;/li&gt;
&lt;li&gt;Container/container orchestration debugging&lt;/li&gt;
&lt;li&gt;CI/CD pipeline output&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Standard tools like &lt;code&gt;grep&lt;/code&gt; or Perl regular expressions often read the entire file into memory, causing out-of-memory (OOM) errors on constrained systems. Log Grep uses Python's streaming file iterator pattern to handle arbitrarily large files using constant memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clone and run directly&lt;/span&gt;
git clone https://github.com/Poolion/log-grep.git
&lt;span class="nb"&gt;cd &lt;/span&gt;log-grep
python3 log-grep.py &lt;span class="s2"&gt;"ERROR"&lt;/span&gt; bigfile.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or add to your PATH:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp &lt;/span&gt;log-grep.py /usr/local/bin/
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /usr/local/bin/log-grep.py
log-grep.py &lt;span class="s2"&gt;"pattern"&lt;/span&gt; logfile.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Usage Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Basic Pattern Search
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 log-grep.py &lt;span class="s2"&gt;"ERROR"&lt;/span&gt; application.log
&lt;span class="c"&gt;# Searches for lines containing "ERROR"&lt;/span&gt;
&lt;span class="c"&gt;# Shows first 10 matches with line numbers (default)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Limit to Specific Matches
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 log-grep.py &lt;span class="s2"&gt;"connection.*timeout"&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; 5
&lt;span class="c"&gt;# Only show the first 5 matches&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Show All Matches
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 log-grep.py &lt;span class="s2"&gt;"^2024-01-"&lt;/span&gt; logfile.log &lt;span class="nt"&gt;--count&lt;/span&gt; 0
&lt;span class="c"&gt;# Shows all matching lines (no line count limit)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Suppress Line Numbers
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 log-grep.py &lt;span class="s2"&gt;"failed.*login"&lt;/span&gt; &lt;span class="nt"&gt;--no-line-numbers&lt;/span&gt;
&lt;span class="c"&gt;# Clean output without line numbers&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pipe from Another Command
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /var/log/&lt;span class="k"&gt;*&lt;/span&gt;.log | python3 log-grep.py &lt;span class="s2"&gt;"ERROR"&lt;/span&gt;
&lt;span class="c"&gt;# Works with stdin via "-f" flag&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Technical Details
&lt;/h2&gt;

&lt;p&gt;The tool uses Python's built-in file iteration, which keeps the file descriptor open and reads one line at a time using buffered I/O. This pattern is memory-efficient:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line_num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Process each line individually
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is different from loading everything at once with &lt;code&gt;file.readlines()&lt;/code&gt; or Python's regex module's non-streaming modes.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;-c&lt;/code&gt; (count) parameter limits output to prevent excessive console flooding when searching common patterns in large files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern Support
&lt;/h3&gt;

&lt;p&gt;Log Grep uses &lt;code&gt;re.search()&lt;/code&gt; for pattern matching, supporting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Regular expressions (&lt;code&gt;"ERROR.*timeout"&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Character classes (&lt;code&gt;"[0-9]{3}"&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Anchors (&lt;code&gt;"^INFO"&lt;/code&gt; or &lt;code&gt;"error$"&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Quantifiers (&lt;code&gt;"fail[ur]"&lt;/code&gt; matches 'fail' and 'fair')&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When to Use
&lt;/h2&gt;

&lt;p&gt;Use Log Grep when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Files exceed available RAM&lt;/li&gt;
&lt;li&gt;Working with high-volume logging infrastructure
&lt;/li&gt;
&lt;li&gt;Need controlled output (count limits)&lt;/li&gt;
&lt;li&gt;Processing logs in constrained containers/VPS&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Alternatives
&lt;/h2&gt;

&lt;p&gt;Standard &lt;code&gt;grep&lt;/code&gt; works for small files under 1GB. For larger files, consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;less +g pattern file&lt;/code&gt; (GNU grep's stream mode)&lt;/li&gt;
&lt;li&gt;This tool provides better count control and line number options&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Log Grep provides a simple, efficient solution for pattern searching large log files without memory concerns. It's perfect for system administrators, DevOps engineers, and developers debugging production systems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/Poolion/log-grep" rel="noopener noreferrer"&gt;https://github.com/Poolion/log-grep&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;If you find this useful, you can support development: &lt;a href="https://www.buymeacoffee.com/poolion" rel="noopener noreferrer"&gt;https://www.buymeacoffee.com/poolion&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>opensource</category>
      <category>productivity</category>
      <category>tool</category>
    </item>
    <item>
      <title>Schema Checker CLI: Validate YAML/JSON Config Files Against Required Fields in Python</title>
      <dc:creator>poolion</dc:creator>
      <pubDate>Thu, 20 Aug 2026 01:34:37 +0000</pubDate>
      <link>https://dev.to/poolion/schema-checker-cli-validate-yamljson-config-files-against-required-fields-in-python-9bo</link>
      <guid>https://dev.to/poolion/schema-checker-cli-validate-yamljson-config-files-against-required-fields-in-python-9bo</guid>
      <description>&lt;h1&gt;
  
  
  Schema Checker CLI: Validate Config Files Against Common Patterns
&lt;/h1&gt;

&lt;p&gt;Configuration files are where apps meet cloud deployment requirements. Deploy pipelines fail when required fields missing—database host empty, ports list has strings instead of integers. This CLI catches those issues before container startup times out.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Does
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Syntax validation&lt;/strong&gt;: Detect malformed JSON or YAML patterns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Field presence checks&lt;/strong&gt;: Ensure &lt;code&gt;database.host&lt;/code&gt;, &lt;code&gt;ports[0].name&lt;/code&gt; exist&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type checking&lt;/strong&gt;: Verify port entries are integers, not strings like &lt;code&gt;"80"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-detect format&lt;/strong&gt;: Skips PyYAML dependency, uses regex for YAML detection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Perfect for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CI/CD pipeline pre-checks before container builds&lt;/li&gt;
&lt;li&gt;Deployment templates that must follow schema standards&lt;/li&gt;
&lt;li&gt;Teams migrating from loose configs to strict schemas&lt;/li&gt;
&lt;li&gt;Development validation before push to staging&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Usage Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Basic Validation (Auto-Detect Format)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python schema-checker.py config.yaml
&lt;span class="c"&gt;# ✓ Valid or Error: missing database.host&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Automatically detects YAML syntax first, then JSON. Warns if format detection fails rather than failing silently with external dependency errors.&lt;/p&gt;

&lt;h3&gt;
  
  
  Require Specific Fields
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python schema-checker.py config.json &lt;span class="nt"&gt;-r&lt;/span&gt; database port logging
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check for commonly required top-level fields across team standards. Fails CI when missing &lt;code&gt;database.host&lt;/code&gt; or &lt;code&gt;port.loglevel&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: missing database.host
Error: missing port.loglevel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Force JSON-Only Mode
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python schema-checker.py data.noyaml &lt;span class="nt"&gt;--json-only&lt;/span&gt;
&lt;span class="c"&gt;# ✓ Valid JSON or Error: parse failed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Skip YAML regex detection when file known to be JSON. Prevents false positives from detecting YAML when only JSON syntax rules apply.&lt;/p&gt;

&lt;h3&gt;
  
  
  CI/CD Integration Example
&lt;/h3&gt;

&lt;p&gt;Add pre-build checks to GitHub Actions or GitLab pipelines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python schema-checker.py config.yaml --quiet&lt;/span&gt;
  &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.event_name == 'push' &amp;amp;&amp;amp; $branch != 'production' }}&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "Config validation passed" || exit &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fail builds when configs miss required fields. Prevents deploy failures when missing &lt;code&gt;database.host&lt;/code&gt; causes container startup timeouts (can't connect to empty host, pod creation hangs).&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick Check for Teams
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python schema-checker.py deploy.yml &lt;span class="nt"&gt;--quiet&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"OK"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="c"&gt;# Exit code 0 = valid, anything else = validation failure&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feeds into scripts that gate deployments. When configuration validation passes with no errors found—exit code zero and all required fields present—the pipeline proceeds to container generation with confidence that schema won't cause delays.&lt;/p&gt;

&lt;h2&gt;
  
  
  Command Reference
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--json-only, -j&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Treat as JSON only, skip YAML detection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--quiet, -q&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Only show errors, no summary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--required-fields, -r&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Explicitly require these top-level fields&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;file&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Configuration file path&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Multi-Directory CI Example
&lt;/h3&gt;

&lt;p&gt;After each run, ensure pipeline validation passes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python schema-checker.py config.yaml &lt;span class="nt"&gt;--quiet&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt;
&lt;span class="c"&gt;# Use count output to trigger alerts when missing_required &amp;gt; 0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feeds into alert systems that warn of approaching disk exhaustion—exit codes determine whether validation failures proceed or halt deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Example
&lt;/h2&gt;

&lt;p&gt;The validator checks syntax first, then structural rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_yaml_syntax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Parse and check YAML structure.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safe_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;YAMLError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YAML parse failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_database_section&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Check database config structure.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;issues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;database&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;db&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Missing database or db section&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;database&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;db&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;

    &lt;span class="c1"&gt;# Check recommended fields exist
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;host&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;  &lt;span class="c1"&gt;# Allow empty string from external service config
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;port&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;issues&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_ports_section&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Port validation ensures list contains integer entries for network listeners. Many services fail when YAML defines ports as strings rather than integers—common mistake when migrating from file-based configs to YAML templates that strict parsers reject. Structural checks warn about missing required fields before pipeline deployment tools attempt container creation with invalid configs.&lt;/p&gt;

&lt;p&gt;When database host is empty string, connection timeouts waste compute resources before error reports reach teams. Schema validation catches this during development or CI runs rather than letting it cause production incidents after deployment succeeds but services fail to start.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Config Patterns Validated
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Section&lt;/th&gt;
&lt;th&gt;Required Fields&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;database&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;host&lt;/code&gt;, &lt;code&gt;port&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt; (or &lt;code&gt;db&lt;/code&gt; alias)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;api&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;url&lt;/code&gt;, &lt;code&gt;prefix&lt;/code&gt;, &lt;code&gt;version&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;server&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;hostname&lt;/code&gt;, &lt;code&gt;listen_port&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;cache&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;redis_host&lt;/code&gt;, &lt;code&gt;memcached_servers&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;log&lt;/strong&gt; or &lt;strong&gt;logging&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;level&lt;/code&gt;, &lt;code&gt;format&lt;/code&gt;, &lt;code&gt;destination&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Teams migrating from legacy systems to new stack need schema enforcement early—this validates against common patterns before full schema library integration required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern Example: Database Section
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# This passes validation&lt;/span&gt;
&lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres.internal&lt;/span&gt;
  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5432&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appdb&lt;/span&gt;

&lt;span class="c1"&gt;# This triggers warning (missing port)&lt;/span&gt;
&lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;database.example.com&lt;/span&gt;

&lt;span class="c1"&gt;# This fails validation (missing required field)&lt;/span&gt;
&lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appdb&lt;/span&gt;    &lt;span class="c1"&gt;# No host or port defined&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pattern matching catches when configs deviate from team templates or migration guides. Teams migrating from legacy systems to new stack need schema enforcement early—this validates against common patterns before full schema library integration required.&lt;/p&gt;

&lt;p&gt;When configs lack strict naming and teams expect fields like &lt;code&gt;db.host&lt;/code&gt; not &lt;code&gt;database.host&lt;/code&gt;, warnings guide restructuring. Standardization reduces errors downstream in pipeline deployment stages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alternatives Compared
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Limitation&lt;/th&gt;
&lt;th&gt;This tool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;yamllint&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No custom field checks&lt;/td&gt;
&lt;td&gt;Enforces business schema rules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JSON Schema validators&lt;/td&gt;
&lt;td&gt;Overkill for simple configs&lt;/td&gt;
&lt;td&gt;Minimal Python, no dependencies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manual review&lt;/td&gt;
&lt;td&gt;Easy to miss required fields&lt;/td&gt;
&lt;td&gt;Automates common pattern enforcement&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Simple validation avoids complexity of full schema libraries. Focuses on patterns most apps share—database section with host/port/name, ports list with integers, server listen configuration. When teams standardize config templates, this ensures all variants follow same structure before deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Source Code
&lt;/h2&gt;

&lt;p&gt;Public repo with examples for security automation or policy enforcement tools. Readable, dependency-free implementation using only Python standard library.&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;Repo&lt;/strong&gt;: &lt;a href="https://github.com/Poolion/schema-checker-cli" rel="noopener noreferrer"&gt;https://github.com/Poolion/schema-checker-cli&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you find this useful, you can support development: &lt;a href="https://www.buymeacoffee.com/poolion" rel="noopener noreferrer"&gt;https://www.buymeacoffee.com/poolion&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>cli</category>
      <category>opensource</category>
      <category>configuration</category>
    </item>
    <item>
      <title>Log Pruner CLI: Find and Clean Oversized Log Files in Python</title>
      <dc:creator>poolion</dc:creator>
      <pubDate>Thu, 20 Aug 2026 01:25:59 +0000</pubDate>
      <link>https://dev.to/poolion/log-pruner-cli-find-and-clean-oversized-log-files-in-python-205m</link>
      <guid>https://dev.to/poolion/log-pruner-cli-find-and-clean-oversized-log-files-in-python-205m</guid>
      <description>&lt;h1&gt;
  
  
  Log Pruner CLI: A Minimal Tool for Finding Oversized Logs
&lt;/h1&gt;

&lt;p&gt;Oversized log files clog disk space before rotations fire. This CLI scans directories and flags 10MB+ logs for review or deletion, helps identify which entries still matter before cleanup, and counts candidates to feed automation checks. Uses only Python standard library.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Does
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Size-based flagging&lt;/strong&gt;: Files &amp;gt;10MB get &lt;code&gt;review&lt;/code&gt;, &amp;gt;50MB get &lt;code&gt;delete&lt;/code&gt;/&lt;code&gt;rotation&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Age detection&lt;/strong&gt;: Report how many days since last modification&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quick summary mode&lt;/strong&gt;: Count candidates without listing all 30 files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deletion help&lt;/strong&gt;: Generate &lt;code&gt;rm&lt;/code&gt; commands for top offenders&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Perfect for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Post-build cleanup in CI/CD pipelines&lt;/li&gt;
&lt;li&gt;Cloud container quota compliance checks (VPS limits)&lt;/li&gt;
&lt;li&gt;Team directories where logs accumulate without rotation&lt;/li&gt;
&lt;li&gt;Systems with strict quotas that require proactive management&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Usage Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Detailed Report
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python log-pruner.py &lt;span class="nt"&gt;-d&lt;/span&gt; /var/log &lt;span class="nt"&gt;-l&lt;/span&gt; 2 &lt;span class="nt"&gt;-r&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Flags oversized or aged logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;daemon.log                    15.2MB (age=14d) -&amp;gt; review
nginx/access.log              72.4MB (age=90d) -&amp;gt; delete
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Color-coded recommendations indicate which files exceed thresholds or need rotation planning. Large files from build artifacts accumulate if pipelines don't clear &lt;code&gt;/tmp&lt;/code&gt; before shipping containers or images—this catches them quickly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick Count Only
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python log-pruner.py &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; /home/app/logs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feeds dashboards or scripts that enforce quotas without manual intervention:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Total log-type files: 842
&amp;gt;10MB logs: 15
Review candidates (5-10MB): 6
Rotation candidates (&amp;gt;50MB): 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Count output integrates with CI/CD gates—fail if review candidates exceed policy threshold. When storage quotas tighten before quota expires, automated checks using this count decide whether to alert team or proceed with rotation scripts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Command Reference
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-d/--dirs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Directory(s) to scan (repeat)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-l/--depth&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Depth per directory (default=1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-r/--rotate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Flag &amp;gt;50MB for rotation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-c/--count&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Summary counts only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Multi-Directory Scan
&lt;/h3&gt;

&lt;p&gt;Scan multiple locations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python log-pruner.py &lt;span class="nt"&gt;-d&lt;/span&gt; /var/log &lt;span class="nt"&gt;-d&lt;/span&gt; /tmp/monitoring
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Combines findings across system logs and monitoring agent outputs in single report. Useful for comprehensive audits covering all known log storage paths before compliance reviews or scaling events.&lt;/p&gt;

&lt;h3&gt;
  
  
  CI/CD Integration Example
&lt;/h3&gt;

&lt;p&gt;After builds fail cleanup, catch lingering files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python log-pruner.py &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; /tmp/build-artifacts
&lt;span class="c"&gt;# Use count output to trigger alerts when review_candidates &amp;gt; MAX_ALLOWED&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feeds into scripts that decide whether to abort builds or alert for manual attention. When artifacts ship to remote storage and disk quota isn't raised before rotation reduces size below threshold, teams face unexpected failures unless proactive checks detect accumulation first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Example
&lt;/h2&gt;

&lt;p&gt;The scanner detects log files by filename patterns and flags oversized ones:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;scan_logs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;logs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;iterate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;fname&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;fpath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fpath&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;

            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;stat_info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fpath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stat_info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;st_size&lt;/span&gt;
                &lt;span class="n"&gt;mtime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stat_info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;st_mtime&lt;/span&gt;

                &lt;span class="c1"&gt;# Age days since last modification
&lt;/span&gt;                &lt;span class="n"&gt;age_days&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;mtime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;86400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

                &lt;span class="c1"&gt;# Simple log pattern detection
&lt;/span&gt;                &lt;span class="n"&gt;name_lower&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="n"&gt;is_log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name_lower&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.log&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; 
                          &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ext&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;name_lower&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ext&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;_log&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.err&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;

                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;is_log&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;recommendation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;review&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;
                    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="n"&gt;recommendation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;delete&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

                    &lt;span class="n"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;size&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                                 &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;age_days&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;age_days&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;recommendation&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;recommendation&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;IOError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;

    &lt;span class="nf"&gt;iterate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abspath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uses &lt;code&gt;os.stat()&lt;/code&gt; to get byte count and modification timestamp. Age calculation divides seconds since last write by 86400 (seconds per day). Pattern matching detects common log extensions without requiring full file content inspection—saves time scanning directories for known patterns only.&lt;/p&gt;

&lt;p&gt;Threshold logic flags files larger than 10MB for review, those exceeding 50MB for rotation or deletion. This balances catching problematic storage consumers while allowing legitimate large logs when teams configure rotation after checking retention relevance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Production Quota Checks
&lt;/h3&gt;

&lt;p&gt;Cloud environments enforce quotas that auto-delete data beyond limits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python log-pruner.py &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; /opt/monitoring
&lt;span class="c"&gt;# Alert when rotation_candidates &amp;gt; 5, before quota hits critical threshold&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Count output feeds alert systems that warn of approaching disk exhaustion. When monitoring agents write to shared directories without central management, this catches accumulation before quota breach causes service restarts or data loss from auto-deletion policies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build Pipeline Cleanup
&lt;/h3&gt;

&lt;p&gt;After each run, ensure logs don't inflate artifact size:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python log-pruner.py &lt;span class="nt"&gt;-d&lt;/span&gt; /tmp/build-artifacts &lt;span class="nt"&gt;-l&lt;/span&gt; 1
&lt;span class="c"&gt;# Delete flagged files before archiving to remote storage&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deletion candidates get passed to cleanup scripts that remove them safely. When build artifacts ship without prior rotation or truncation, archive downloads become unnecessarily large—flagged files catch size inflation before it impacts delivery costs or client download times.&lt;/p&gt;

&lt;h3&gt;
  
  
  Legacy System Review
&lt;/h3&gt;

&lt;p&gt;Systems lacking centralized logging accumulate files in custom directories:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python log-pruner.py &lt;span class="nt"&gt;-d&lt;/span&gt; /home/legacy/logs &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; 2
&lt;span class="c"&gt;# Flag files before applying `logrotate` configuration from manual review&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Manual inspection of flagged logs determines whether entries still relevant for debugging recent issues. Old entries from production incidents need archival, while debug info from current failures stays readable until resolution completes or next rotation window passes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Patterns Detected
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Example Files&lt;/th&gt;
&lt;th&gt;Why Oversized?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;*.log&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;daemon.log&lt;/code&gt;, &lt;code&gt;app.log&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;No rotation config before write&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;*_log.txt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nginx/access_log.txt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Custom service capturing to files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.err&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;stderr.err&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Error redirection to files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;_log&lt;/code&gt; in name&lt;/td&gt;
&lt;td&gt;&lt;code&gt;system_auth_log.log&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Script output without naming norms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Pattern matching catches variations in naming conventions from different services. Without central logging daemon like journald, application developers write logs directly to files—often without rotation scripts that trim entries before reaching size limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alternatives Compared
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;find -size +10M -delete&lt;/code&gt;&lt;/strong&gt;: Removes everything without checking relevance first&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;du -sh *&lt;/code&gt;&lt;/strong&gt;: Shows folder totals—not which specific logs cause disk pressure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;This tool&lt;/strong&gt;: Size + age context, recommendations before deletion&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Manual deletion risks removing recent debug entries needed for incident response. Automated tools without rotation awareness lose data before teams can determine retention value. This tool balances safety with space management by flagging first, allowing humans or automation scripts to decide whether deletion applies to specific paths.&lt;/p&gt;

&lt;h2&gt;
  
  
  Source Code
&lt;/h2&gt;

&lt;p&gt;Public repo with examples. Readable, dependency-free implementation suitable as foundation for security automation or policy enforcement scripts.&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;Repo&lt;/strong&gt;: &lt;a href="https://github.com/Poolion/log-pruner-cli" rel="noopener noreferrer"&gt;https://github.com/Poolion/log-pruner-cli&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you find this useful, you can support development: &lt;a href="https://www.buymeacoffee.com/poolion" rel="noopener noreferrer"&gt;https://www.buymeacoffee.com/poolion&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>logging</category>
      <category>cli</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Permission Audit CLI: Find World-Writable Files in Python</title>
      <dc:creator>poolion</dc:creator>
      <pubDate>Thu, 20 Aug 2026 01:20:30 +0000</pubDate>
      <link>https://dev.to/poolion/permission-audit-cli-find-world-writable-files-in-python-2poh</link>
      <guid>https://dev.to/poolion/permission-audit-cli-find-world-writable-files-in-python-2poh</guid>
      <description>&lt;h1&gt;
  
  
  Permission Audit CLI: A Minimal Tool for Security Reviews and Compliance Checks
&lt;/h1&gt;

&lt;p&gt;Security audits, compliance reviews, or permission normalization—how do you efficiently check which files need attention? This CLI tool reports file permissions (octal modes) and flags world-writable files that violate principle-of-least-access policies. Uses only Python standard library.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Does
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Permission listing&lt;/strong&gt;: Show octal codes like &lt;code&gt;644&lt;/code&gt;, &lt;code&gt;755&lt;/code&gt;, &lt;code&gt;0600&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Danger check&lt;/strong&gt;: Report world-writable or group-writable files (security risks)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ownership flags&lt;/strong&gt;: Highlight when file UID differs from real user&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Summary/report modes&lt;/strong&gt;: Choose compact counts or detailed listings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Perfect for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Security audits before deployments or migrations&lt;/li&gt;
&lt;li&gt;CI/CD checks that fail on &lt;code&gt;0777&lt;/code&gt; files in production&lt;/li&gt;
&lt;li&gt;Team directory reviews where shared folders need proper group access&lt;/li&gt;
&lt;li&gt;Regulatory compliance (documenting state per policy)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;No pip install needed. Drop &lt;code&gt;perm-audit.py&lt;/code&gt; anywhere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python perm-audit.py &lt;span class="nt"&gt;-d&lt;/span&gt; /path/to/dir &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;-l&lt;/span&gt; depth] &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;-v&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;-w&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy to &lt;code&gt;/usr/local/bin/perm-audit&lt;/code&gt; for PATH access:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp &lt;/span&gt;perm-audit.py /usr/local/bin/perm-audit
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /usr/local/bin/perm-audit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Usage Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Full Audit (Summary Mode)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python perm-audit.py &lt;span class="nt"&gt;-d&lt;/span&gt; /home/developer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Shows top 20 permission modes sorted by count:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mode counts (octal):
 644     1,532 files 0755       482 files 0600        97 files 0644          24 files 0755        18 files
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each code means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;0644&lt;/code&gt;: owner rw+group/others r (standard data file)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;0755&lt;/code&gt;: owner rwx+group/r-others rx (executable scripts)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;0600&lt;/code&gt;: owner-only rw (secrets, keys—safe where restricted)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Verbose Listing
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python perm-audit.py &lt;span class="nt"&gt;-d&lt;/span&gt; /home/projects &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Detailed view with flags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;/&lt;span class="n"&gt;path&lt;/span&gt;/&lt;span class="n"&gt;to&lt;/span&gt;/&lt;span class="n"&gt;file1&lt;/span&gt;.&lt;span class="n"&gt;py&lt;/span&gt;              [&lt;span class="m"&gt;644&lt;/span&gt;] &lt;span class="n"&gt;executable&lt;/span&gt;=&lt;span class="n"&gt;False&lt;/span&gt; &lt;span class="n"&gt;writable&lt;/span&gt;=&lt;span class="n"&gt;False&lt;/span&gt;
/&lt;span class="n"&gt;path&lt;/span&gt;/&lt;span class="n"&gt;to&lt;/span&gt;/&lt;span class="n"&gt;public&lt;/span&gt;/&lt;span class="n"&gt;index&lt;/span&gt;.&lt;span class="n"&gt;html&lt;/span&gt;     [&lt;span class="m"&gt;644&lt;/span&gt;] &lt;span class="n"&gt;executable&lt;/span&gt;=&lt;span class="n"&gt;False&lt;/span&gt; &lt;span class="n"&gt;writable&lt;/span&gt;=&lt;span class="n"&gt;False&lt;/span&gt;
[&lt;span class="n"&gt;path&lt;/span&gt;!] /&lt;span class="n"&gt;etc&lt;/span&gt;/&lt;span class="n"&gt;passwd&lt;/span&gt;           [&lt;span class="m"&gt;0600&lt;/span&gt;] &lt;span class="n"&gt;owner_writable&lt;/span&gt; (&lt;span class="n"&gt;uid&lt;/span&gt; != &lt;span class="n"&gt;real&lt;/span&gt;-&lt;span class="n"&gt;user&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Truncated paths show first folder—useful for spotting unusual permissions across a tree.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security Check: World-Writable Only
&lt;/h3&gt;

&lt;p&gt;Before deploying, find files that allow others to modify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python perm-audit.py &lt;span class="nt"&gt;-d&lt;/span&gt; /var/www/html &lt;span class="nt"&gt;-w&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;World-writable (mode &lt;code&gt;06xx&lt;/code&gt;) means anyone with group access can write—common vulnerability when uploads directory isn't cleaned.&lt;/p&gt;

&lt;p&gt;Use for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shared team directories&lt;/strong&gt;: Verify sticky bit on &lt;code&gt;/tmp&lt;/code&gt; or correct group perms elsewhere&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web roots&lt;/strong&gt;: Flag writable scripts/CGI in world-writable directories
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migrations&lt;/strong&gt;: Detect files copied from external sources with loose permissions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Command Reference
&lt;/h2&gt;

&lt;p&gt;All commands use &lt;code&gt;-d&lt;/code&gt; with directory to audit:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Source directory (required)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-l,--depth&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Max depth (default=2, 0=all paths)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;`-v&lt;/td&gt;
&lt;td&gt;Verbose listing (all files)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;`-w&lt;/td&gt;
&lt;td&gt;World-writable/security check only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Common Workflows
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Full Security Scan
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python perm-audit.py &lt;span class="nt"&gt;-d&lt;/span&gt; /home/developer &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Octal permissions sorted by count&lt;/li&gt;
&lt;li&gt;Writable flags if group/others can write&lt;/li&gt;
&lt;li&gt;Owner-writable flag for UID mismatches&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  CI/CD Fail Gate
&lt;/h4&gt;

&lt;p&gt;Find &lt;code&gt;0777&lt;/code&gt; files in build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python perm-audit.py &lt;span class="nt"&gt;-d&lt;/span&gt; /tmp/build-artifact &lt;span class="nt"&gt;-v&lt;/span&gt; 2&amp;gt;&amp;amp;1 | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"0777"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Typically indicates scripts copied with full permissions before packaging. For sensitive environments, world-writable files should be rejected by security gates since any user in group can modify file contents.&lt;/p&gt;

&lt;h4&gt;
  
  
  Quick Summary on Production System
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python perm-audit.py &lt;span class="nt"&gt;-d&lt;/span&gt; /var/www/html &lt;span class="nt"&gt;-v&lt;/span&gt; 2&amp;gt;/dev/null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Shows unique permission set. Common for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data files &lt;code&gt;644&lt;/code&gt; or &lt;code&gt;600&lt;/code&gt; (read-only by group/others)&lt;/li&gt;
&lt;li&gt;Executables/sidecars &lt;code&gt;755&lt;/code&gt; or &lt;code&gt;664&lt;/code&gt; (when group owns binaries)&lt;/li&gt;
&lt;li&gt;Configs &lt;code&gt;600&lt;/code&gt; in restricted folders, &lt;code&gt;644&lt;/code&gt; when shared config group exists&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Code Example
&lt;/h2&gt;

&lt;p&gt;The scanner walks directories and builds a permission table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;iterate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;stat_info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;S_IMODE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stat_info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;st_mode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mo"&gt;0o777&lt;/span&gt;

            &lt;span class="c1"&gt;# Security flags
&lt;/span&gt;            &lt;span class="n"&gt;is_world_writable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;S_IWOTH&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;is_owner_writable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;st_uid&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getuid&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;OSError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;dirs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;path&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mode&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;(error)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Octal representation uses &lt;code&gt;stat()&lt;/code&gt; to extract permission bits. Special bits like setuid/setgid are stripped—only user/group/others bits reported.&lt;/p&gt;

&lt;p&gt;World-writable check uses &lt;code&gt;mode &amp;amp; stat.S_IWOTH&lt;/code&gt; (octal &lt;code&gt;02&lt;/code&gt; bit). Owner-writable flag triggers when file's UID differs from real user—indicates leftover write access after cleanup or elevated process creation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Cases Compared
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Manual Check&lt;/th&gt;
&lt;th&gt;This Tool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Find world-writable files&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;find -perm o+w&lt;/code&gt; (slow)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;-w&lt;/code&gt; flag, instant report&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Permission summary&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ls -la&lt;/code&gt; page-heavy&lt;/td&gt;
&lt;td&gt;Compact table/count mode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compliance audit&lt;/td&gt;
&lt;td&gt;Multiple runs&lt;/td&gt;
&lt;td&gt;Single pass, sorted output&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Tools like &lt;code&gt;find . ! -type d -perm /o+w&lt;/code&gt; exist but produce verbose listings and require parsing. This tool normalizes to permission code with count summaries—easier for humans scanning results. Web servers show many &lt;code&gt;644&lt;/code&gt; HTML files while &lt;code&gt;0600&lt;/code&gt; config files dominate backend directories. When group permissions expand, files may shift to &lt;code&gt;0775&lt;/code&gt; (executable with group write)—unusual in production environments where binaries should be read-only except for the owner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Audit Checklist
&lt;/h2&gt;

&lt;p&gt;Use this tool to verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Secrets/keys in &lt;code&gt;0600&lt;/code&gt;, not shared-access modes&lt;/li&gt;
&lt;li&gt;[ ] Web roots have no world-writable subdirectories&lt;/li&gt;
&lt;li&gt;[ ] Executables are &lt;code&gt;755&lt;/code&gt; or group-read only (&lt;code&gt;754&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;[ ] No scripts with &lt;code&gt;0777&lt;/code&gt; in production paths&lt;/li&gt;
&lt;li&gt;[ ] Logs rotate before accumulation and permissions reset to &lt;code&gt;0644&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Source Code
&lt;/h2&gt;

&lt;p&gt;Public repo with examples. Readable, dependency-free, suitable as starting point for security automation or policy enforcement tools.&lt;/p&gt;

&lt;p&gt;🔗 &lt;strong&gt;Repo&lt;/strong&gt;: &lt;a href="https://github.com/Poolion/perm-audit-cli" rel="noopener noreferrer"&gt;https://github.com/Poolion/perm-audit-cli&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you find this useful, you can support development: &lt;a href="https://www.buymeacoffee.com/poolion" rel="noopener noreferrer"&gt;https://www.buymeacoffee.com/poolion&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>security</category>
      <category>cli</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
