Claude Code for fileinput: Python Multi-File Line Iterator — Claude Skills 360 Blog
Blog / AI / Claude Code for fileinput: Python Multi-File Line Iterator
AI

Claude Code for fileinput: Python Multi-File Line Iterator

Published: November 9, 2028
Read time: 5 min read
By: Claude Skills 360

Python’s fileinput module iterates over lines from multiple files (or stdin) as a single stream, with per-line metadata and optional in-place editing. import fileinput. Basic use: for line in fileinput.input(files) — transparently opens each file in files; if files=() reads from stdin or sys.argv[1:]. Per-line metadata: fileinput.filename() → current file path string; fileinput.fileno() → file descriptor int; fileinput.lineno() → cumulative line count across all files; fileinput.filelineno() → line number within current file; fileinput.isfirstline() → True on line 1 of each file; fileinput.isstdin() → True when reading stdin. Control: fileinput.nextfile() — skip rest of current file; fileinput.close(). Open hook: fileinput.input(files, openhook=fileinput.hook_compressed) — auto-decompresses .gz/.bz2 files; fileinput.hook_encoded("utf-8") — decodes with specified charset. In-place editing: fileinput.input(files, inplace=True) — redirects stdout back to each file; print(modified_line, end="") writes the replacement. Context manager: with fileinput.input(files) as f: for line in f:. Class API: fileinput.FileInput(files, ...). Claude Code generates multi-file search tools, sed-style in-place editors, line number annotators, and grep replacement utilities.

CLAUDE.md for fileinput

## fileinput Stack
- Stdlib: import fileinput
- Read:   for line in fileinput.input(files):
-             print(fileinput.filename(), fileinput.filelineno(), line, end="")
- Stdin:  for line in fileinput.input():    # reads sys.argv[1:] or stdin
- Inplace: with fileinput.input(files, inplace=True) as f:
-              for line in f: print(line.replace("old", "new"), end="")
- Hooks:  fileinput.hook_compressed   # auto-decompress .gz .bz2
-         fileinput.hook_encoded("utf-8")

fileinput Multi-File Line Pipeline

# app/fileinpututil.py — search, replace, annotate, compress, grep, context
from __future__ import annotations

import fileinput
import re
import shutil
import tempfile
from dataclasses import dataclass, field
from pathlib import Path


# ─────────────────────────────────────────────────────────────────────────────
# 1. Line iterator with metadata
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class LineRecord:
    filename: str
    lineno:   int    # within file
    global_n: int    # cumulative across all files
    text:     str    # with trailing newline stripped
    is_first: bool   # first line of file
    is_stdin: bool

    def __str__(self) -> str:
        return f"{self.filename}:{self.lineno}: {self.text}"


def iter_lines(
    files: list[str | Path] | None = None,
    encoding: str = "utf-8",
    compressed: bool = False,
) -> "Iterator[LineRecord]":
    """
    Iterate over lines from files (or stdin) producing LineRecord objects.
    If compressed=True uses hook_compressed for .gz/.bz2 transparency.

    Example:
        for rec in iter_lines(["a.txt", "b.txt"]):
            print(rec)
    """
    str_files = [str(f) for f in (files or [])]
    if compressed:
        hook = fileinput.hook_compressed
    else:
        hook = fileinput.hook_encoded(encoding)

    with fileinput.input(str_files, openhook=hook) as fi:
        for raw in fi:
            yield LineRecord(
                filename=fi.filename() or "<stdin>",
                lineno=fi.filelineno(),
                global_n=fi.lineno(),
                text=raw.rstrip("\n\r"),
                is_first=fi.isfirstline(),
                is_stdin=fi.isstdin(),
            )


# ─────────────────────────────────────────────────────────────────────────────
# 2. Grep — search across multiple files
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class GrepMatch:
    filename: str
    lineno:   int
    text:     str
    match:    re.Match

    def __str__(self) -> str:
        return f"{self.filename}:{self.lineno}: {self.text}"


def grep(
    pattern: str | re.Pattern,
    files: list[str | Path],
    ignore_case: bool = False,
    invert: bool = False,
    encoding: str = "utf-8",
) -> list[GrepMatch]:
    """
    Grep multiple files for a regex pattern.
    Returns list of GrepMatch objects.

    Example:
        matches = grep(r"def \\w+\\(", ["a.py", "b.py"])
        for m in matches:
            print(m)
    """
    flags = re.IGNORECASE if ignore_case else 0
    if isinstance(pattern, str):
        pat = re.compile(pattern, flags)
    else:
        pat = pattern

    results = []
    for rec in iter_lines(files, encoding=encoding):
        m = pat.search(rec.text)
        hit = m is not None
        if (hit and not invert) or (not hit and invert):
            results.append(GrepMatch(
                filename=rec.filename,
                lineno=rec.lineno,
                text=rec.text,
                match=m,
            ))
    return results


def count_pattern(
    pattern: str,
    files: list[str | Path],
    ignore_case: bool = False,
) -> dict[str, int]:
    """
    Count pattern occurrences per file.

    Example:
        counts = count_pattern(r"TODO", ["a.py", "b.py"])
        for fname, n in sorted(counts.items()):
            print(f"  {n:4d}  {fname}")
    """
    flags = re.IGNORECASE if ignore_case else 0
    pat = re.compile(pattern, flags)
    counts: dict[str, int] = {}
    for rec in iter_lines(files):
        key = rec.filename
        if key not in counts:
            counts[key] = 0
        if pat.search(rec.text):
            counts[key] += 1
    return counts


# ─────────────────────────────────────────────────────────────────────────────
# 3. In-place editing
# ─────────────────────────────────────────────────────────────────────────────

def inplace_replace(
    files: list[str | Path],
    old: str,
    new: str,
    regex: bool = False,
    backup: str = "",
    encoding: str = "utf-8",
) -> dict[str, int]:
    """
    Replace old with new in files in-place.
    If regex=True, old is treated as a regular expression.
    Returns {filename: count_of_replacements}.

    Example:
        counts = inplace_replace(["a.py"], "print ", "log.info(")
        inplace_replace(["b.py"], r"\\bFOO\\b", "BAR", regex=True)
    """
    str_files = [str(f) for f in files]
    counts: dict[str, int] = {}

    with fileinput.input(str_files, inplace=True,
                         backup=backup, encoding=encoding) as fi:
        for line in fi:
            fname = fi.filename()
            if regex:
                new_line, n = re.subn(old, new, line)
            else:
                new_line = line.replace(old, new)
                n = line.count(old)
            counts[fname] = counts.get(fname, 0) + n
            print(new_line, end="")
    return counts


def inplace_filter(
    files: list[str | Path],
    fn,
    backup: str = "",
    encoding: str = "utf-8",
) -> None:
    """
    Apply fn(line_text) → str | None to each line.
    If fn returns None, the line is deleted; otherwise the returned string is written.

    Example:
        # Remove blank lines
        inplace_filter(["a.txt"], lambda line: None if not line.strip() else line)
    """
    str_files = [str(f) for f in files]
    with fileinput.input(str_files, inplace=True,
                         backup=backup, encoding=encoding) as fi:
        for line in fi:
            result = fn(line.rstrip("\n\r"))
            if result is not None:
                print(result)


# ─────────────────────────────────────────────────────────────────────────────
# 4. Annotators
# ─────────────────────────────────────────────────────────────────────────────

def annotate_line_numbers(
    files: list[str | Path],
    separator: str = "  ",
    encoding: str = "utf-8",
) -> str:
    """
    Return the contents of all files with line numbers prefixed.

    Example:
        print(annotate_line_numbers(["a.py"]))
    """
    lines = []
    for rec in iter_lines(files, encoding=encoding):
        lines.append(f"{rec.lineno:4d}{separator}{rec.text}")
    return "\n".join(lines)


def file_summary(files: list[str | Path], encoding: str = "utf-8") -> dict:
    """
    Return a summary dict per file: {filename: {lines, chars, blank_lines}}.

    Example:
        for fname, info in file_summary(["a.py", "b.py"]).items():
            print(fname, info)
    """
    summary: dict[str, dict] = {}
    for rec in iter_lines(files, encoding=encoding):
        s = summary.setdefault(rec.filename, {"lines": 0, "chars": 0, "blank_lines": 0})
        s["lines"] += 1
        s["chars"] += len(rec.text)
        if not rec.text.strip():
            s["blank_lines"] += 1
    return summary


def first_lines(files: list[str | Path], n: int = 10) -> dict[str, list[str]]:
    """
    Return the first n lines of each file.

    Example:
        heads = first_lines(["a.py", "b.py"], n=5)
        for fname, lines in heads.items():
            print(fname, lines)
    """
    result: dict[str, list[str]] = {}
    fi = fileinput.input([str(f) for f in files])
    try:
        for raw in fi:
            fname = fi.filename()
            bucket = result.setdefault(fname, [])
            if len(bucket) < n:
                bucket.append(raw.rstrip("\n\r"))
            elif fi.filelineno() > n:
                fi.nextfile()   # done with this file, skip to next
    finally:
        fi.close()
    return result


# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────

if __name__ == "__main__":
    import os
    print("=== fileinput demo ===")

    with tempfile.TemporaryDirectory() as tmp:
        # Create test files
        fa = Path(tmp) / "a.py"
        fb = Path(tmp) / "b.py"
        fa.write_text(
            "# File A\ndef hello():\n    print('hello')\n\nhello()\n",
            encoding="utf-8"
        )
        fb.write_text(
            "# File B\n# TODO: implement feature\ndef foo(x):\n    return x * 2\n\n",
            encoding="utf-8"
        )

        # ── iter_lines ─────────────────────────────────────────────────────────
        print("\n--- iter_lines ---")
        for rec in iter_lines([fa, fb]):
            marker = "▶" if rec.is_first else " "
            print(f"  {marker} {rec}")

        # ── grep ───────────────────────────────────────────────────────────────
        print("\n--- grep 'def ' ---")
        for m in grep(r"def \w+", [fa, fb]):
            print(f"  {m}")

        # ── count_pattern ─────────────────────────────────────────────────────
        print("\n--- count_pattern '#' ---")
        counts = count_pattern(r"^#", [fa, fb])
        for fname, n in counts.items():
            print(f"  {fname}: {n} comment lines")

        # ── annotate_line_numbers ─────────────────────────────────────────────
        print("\n--- annotate_line_numbers(a.py) ---")
        for line in annotate_line_numbers([fa]).splitlines()[:5]:
            print(f"  {line}")

        # ── file_summary ──────────────────────────────────────────────────────
        print("\n--- file_summary ---")
        for fname, info in file_summary([fa, fb]).items():
            print(f"  {Path(fname).name}: {info}")

        # ── first_lines ───────────────────────────────────────────────────────
        print("\n--- first_lines(n=3) ---")
        for fname, lines in first_lines([fa, fb], n=3).items():
            print(f"  {Path(fname).name}: {lines}")

        # ── inplace_replace ───────────────────────────────────────────────────
        print("\n--- inplace_replace 'print' → 'log.info' ---")
        counts_rep = inplace_replace([fa], "print", "log.info")
        print(f"  replacements: {counts_rep}")
        print(f"  fa after: {fa.read_text()[:80]!r}")

        # ── inplace_filter (remove blank lines) ───────────────────────────────
        print("\n--- inplace_filter (remove blank lines from b.py) ---")
        blank_before = fb.read_text().count("\n\n")
        inplace_filter([fb], lambda line: None if not line.strip() else line)
        blank_after = fb.read_text().count("\n\n")
        print(f"  blank lines before: {blank_before}  after: {blank_after}")

    print("\n=== done ===")

For the pathlib + open() alternative — for path in paths: for line in path.open(): provides the same multi-file line reading with explicit file management, path.name for filename, and a manual line counter variable — use plain open() loops when you know which files you’re processing and don’t need isfirstline(), nextfile(), or in-place editing; use fileinput when you want transparent stdin fallback, automatic compressed-file decompression, or in-place editing that handles file replacement safely (backup suffix, atomic write-back). For the grep CLI alternative — subprocess.run(["grep", "-r", "-n", pattern] + files, capture_output=True) delegates to the system grep with full POSIX regex support, binary file awareness, and parallel I/O — use system grep for large codebases or performance-sensitive searches; use fileinput.input() + re.search() when you need Python logic per line (e.g., multi-field extraction, conditional transformations, or accumulating state across lines). The Claude Skills 360 bundle includes fileinput skill sets covering LineRecord with iter_lines() metadata iterator, GrepMatch with grep()/count_pattern() multi-file search, inplace_replace()/inplace_filter() in-place editors, and annotate_line_numbers()/file_summary()/first_lines(). Start with the free tier to try multi-file line processing patterns and fileinput pipeline code generation.

Keep Reading

AI

Claude Code for email.contentmanager: Python Email Content Accessors

Read and write EmailMessage body content with Python's email.contentmanager module and Claude Code — email contentmanager ContentManager for the class that maps content types to get and set handler functions allowing EmailMessage to support get_content and set_content with type-specific behaviour, email contentmanager raw_data_manager for the ContentManager instance that handles raw bytes and str payloads without any conversion, email contentmanager content_manager for the standard ContentManager instance used by email.policy.default that intelligently handles text plain text html multipart and binary content types, email contentmanager get_content_text for the handler that returns the decoded text payload of a text-star message part as a str, email contentmanager get_content_binary for the handler that returns the raw decoded bytes payload of a non-text message part, email contentmanager get_data_manager for the get-handler lookup used by EmailMessage get_content to find the right reader function for the content type, email contentmanager set_content text for the handler that creates and sets a text part correctly choosing charset and transfer encoding, email contentmanager set_content bytes for the handler that creates and sets a binary part with base64 encoding and optional filename Content-Disposition, email contentmanager EmailMessage get_content for the method that reads the message body using the registered content manager handlers, email contentmanager EmailMessage set_content for the method that sets the message body and MIME headers in one call, email contentmanager EmailMessage make_alternative make_mixed make_related for the methods that convert a simple message into a multipart container, email contentmanager EmailMessage add_attachment for the method that attaches a file or bytes to a multipart message, and email contentmanager integration with email.message and email.policy and email.mime and io for building high-level email readers attachment extractors text body accessors HTML readers and policy-aware MIME construction pipelines.

5 min read Feb 12, 2029
AI

Claude Code for email.charset: Python Email Charset Encoding

Control header and body encoding for international email with Python's email.charset module and Claude Code — email charset Charset for the class that wraps a character set name with the encoding rules for header encoding and body encoding describing how to encode text for that charset in email messages, email charset Charset header_encoding for the attribute specifying whether headers using this charset should use QP quoted-printable encoding BASE64 encoding or no encoding, email charset Charset body_encoding for the attribute specifying the Content-Transfer-Encoding to use for message bodies in this charset such as QP or BASE64, email charset Charset output_codec for the attribute giving the Python codec name used to encode the string to bytes for the wire format, email charset Charset input_codec for the attribute giving the Python codec name used to decode incoming bytes to str, email charset Charset get_output_charset for returning the output charset name, email charset Charset header_encode for encoding a header string using the charset's header_encoding method, email charset Charset body_encode for encoding body content using the charset's body_encoding, email charset Charset convert for converting a string from the input_codec to the output_codec, email charset add_charset for registering a new charset with custom encoding rules in the global charset registry, email charset add_alias for adding an alias name that maps to an existing registered charset, email charset add_codec for registering a codec name mapping for use by the charset machinery, and email charset integration with email.message and email.mime and email.policy and email.encoders for building international email senders non-ASCII header encoders Content-Transfer-Encoding selectors charset-aware message constructors and MIME encoding pipelines.

5 min read Feb 11, 2029
AI

Claude Code for email.utils: Python Email Address and Header Utilities

Parse and format RFC 2822 email addresses and dates with Python's email.utils module and Claude Code — email utils parseaddr for splitting a display-name plus angle-bracket address string into a realname and email address tuple, email utils formataddr for combining a realname and address string into a properly quoted RFC 2822 address with angle brackets, email utils getaddresses for parsing a list of raw address header strings each potentially containing multiple comma-separated addresses into a list of realname address tuples, email utils parsedate for parsing an RFC 2822 date string into a nine-tuple compatible with time.mktime, email utils parsedate_tz for parsing an RFC 2822 date string into a ten-tuple that includes the UTC offset timezone in seconds, email utils parsedate_to_datetime for parsing an RFC 2822 date string into an aware datetime object with timezone, email utils formatdate for formatting a POSIX timestamp or the current time as an RFC 2822 date string with optional usegmt and localtime flags, email utils format_datetime for formatting a datetime object as an RFC 2822 date string, email utils make_msgid for generating a globally unique Message-ID string with optional idstring and domain components, email utils decode_rfc2231 for decoding an RFC 2231 encoded parameter value into a tuple of charset language and value, email utils encode_rfc2231 for encoding a string as an RFC 2231 encoded parameter value, email utils collapse_rfc2231_value for collapsing a decoded RFC 2231 tuple to a Unicode string, and email utils integration with email.message and email.headerregistry and datetime and time for building address parsers date formatters message-id generators header extractors and RFC-compliant email construction utilities.

5 min read Feb 10, 2029

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free