Drill 02: File Operations

Read, write, JSON, CSV, and modern pathlib patterns.

Run This Drill

bash ~/atelier/_bibliotheca/domus-captures/docs/modules/ROOT/examples/python-drills/02-file-ops.sh

Drill Script

#!/bin/bash
# PYTHON DRILL 02: FILE OPERATIONS
# Paste this entire script into your terminal
# Topics: Reading, writing, JSON, CSV, Path

# Create test files
cat << 'EOF' > /tmp/servers.txt
ise-01,10.50.1.20,443
ise-02,10.50.1.21,443
bind-01,10.50.1.90,53
vault-01,10.50.1.132,8200
EOF

cat << 'EOF' > /tmp/config.json
{
  "hostname": "ise-01",
  "ip": "10.50.1.20",
  "port": 443,
  "enabled": true,
  "roles": ["pan", "mnt"]
}
EOF

echo "=================================================================="
echo "             PYTHON DRILL 02: FILE OPERATIONS                    "
echo "=================================================================="
echo ""
echo "Test files created: /tmp/servers.txt, /tmp/config.json"
echo ""

# ---------------------------------------------------------------------------
echo "------------------------------------------------------------------"
echo "DRILL 2.1: READING TEXT FILES"
echo "Three ways to read files"
echo "------------------------------------------------------------------"
echo ""
python3 << 'PYEOF'
# Method 1: Read entire file
with open("/tmp/servers.txt") as f:
    content = f.read()
print("=== Read entire file ===")
print(content)

# Method 2: Read as list of lines
with open("/tmp/servers.txt") as f:
    lines = f.readlines()
print(f"=== As list ({len(lines)} lines) ===")
print(lines)

# Method 3: Iterate (memory efficient)
print("=== Iterate line by line ===")
with open("/tmp/servers.txt") as f:
    for line in f:
        print(f"  {line.strip()}")
PYEOF
echo ""

# ---------------------------------------------------------------------------
echo "------------------------------------------------------------------"
echo "DRILL 2.2: PARSING CSV-LIKE DATA"
echo "Split lines into fields"
echo "------------------------------------------------------------------"
echo ""
python3 << 'PYEOF'
servers = []
with open("/tmp/servers.txt") as f:
    for line in f:
        hostname, ip, port = line.strip().split(",")
        servers.append({
            "hostname": hostname,
            "ip": ip,
            "port": int(port)
        })

print("=== Parsed servers ===")
for server in servers:
    print(f"  {server['hostname']}: {server['ip']}:{server['port']}")

# Filter to specific port
port_443 = [s for s in servers if s["port"] == 443]
print(f"\n=== Port 443 only ===")
for s in port_443:
    print(f"  {s['hostname']}")
PYEOF
echo ""

# ---------------------------------------------------------------------------
echo "------------------------------------------------------------------"
echo "DRILL 2.3: JSON FILES"
echo "Read, modify, write JSON"
echo "------------------------------------------------------------------"
echo ""
python3 << 'PYEOF'
import json

# Read JSON
with open("/tmp/config.json") as f:
    config = json.load(f)

print("=== Read JSON ===")
print(f"Hostname: {config['hostname']}")
print(f"Roles: {config['roles']}")

# Modify
config["status"] = "active"
config["roles"].append("psn")

# Write JSON (pretty)
with open("/tmp/config_updated.json", "w") as f:
    json.dump(config, f, indent=2)

print("\n=== Written to /tmp/config_updated.json ===")
with open("/tmp/config_updated.json") as f:
    print(f.read())

# JSON string operations
data = {"key": "value", "num": 42}
json_str = json.dumps(data)
print(f"\n=== dumps (to string): {json_str}")

parsed = json.loads(json_str)
print(f"=== loads (from string): {parsed}")
PYEOF
echo ""

# ---------------------------------------------------------------------------
echo "------------------------------------------------------------------"
echo "DRILL 2.4: CSV MODULE"
echo "Proper CSV handling with headers"
echo "------------------------------------------------------------------"
echo ""
python3 << 'PYEOF'
import csv

