Skip to content

Instantly share code, notes, and snippets.

@megamit
Last active March 21, 2026 09:22
Show Gist options
  • Select an option

  • Save megamit/fe78dfb58214b5b645f3b1a255a31498 to your computer and use it in GitHub Desktop.

Select an option

Save megamit/fe78dfb58214b5b645f3b1a255a31498 to your computer and use it in GitHub Desktop.
Script to check if a torrent contains a file that matches a pattern
#!/bin/bash
# Usage: check-pattern <torrent-file> <pattern>
# The belolw benparse and bp_parse functions are taken exactly from the TorrentParser
# script at https://mywiki.wooledge.org/TorrentParser, which is unlicensed.
# Full credit to the original author, who is not me. I have only copied the code and
# added a small wrapper around it to check for a pattern in the torrent file.
# TORRENT PARSER START
export LC_ALL=C
if [[ $# -ne 2 ]]; then
echo "Usage: $0 <torrent-file> <pattern>" >&2
echo "Check if any files in the torrent match the given pattern" >&2
echo "Returns 0 if pattern matches, non-zero if no matches" >&2
exit 1
fi
torrent_file="$1"
pattern="$2"
# Global associative array for torrent data
declare -A ben
# Filename ($1)
# Results go into global associative array ben
benparse() {
local data skip p max
[[ -r $1 ]] || { echo "cannot read file '$1'"; return 1; }
IFS= read -rd '' data < <(tr \\0 \\1 <"$1")
max=${#data}
# Begin parsing file at offset 0, namespace ""
bp_parse 0 ""
}
# Starting offset ($1), starting namespace ($2)
# Uses global AA ben, module variable data
# Return parsed data in module var p
# Return total char length of parsed data in module var skip
bp_parse() {
(($1 >= max)) && return
case "${data:$1:1}" in
d)
# Data dictionary, terminated by "e". Get pairs.
local i=$1 j=$(($1 + 1)) key value
while ((j < max)) && [[ ${data:j:1} != e ]]; do
bp_parse $j "$2."
key=$p
((j+=skip))
bp_parse $j "$2.$key"
value=$p
((j+=skip))
[[ $value ]] && ben["$2.$key"]=$value
done
p="" # We populate the AA ourselves, rather than passing data back
skip=$((j-i+1))
;;
i)
# Integer, terminated by "e"
local i=$1 j=$(($1 + 1))
while [[ ${data:j:1} != e ]]; do
((j++))
done
p=${data:i+1:j-i-1}
skip=$((j-i+1))
;;
l)
# List, concatenated elements, terminated by "e"
local i=$1 j=$(($1 + 1)) k=0 value
while [[ ${data:j:1} != e ]]; do
bp_parse $j "$2.$k"
[[ $p ]] && ben["$2.$k"]=$p
((k++, j+=skip))
done
p=""
skip=$((j-i+1))
;;
*)
# String, length-prefixed (integer, colon). Get the length first.
local n n_len
bp_getnum $1
n_len=${#n}
p=${data:$1+n_len+1:n}
skip=$((n_len+1+n))
;;
esac
}
# Find an integer in data, beginning at offset ($1)
# Return value in upstream variable n
bp_getnum() {
local i=$1 j=$1
while [[ ${data:j:1} = [[:digit:]-] ]]; do
((j++))
done
n=${data:i:j-i}
}
# TORRENT PARSER END
# Parse the torrent file
benparse "$torrent_file"
# Check if any file paths match the pattern
found_match=false
# Iterate through all keys in the associative array
for key in "${!ben[@]}"; do
# Look for file path keys (e.g., .info.files.0.path.0)
if [[ $key =~ \.info\.files\.[0-9]+\.path\.[0-9]+$ ]]; then
filename="${ben[$key]}"
# Check if filename matches the pattern (using bash pattern matching)
if [[ $filename == $pattern ]]; then
echo "Match found: $filename"
found_match=true
fi
fi
done
# Handle single-file torrents (where the filename is in .info.name)
if [[ -n "${ben['.info.name']}" ]]; then
filename="${ben['.info.name']}"
if [[ $filename == $pattern ]]; then
echo "Match found: $filename"
found_match=true
fi
fi
# Exit with appropriate code
if [[ $found_match == true ]]; then
exit 1 # Match found
else
exit 0 # No match found
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment