ᛉ Yggdrasil

Linux

Text Parsing

15 Sep 2026 · 4,612 words · 21 min

Contents

Legend: [CLI] = Run in terminal | [SCRIPT] = Inside a script | [REPL] = Interactive shell/REPL | [WIN] = Windows-specific


1. Introduction — Parsing Concepts

What it means: Text parsing is extracting structured or semi-structured data from raw text, files, or data streams. In recon and red teaming, you'll constantly handle JSON API responses, XML configs, raw logs, CSV exports, and HTML pages — each needing different tools.

Format → Tool Map

Format Best CLI Tool Best Python Lib Use Case
JSON jq json, jmespath API responses, configs
XML xmllint, xpath lxml, xml.etree SOAP, configs, nmap output
YAML yq pyyaml Kubernetes, Ansible, configs
HTML pup BeautifulSoup Web scraping, recon
CSV/TSV awk, mlr csv, pandas Exports, scan results
Plain text / logs grep, awk, sed re Log analysis, recon output

Parsing vs Extraction vs Transformation

Term Meaning
Parsing Reading structured data and navigating its tree/fields
Extraction Pulling specific values from parsed data
Transformation Reshaping or reformatting the data
Filtering Selecting only records that match a condition

2. jq — JSON Parsing

What it means: jq is a lightweight CLI tool to slice, filter, map, and transform JSON. It's the sed/awk equivalent for JSON. Essential for working with API responses, nmap JSON output, curl results, and any JSON-heavy recon data.


2.1 Installation

# Debian/Ubuntu/Kali
sudo apt install jq

# Arch
sudo pacman -S jq

# macOS
brew install jq

2.2 Basic Field Access

# Pretty-print entire JSON
[CLI] cat data.json | jq '.'
[CLI] curl -s https://api.example.com/users | jq '.'

# Access a top-level field
[CLI] jq '.name' data.json

# Nested field access
[CLI] jq '.user.email' data.json

# Access array element by index
[CLI] jq '.' data.json            # first element
[CLI] jq '.[-1]' data.json           # last element
[CLI] jq '.[2:5]' data.json          # slice index 2 to 4

2.3 Iterating Arrays

# Iterate all elements of an array
[CLI] jq '.[]' data.json

# Extract a field from every object in an array
[CLI] jq '.[].name' data.json
[CLI] jq '.[].ip' hosts.json

# Compact output (one result per line, no pretty-print)
[CLI] jq -r '.[].name' data.json     # -r = raw string (no quotes)

2.4 Filtering & Conditions

# Select objects matching a condition
[CLI] jq '.[] | select(.status == "open")' data.json
[CLI] jq '.[] | select(.port == 443)' nmap.json
[CLI] jq '.[] | select(.age > 30)' users.json

# Multiple conditions (and)
[CLI] jq '.[] | select(.status == "open" and .port == 80)' data.json

# Contains / string match
[CLI] jq '.[] | select(.name | contains("admin"))' data.json
[CLI] jq '.[] | select(.host | startswith("192.168"))' data.json

2.5 Transforming Output

# Build a new object from fields
[CLI] jq '.[] | {host: .address, port: .port}' data.json

# Extract specific fields as CSV
[CLI] jq -r '.[] | [.name, .ip, .port] | @csv' data.json

# Extract as TSV
[CLI] jq -r '.[] | [.name, .ip] | @tsv' data.json

# Join array of strings
[CLI] jq '.tags | join(", ")' data.json

# Convert keys to array
[CLI] jq 'keys' data.json

# Get all values
[CLI] jq 'values' data.json

2.6 Sorting & Grouping

# Sort array by a field
[CLI] jq 'sort_by(.port)' data.json
[CLI] jq 'sort_by(.name) | reverse' data.json

# Group by a field
[CLI] jq 'group_by(.status)' data.json

# Unique values of a field
[CLI] jq '[.[].status] | unique' data.json

# Count elements
[CLI] jq 'length' data.json
[CLI] jq '.[] | select(.open == true) | .port' data.json | wc -l

2.7 Useful One-Liners (Red Team)

# Extract all open ports from nmap JSON output
[CLI] jq -r '.[] | .ports[]? | select(.state.state=="open") | .portid' nmap.json

# Get all hostnames from nmap
[CLI] jq -r '.[].hostnames[]?.name' nmap.json

# Extract IPs from a JSON array of hosts
[CLI] jq -r '.[].address.addr' nmap.json

# Parse curl API response and grab token
[CLI] curl -s -X POST https://target/api/login -d '{"user":"admin","pass":"pass"}' \
  | jq -r '.token'

# Pretty print + filter in one pipeline
[CLI] cat response.json | jq '.data.users[] | select(.role == "admin") | .username'

# Read from file, filter, write to new file
[CLI] jq '[.[] | select(.port == 445)]' hosts.json > smb_hosts.json

2.8 jq Variables & Functions

# Store a value in a variable
[CLI] jq '.users[] | .name as $n | .roles[] | {user: $n, role: .}' data.json

# Define and call a function
[CLI] jq 'def double: . * 2; .value | double' data.json

# Map shorthand
[CLI] jq '[.[] | .port] | map(. + 1000)' data.json

# Reduce
[CLI] jq 'reduce .[] as $x (0; . + $x.count)' data.json

3. xmllint & xpath — XML Parsing

What it means: xmllint is a CLI tool from the libxml2 suite for parsing, validating, and querying XML. xpath expressions let you navigate the XML tree just like CSS selectors do for HTML. Common targets: nmap XML output, SOAP responses, config files, Active Directory LDAP exports.


3.1 Installation

# Debian/Ubuntu/Kali
sudo apt install libxml2-utils   # provides xmllint

# Arch
sudo pacman -S libxml2

# Python xpath (lxml)
pip install lxml

3.2 Basic xmllint Usage

# Pretty-print / format XML
[CLI] xmllint --format file.xml

# Validate XML structure
[CLI] xmllint --noout file.xml
# (no output = valid; errors printed if invalid)

# Validate against a schema (XSD)
[CLI] xmllint --schema schema.xsd file.xml --noout

# Output to file
[CLI] xmllint --format file.xml -o formatted.xml

3.3 xpath Queries with xmllint

# General syntax
[CLI] xmllint --xpath "<XPATH_EXPRESSION>" file.xml

# Get text content of an element
[CLI] xmllint --xpath "//title/text()" page.xml

# Get attribute value
[CLI] xmllint --xpath "//host/@name" nmap.xml
[CLI] xmllint --xpath "string(//user/email)" data.xml

# Get all elements matching a tag
[CLI] xmllint --xpath "//port" nmap.xml

# Filter by attribute value
[CLI] xmllint --xpath '//port[@protocol="tcp"]' nmap.xml

# Get child elements
[CLI] xmllint --xpath '//host/address/@addr' nmap.xml

# Grab all open ports from nmap XML
[CLI] xmllint --xpath '//port[state/@state="open"]/@portid' nmap.xml

3.4 xpath Quick Reference

Expression Meaning
/root/child Absolute path from root
//tag Any tag anywhere in document
//tag[@attr] Tag with attribute present
//tag[@attr="val"] Tag where attr equals value
//tag/text() Text content of tag
//tag/@attr Value of attribute
//tag[1] First matching element (1-indexed)
//tag[last()] Last matching element
//tag[contains(@attr,"val")] Attribute contains string
count(//tag) Count matching nodes

3.5 Python xml.etree (Built-in)

import xml.etree.ElementTree as ET

tree = ET.parse('nmap.xml')
root = tree.getroot()

# Iterate all host elements
for host in root.findall('.//host'):
    addr = host.find('address').get('addr')
    print(addr)

# Find all open ports
for port in root.findall('.//port'):
    state = port.find('state').get('state')
    if state == 'open':
        print(port.get('portid'))

# XPath with lxml (more powerful)
from lxml import etree

tree = etree.parse('data.xml')
# Full XPath support
results = tree.xpath('//port[state/@state="open"]/@portid')
print(results)

3.6 Python lxml — Advanced

from lxml import etree

tree = etree.parse('file.xml')

# Get all text content of matching nodes
titles = tree.xpath('//title/text()')

# Get elements with namespace
ns = {'ns': 'http://example.com/namespace'}
items = tree.xpath('//ns:item', namespaces=ns)

# Modify and serialize back
root = tree.getroot()
for elem in root.iter('password'):
    elem.text = '[REDACTED]'

print(etree.tostring(root, pretty_print=True).decode())

# Parse from string
xml_string = b'<root><item id="1">value</item></root>'
root = etree.fromstring(xml_string)
print(root.find('.//item[@id="1"]').text)

3.7 Useful One-Liners (Red Team)

# Extract all IPs from nmap XML
[CLI] xmllint --xpath '//address[@addrtype="ipv4"]/@addr' scan.xml \
  | tr ' ' '\n' | grep -oP '[\d.]+'

# Extract all open port numbers
[CLI] xmllint --xpath '//port[state/@state="open"]/@portid' scan.xml \
  | tr ' ' '\n' | grep -oP '\d+'

# Get all service names
[CLI] xmllint --xpath '//service/@name' scan.xml | tr ' ' '\n' | sort -u

# Extract all hostnames
[CLI] xmllint --xpath '//hostname/@name' scan.xml | tr ' ' '\n'

# Parse SOAP response and get value
[CLI] curl -s https://target/service | xmllint --xpath '//ResponseValue/text()' -

4. awk — Columnar Text Parsing

What it means: awk is a full programming language built for processing structured text, especially space/tab/delimiter-separated columns. It's the primary tool for log analysis, extracting specific fields, and performing calculations on text data.


4.1 Core Syntax

# awk 'PATTERN { ACTION }' file
# Runs ACTION for every line matching PATTERN
# No PATTERN = run on every line

# Built-in variables:
# $0 = entire line
# $1, $2, ... = fields (columns)
# NF = number of fields
# NR = current line number (record number)
# FS = field separator (default: whitespace)
# OFS = output field separator
# RS = record separator (default: newline)

4.2 Field Extraction

# Print first column
[CLI] awk '{print $1}' file.txt

# Print multiple columns
[CLI] awk '{print $1, $3, $5}' file.txt

# Print last column
[CLI] awk '{print $NF}' file.txt

# Print second-to-last
[CLI] awk '{print $(NF-1)}' file.txt

# Custom delimiter — extract username from /etc/passwd
[CLI] awk -F: '{print $1}' /etc/passwd

# Custom delimiter — extract IPs from CSV
[CLI] awk -F, '{print $2}' hosts.csv

# Tab-separated
[CLI] awk -F'\t' '{print $1}' data.tsv

4.3 Filtering with Patterns

# Lines containing a word
[CLI] awk '/admin/' /etc/passwd

# Lines NOT containing a word
[CLI] awk '!/root/' /etc/passwd

# Lines where field equals value
[CLI] awk '$3 == "open"' ports.txt

# Lines where field is greater than
[CLI] awk '$5 > 1000' data.txt

# Lines where field matches regex
[CLI] awk '$1 ~ /^192\.168/' hosts.txt

# Print lines from line 5 to 10
[CLI] awk 'NR>=5 && NR<=10' file.txt

# Skip header (first line)
[CLI] awk 'NR>1' file.txt

4.4 Calculations & Aggregation

# Sum a column
[CLI] awk '{sum += $3} END {print sum}' data.txt

# Average
[CLI] awk '{sum += $2; count++} END {print sum/count}' data.txt

# Count lines matching pattern
[CLI] awk '/FAILED/{count++} END {print count}' auth.log

# Count unique values in column 1
[CLI] awk '{count[$1]++} END {for (k in count) print k, count[k]}' data.txt | sort -k2 -rn

4.5 Output Formatting

# Print with custom separator
[CLI] awk '{print $1 "," $2 "," $3}' file.txt

# printf for formatted output
[CLI] awk '{printf "%-20s %s\n", $1, $2}' file.txt

# Change output separator globally
[CLI] awk 'BEGIN{OFS=","} {print $1, $2, $3}' file.txt

# Add header
[CLI] awk 'BEGIN{print "IP,Port,Status"} {print $1","$2","$3}' data.txt

4.6 Red Team One-Liners

# Extract usernames from shadow/passwd
[CLI] awk -F: '$3 >= 1000 {print $1}' /etc/passwd   # non-system users

# Extract failed SSH IPs from auth.log
[CLI] awk '/Failed password/{print $11}' /var/log/auth.log | sort | uniq -c | sort -rn

# Extract open ports from nmap plain output
[CLI] awk '/open/{print $1}' nmap_output.txt

# Extract unique C2 domains from a proxy log
[CLI] awk '{print $7}' proxy.log | grep -oP 'https?://[^/]+' | sort -u

# Print lines where column 2 is an IP
[CLI] awk '$2 ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/ {print $2}' data.txt

# Replace field and print
[CLI] awk '{$3="REDACTED"; print}' log.txt

5. sed — Stream Editing

What it means: sed (stream editor) processes text line-by-line and applies substitution, deletion, insertion, and transformation operations using regex. It's ideal for cleaning, modifying, and reformatting text data in pipelines.


5.1 Core Syntax

# sed 'COMMAND' file
# sed -i 'COMMAND' file    (edit in place)
# sed -n 'COMMAND' file    (suppress default output)

# Substitute: s/PATTERN/REPLACEMENT/FLAGS
# Flags: g=global, i=case-insensitive, p=print

5.2 Substitution

# Replace first occurrence per line
[CLI] sed 's/foo/bar/' file.txt

# Replace ALL occurrences per line (g flag)
[CLI] sed 's/foo/bar/g' file.txt

# Case-insensitive replace
[CLI] sed 's/foo/bar/gi' file.txt

# Replace only on lines matching a pattern
[CLI] sed '/admin/s/password/REDACTED/g' file.txt

# Replace and print only changed lines
[CLI] sed -n 's/foo/bar/gp' file.txt

# Edit file in place
[CLI] sed -i 's/old/new/g' file.txt

# Edit in place with backup
[CLI] sed -i.bak 's/old/new/g' file.txt

5.3 Deletion & Selection

# Delete lines matching pattern
[CLI] sed '/^#/d' config.txt          # delete comments
[CLI] sed '/^$/d' file.txt            # delete blank lines
[CLI] sed '/password/d' file.txt

# Print only lines matching pattern (-n + p)
[CLI] sed -n '/error/p' log.txt

# Print specific line number
[CLI] sed -n '5p' file.txt

# Print range of lines
[CLI] sed -n '5,10p' file.txt

# Delete first line (header)
[CLI] sed '1d' file.txt

# Delete last line
[CLI] sed '$d' file.txt

5.4 Insertion & Append

# Insert line BEFORE matching pattern
[CLI] sed '/pattern/i\New line before' file.txt

# Append line AFTER matching pattern
[CLI] sed '/pattern/a\New line after' file.txt

# Insert at line 3
[CLI] sed '3i\Inserted line' file.txt

# Add a header to a file
[CLI] sed -i '1i\# Generated by script' file.txt

5.5 Red Team One-Liners

# Remove ANSI color codes from tool output
[CLI] sed 's/\x1B\[[0-9;]*[mK]//g' colored_output.txt

# Extract IP addresses (print only lines with IPs)
[CLI] sed -n '/[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}/p' output.txt

# Remove blank lines and comments from config
[CLI] sed '/^$/d;/^#/d' config.conf

# Convert Windows line endings (CRLF → LF)
[CLI] sed -i 's/\r//' file.txt

# Mask passwords in a log file
[CLI] sed 's/password=[^ ]*/password=REDACTED/gi' app.log

# Extract URLs from a file
[CLI] sed -n 's/.*\(https\?:\/\/[^ "]*\).*/\1/p' file.txt

6. grep & regex — Pattern Matching

What it means: grep searches files/streams for lines matching a pattern. With -E (extended regex) or -P (Perl-compatible regex / PCRE), it becomes a powerful extraction tool. Essential for log triage, IOC hunting, and recon output processing.


6.1 grep Flags Reference

Flag Meaning
-i Case-insensitive
-r Recursive (search all files in dir)
-l Print only filenames with matches
-n Show line numbers
-v Invert match (lines NOT matching)
-c Count matching lines
-o Print only the matched portion
-E Extended regex (ERE)
-P Perl-compatible regex (PCRE)
-A N N lines after match
-B N N lines before match
-C N N lines before and after
--color Highlight matches

6.2 Common Patterns

# IP addresses (IPv4)
[CLI] grep -oP '\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b' file.txt

# Email addresses
[CLI] grep -oP '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}' file.txt

# URLs
[CLI] grep -oP 'https?://[^\s"<>]+' file.txt

# MD5 hashes
[CLI] grep -oP '\b[a-fA-F0-9]{32}\b' file.txt

# NTLM / SHA256 hashes
[CLI] grep -oP '\b[a-fA-F0-9]{64}\b' file.txt

# Windows file paths
[CLI] grep -oP '[A-Za-z]:\\[^\s"]+' file.txt

# Linux file paths
[CLI] grep -oP '(/[a-zA-Z0-9._-]+)+' file.txt

# Port numbers
[CLI] grep -oP ':\d{1,5}\b' file.txt

# Base64 strings
[CLI] grep -oP '[A-Za-z0-9+/]{20,}={0,2}' file.txt

# AWS keys
[CLI] grep -oP 'AKIA[0-9A-Z]{16}' file.txt

# Private keys
[CLI] grep -r 'BEGIN.*PRIVATE KEY' /etc/ 2>/dev/null

6.3 Recursive Recon Searches

# Find passwords in config files
[CLI] grep -rni 'password\|passwd\|pwd\|secret\|token\|api_key' /var/www/ 2>/dev/null

# Find hardcoded credentials in code
[CLI] grep -rn --include="*.php" 'mysql_connect\|PDO\|password' /var/www/

# Find SSH keys
[CLI] find / -name "id_rsa" 2>/dev/null | xargs grep -l 'PRIVATE'

# Find SUID binaries mentioning interesting strings
[CLI] find / -perm -4000 2>/dev/null | xargs ls -la

# Search in all log files
[CLI] grep -rn 'Failed password' /var/log/

# Find all IPs in a directory of scan results
[CLI] grep -rhoP '\b10\.\d+\.\d+\.\d+\b' ./scans/ | sort -u

7. Python Parsing

What it means: Python is the most versatile parsing environment. With built-in libraries (json, csv, xml, re) and third-party libs (lxml, BeautifulSoup, pyyaml, pandas), it handles any format. Use it when CLI tools aren't enough.


7.1 JSON — Built-in

import json

# Load from file
with open('data.json') as f:
    data = json.load(f)

# Load from string
data = json.loads('{"name": "admin", "port": 445}')

# Access fields
print(data['name'])
print(data.get('missing', 'default'))  # safe access with default

# Iterate array
for item in data['hosts']:
    print(item['ip'], item['port'])

# Filter
open_ports = [h for h in data['hosts'] if h['state'] == 'open']

# Write to file
with open('output.json', 'w') as f:
    json.dump(data, f, indent=2)

# Pretty print to stdout
print(json.dumps(data, indent=2))

7.2 Regex — re module

import re

text = "Found host 192.168.1.10 on port 445 (SMB open)"

# Search (first match)
match = re.search(r'\b\d{1,3}(?:\.\d{1,3}){3}\b', text)
if match:
    print(match.group())   # 192.168.1.10

# Find all matches
ips = re.findall(r'\b\d{1,3}(?:\.\d{1,3}){3}\b', text)

# Find with groups
match = re.search(r'port (\d+) \((\w+)', text)
if match:
    port = match.group(1)    # 445
    service = match.group(2) # SMB

# Named groups
match = re.search(r'host (?P<ip>[\d.]+) on port (?P<port>\d+)', text)
if match:
    print(match.group('ip'))
    print(match.group('port'))

# Substitute
cleaned = re.sub(r'password=\S+', 'password=REDACTED', text, flags=re.IGNORECASE)

# Split on multiple delimiters
parts = re.split(r'[,;|]', "a,b;c|d")

7.3 Subprocess + Parsing Pipeline

import subprocess
import json

# Run a command and parse JSON output
result = subprocess.run(
    ['nmap', '-oJ', '-', '192.168.1.0/24'],
    capture_output=True, text=True
)
data = json.loads(result.stdout)

# Run and grep output
result = subprocess.run(
    ['cat', '/etc/passwd'],
    capture_output=True, text=True
)
users = [line.split(':') for line in result.stdout.splitlines()]
print(users)

8. CSV & TSV Parsing

What it means: CSV (comma-separated) and TSV (tab-separated) are the most common export formats from databases, scan tools, and spreadsheets. Python's csv module and the miller (mlr) CLI tool handle them cleanly.


8.1 awk for CSV/TSV

# TSV — extract column 2
[CLI] awk -F'\t' '{print $2}' data.tsv

# CSV — extract column 3 (careful: won't handle quoted commas)
[CLI] awk -F',' '{print $3}' data.csv

# Skip header, print columns 1 and 4
[CLI] awk -F',' 'NR>1 {print $1","$4}' data.csv

# Filter rows where column 3 > 100
[CLI] awk -F',' 'NR>1 && $3 > 100 {print}' data.csv

8.2 Python csv Module

import csv

# Read CSV
with open('data.csv', newline='') as f:
    reader = csv.DictReader(f)     # header row becomes keys
    for row in reader:
        print(row['ip'], row['port'])

# Filter rows
with open('hosts.csv', newline='') as f:
    reader = csv.DictReader(f)
    open_hosts = [r for r in reader if r['state'] == 'open']

# Write CSV
with open('output.csv', 'w', newline='') as f:
    fieldnames = ['ip', 'port', 'state']
    writer = csv.DictWriter(f, fieldnames=fieldnames)
    writer.writeheader()
    writer.writerows(open_hosts)

# Read plain CSV (no header)
with open('data.csv') as f:
    reader = csv.reader(f)
    for row in reader:
        print(row, row)

8.3 miller (mlr) — CSV Power Tool

# Install
sudo apt install miller    # or: brew install miller

# Pretty-print CSV as table
[CLI] mlr --csv pretty data.csv

# Filter rows
[CLI] mlr --csv filter '$state == "open"' data.csv

# Select specific columns
[CLI] mlr --csv cut -f ip,port,state data.csv

# Sort by column
[CLI] mlr --csv sort-within-records -f port data.csv
[CLI] mlr --csv sort -f port data.csv

# Count by field value
[CLI] mlr --csv stats1 -a count -f ip -g state data.csv

# Convert CSV to JSON
[CLI] mlr --csv --ojson cat data.csv

# Add a computed column
[CLI] mlr --csv put '$full = $ip . ":" . $port' data.csv

# Rename columns
[CLI] mlr --csv rename ip,host,port,service_port data.csv

9. YAML Parsing — yq

What it means: YAML is the config format for Kubernetes, Ansible, Docker Compose, GitHub Actions, and many modern tools. yq is to YAML what jq is to JSON — a CLI query/transform tool with near-identical syntax.


9.1 Installation

# Linux (binary)
wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O yq
chmod +x yq && sudo mv yq /usr/local/bin/

# Arch
sudo pacman -S go-yq

# pip (Python version)
pip install yq       # wraps jq syntax for YAML

9.2 Basic Queries

# Read a field
[CLI] yq '.name' config.yaml
[CLI] yq '.spec.containers.image' pod.yaml

# Read all elements of an array
[CLI] yq '.hosts[]' inventory.yaml

# Filter array
[CLI] yq '.hosts[] | select(.state == "open")' data.yaml

# Get array length
[CLI] yq '.items | length' data.yaml

# Output as JSON
[CLI] yq -o=json '.' config.yaml

# Output as CSV
[CLI] yq -o=csv '.hosts[] | [.ip, .port]' data.yaml

9.3 Modification

# Set a value
[CLI] yq '.replicas = 3' deployment.yaml

# Edit in place
[CLI] yq -i '.replicas = 3' deployment.yaml

# Add an item to an array
[CLI] yq '.hosts += ["192.168.1.50"]' config.yaml

# Delete a key
[CLI] yq 'del(.password)' config.yaml

# Merge two YAML files
[CLI] yq '. * load("override.yaml")' base.yaml

9.4 Python pyyaml

import yaml

# Load YAML file
with open('config.yaml') as f:
    data = yaml.safe_load(f)

print(data['database']['host'])
print(data['services']['name'])

# Load multi-document YAML
with open('multi.yaml') as f:
    docs = list(yaml.safe_load_all(f))

# Write YAML
with open('output.yaml', 'w') as f:
    yaml.dump(data, f, default_flow_style=False)

# From string
data = yaml.safe_load("name: admin\nport: 443")

10. HTML Parsing — pup & python

What it means: HTML parsing is essential for web recon — extracting links, forms, hidden fields, email addresses, and metadata from web pages. pup is a CSS-selector-based CLI tool; BeautifulSoup is the Python standard.


10.1 pup — CLI HTML Parser

# Install
go install github.com/ericchiang/pup@latest

# Pipe curl output into pup
[CLI] curl -s https://target.com | pup 'SELECTOR'

# Extract all links
[CLI] curl -s https://target.com | pup 'a attr{href}'

# Extract all form inputs
[CLI] curl -s https://target.com | pup 'input attr{name}'

# Get hidden field values
[CLI] curl -s https://target.com | pup 'input[type=hidden] attr{value}'

# Extract page title
[CLI] curl -s https://target.com | pup 'title text{}'

# Get all script src URLs
[CLI] curl -s https://target.com | pup 'script attr{src}'

# All emails in mailto links
[CLI] curl -s https://target.com | pup 'a[href^=mailto] attr{href}' | sed 's/mailto://'

# Output as JSON
[CLI] curl -s https://target.com | pup 'a json{}'

10.2 Python BeautifulSoup

from bs4 import BeautifulSoup
import requests

r = requests.get('https://target.com')
soup = BeautifulSoup(r.text, 'html.parser')

# Find by tag
title = soup.find('title').text

# Find all links
links = [a['href'] for a in soup.find_all('a', href=True)]

# Find by CSS class
items = soup.find_all('div', class_='result')

# Find by ID
form = soup.find(id='login-form')

# Find by attribute
hidden = soup.find_all('input', {'type': 'hidden'})
for field in hidden:
    print(field.get('name'), field.get('value'))

# Get all text content
text = soup.get_text(separator='\n', strip=True)

# Extract meta tags (for recon)
metas = soup.find_all('meta')
for m in metas:
    print(m.get('name'), m.get('content'))

# Find all script tags (JS recon)
scripts = [s.get('src') for s in soup.find_all('script') if s.get('src')]

# CSS selector (like pup)
emails = soup.select('a[href^="mailto:"]')

11. Log Parsing — Real-World Patterns

What it means: Logs are unstructured or semi-structured text that record system events. In red teaming and forensics, you parse logs to find credentials, recon attacker activity, identify pivots, and extract IOCs.


11.1 Auth Log Parsing

# Failed SSH logins — IP addresses
[CLI] grep 'Failed password' /var/log/auth.log \
  | awk '{print $11}' | sort | uniq -c | sort -rn

# Successful SSH logins
[CLI] grep 'Accepted password\|Accepted publickey' /var/log/auth.log \
  | awk '{print $9, $11}'

# All unique IPs that attempted SSH
[CLI] grep -oP '(\d{1,3}\.){3}\d{1,3}' /var/log/auth.log | sort -u

# sudo usage
[CLI] grep 'sudo' /var/log/auth.log | grep 'COMMAND' | awk '{print $6, $NF}'

# New user creation
[CLI] grep 'useradd\|adduser' /var/log/auth.log

11.2 Web Server Log Parsing

# Apache / Nginx access.log format:
# IP - - [date] "METHOD /path HTTP/1.1" status bytes

# Top 10 IPs by request count
[CLI] awk '{print $1}' /var/log/nginx/access.log \
  | sort | uniq -c | sort -rn | head 10

# All 404 errors
[CLI] awk '$9 == 404 {print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn

# All POST requests (potential login bruteforce / upload)
[CLI] grep '"POST ' /var/log/nginx/access.log | awk '{print $1, $7, $9}'

# User-agents (detect scanners)
[CLI] awk -F'"' '{print $6}' /var/log/nginx/access.log | sort | uniq -c | sort -rn

# Requests to /admin, /login, /wp-admin
[CLI] grep -E '/(admin|login|wp-admin|phpmyadmin)' /var/log/nginx/access.log \
  | awk '{print $1, $7, $9}'

# Extract all unique paths
[CLI] awk '{print $7}' /var/log/nginx/access.log | cut -d'?' -f1 | sort -u

11.3 Windows Event Log Parsing (evtx → text)

# Convert .evtx to XML first with python-evtx or chainsaw
pip install python-evtx

# Parse evtx to XML
[CLI] python3 -m evtx /path/to/Security.evtx > security.xml

# Extract Event ID 4624 (logon) from XML
[CLI] xmllint --xpath '//Event[System/EventID=4624]//Data[@Name="IpAddress"]/text()' security.xml

# With chainsaw (fast EVTX hunting)
[CLI] ./chainsaw hunt /path/to/logs --rules rules/ --output results.json
[CLI] cat results.json | jq '.[] | select(.name | contains("Pass-the-Hash"))'

11.4 Structured Log Parsing (JSON Logs)

# Many modern apps (Nginx, cloud tools) log in JSON format

# Extract all errors
[CLI] cat app.json.log | jq 'select(.level == "error")'

# Get unique error messages
[CLI] cat app.json.log | jq -r 'select(.level == "error") | .message' | sort -u

# Filter by time range
[CLI] cat app.json.log | jq 'select(.timestamp > "2025-01-01T00:00:00")'

# Extract request path and status
[CLI] cat access.json.log | jq -r '[.path, (.status | tostring)] | @tsv'

12. Cheat Sheet

Format → Tool Decision Tree

What format is your data?
├── JSON → jq (CLI) | json (Python)
├── XML → xmllint --xpath (CLI) | lxml (Python)
├── YAML → yq (CLI) | pyyaml (Python)
├── HTML → pup (CLI) | BeautifulSoup (Python)
├── CSV → mlr / awk (CLI) | csv / pandas (Python)
├── Logs → grep + awk + sed (CLI) | re (Python)
└── Mixed/Unknown → Python (most flexible)

Need to FILTER? → jq select() | awk pattern | grep
Need to TRANSFORM? → jq map/del | awk print | sed s///
Need to AGGREGATE? → jq group_by | awk sum/count | mlr stats
Need to EXTRACT (regex)?→ grep -oP | sed -n | Python re.findall

Quick One-Liners

# JSON — extract all IPs
jq -r '.[] | .ip' data.json

# XML — extract all open ports from nmap
xmllint --xpath '//port[state/@state="open"]/@portid' nmap.xml | tr ' ' '\n' | grep -oP '\d+'

# CSV — get column 3, skip header
awk -F',' 'NR>1{print $3}' data.csv

# Log — top IPs from nginx log
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head 20

# HTML — extract all links
curl -s https://target.com | pup 'a attr{href}'

# YAML — read a nested field
yq '.spec.containers.image' pod.yaml

# Regex — extract all emails from file
grep -oP '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}' file.txt

# Remove ANSI colors from output
sed 's/\x1B\[[0-9;]*[mK]//g' output.txt

# Base64 decode all matches
grep -oP '[A-Za-z0-9+/]{20,}={0,2}' file.txt | while read b; do echo "$b" | base64 -d 2>/dev/null; done

Tool Limitations

Tool Limitation Workaround
jq No XML/HTML support Use xmllint or pup
xmllint No YAML support Use yq
awk CSV Fails on quoted commas Use mlr or Python csv
grep -P PCRE not always available (macOS) Use ggrep (brew) or Python
pup Can't execute JS Use Selenium/Playwright for dynamic pages
sed Not for binary data Use xxd + grep
proxychains TCP only Ligolo-ng for UDP

← Linux top ↑