# Write CSV with headers
servers = [
    {"hostname": "web-01", "ip": "10.50.10.101", "port": 80},
    {"hostname": "db-01", "ip": "10.50.10.50", "port": 5432},
    {"hostname": "cache-01", "ip": "10.50.10.60", "port": 6379}
]

with open("/tmp/inventory.csv", "w", newline="") as f:
    writer = csv.DictWriter(f, fieldnames=["hostname", "ip", "port"])
    writer.writeheader()
    writer.writerows(servers)

print("=== Written /tmp/inventory.csv ===")

# Read CSV with headers
with open("/tmp/inventory.csv") as f:
    reader = csv.DictReader(f)
    print("\n=== Read back ===")
    for row in reader:
        print(f"  {row['hostname']}: {row['ip']}")
PYEOF
echo ""

# ---------------------------------------------------------------------------
echo "------------------------------------------------------------------"
echo "DRILL 2.5: PATHLIB (MODERN PATH HANDLING)"
echo "Object-oriented filesystem paths"
echo "------------------------------------------------------------------"
echo ""
python3 << 'PYEOF'
from pathlib import Path

# Current directory
cwd = Path.cwd()
print(f"CWD: {cwd}")

# Path manipulation
config_path = Path("/etc/ssh/sshd_config")
print(f"Path: {config_path}")
print(f"Name: {config_path.name}")
print(f"Stem: {config_path.stem}")
print(f"Suffix: {config_path.suffix}")
print(f"Parent: {config_path.parent}")
print(f"Exists: {config_path.exists()}")

# Joining paths
base = Path("/home/user")
full = base / "projects" / "infra"
print(f"\nJoined: {full}")

# Glob for files
tmp = Path("/tmp")
txt_files = list(tmp.glob("*.txt"))
print(f"\n*.txt in /tmp: {[f.name for f in txt_files[:5]]}")

# Read/write with pathlib
p = Path("/tmp/test_pathlib.txt")
p.write_text("Hello from pathlib!\n")
print(f"\nRead back: {p.read_text()}")
PYEOF
echo ""

# ---------------------------------------------------------------------------
echo "------------------------------------------------------------------"
echo "DRILL 2.6: ERROR HANDLING"
echo "Graceful file operation failures"
echo "------------------------------------------------------------------"
echo ""
python3 << 'PYEOF'
from pathlib import Path

# Try to read non-existent file
try:
    with open("/tmp/nonexistent.txt") as f:
        content = f.read()
except FileNotFoundError:
    print("File not found - handled gracefully")

# Check before reading
path = Path("/tmp/maybe_exists.txt")
if path.exists():
    content = path.read_text()
else:
    print(f"{path} does not exist")

# Handle JSON parse errors
import json
bad_json = "{ invalid json }"
try:
    data = json.loads(bad_json)
except json.JSONDecodeError as e:
    print(f"JSON parse error: {e}")
PYEOF
echo ""

# ---------------------------------------------------------------------------
echo "------------------------------------------------------------------"
echo "YOUR TURN - TRY THESE:"
echo "------------------------------------------------------------------"
echo ""
echo "1. Read /etc/passwd first 5 lines:"
echo "   with open('/etc/passwd') as f:"
echo "       for i, line in enumerate(f):"
echo "           if i >= 5: break"
echo "           print(line.strip())"
echo ""
echo "2. Write JSON to file:"
echo "   import json"
echo "   data = {'hosts': ['web', 'db']}"
echo "   Path('/tmp/hosts.json').write_text(json.dumps(data, indent=2))"
echo ""
echo "3. Find all .sh files in /tmp:"
echo "   from pathlib import Path"
echo "   list(Path('/tmp').glob('*.sh'))"
echo ""
echo "------------------------------------------------------------------"
echo "KEY TAKEAWAYS:"
echo "1. Always use 'with open()' for auto-close"
echo "2. json.load(file) / json.dump(data, file)"
echo "3. json.loads(string) / json.dumps(data)"
echo "4. csv.DictReader/DictWriter for headers"
echo "5. pathlib.Path for modern path handling"
echo "------------------------------------------------------------------"