chore: cleanup — untrack binaries, consolidate Go dirs, dedupe tools

- Untrack and delete compiled binaries (tarsum, gosoft.exe, rust uniq/uniq2);
  ignore build outputs (dist/, bin/, *.exe, *.test, .ruff_cache/)
- Merge tools/go/ and projects/go-tools/go/ into projects/go-tools/<name>/
- Fix goipgrep .gitignore: bare 'ipgrep' pattern was ignoring cmd/ipgrep/,
  so the main entrypoint was never tracked; now anchored to /ipgrep
- Archive duplicate implementations to archive/experimental/{rust,go}/
  (uniq, between, tarsum rewrites); canonical versions stay in tools/
- Update README tool catalog to match new layout

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
tobias
2026-06-10 13:42:45 +02:00
parent 401b3e1781
commit 1cbf8afb4a
39 changed files with 449 additions and 27 deletions
+5
View File
@@ -0,0 +1,5 @@
module gobincmp
go 1.24.4
require github.com/glaslos/ssdeep v0.4.0
+10
View File
@@ -0,0 +1,10 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/glaslos/ssdeep v0.4.0 h1:w9PtY1HpXbWLYgrL/rvAVkj2ZAMOtDxoGKcBHcUFCLs=
github.com/glaslos/ssdeep v0.4.0/go.mod h1:il4NniltMO8eBtU7dqoN+HVJ02gXxbpbUfkcyUvNtG0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+216
View File
@@ -0,0 +1,216 @@
// main.go
package main
import (
"bytes"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/glaslos/ssdeep"
)
const blockSize = 1024 * 1024
// main is the entry point of the program.
func main() {
// 1. Get and validate command-line arguments
if len(os.Args) != 3 {
usage := `
Binwally (Go Version): Binary and Directory tree comparison tool
using the Fuzzy Hashing concept (ssdeep)
Usage: go run . <dir1/file1> <dir2/file2>
`
fmt.Printf(usage)
os.Exit(1)
}
path1 := os.Args[1]
path2 := os.Args[2]
info1, err1 := os.Stat(path1)
if err1 != nil {
log.Fatalf("Invalid path: %s", path1)
}
info2, err2 := os.Stat(path2)
if err2 != nil {
log.Fatalf("Invalid path: %s", path2)
}
// 2. Route to appropriate comparison logic
if info1.IsDir() && info2.IsDir() {
// Both are directories
var diffs []int
fmt.Println("\nSCORE RESULT PATH")
compareTrees(path1, path2, &diffs)
if len(diffs) == 0 {
fmt.Println("No diffs found\n")
} else {
totalScore := 0
for _, score := range diffs {
totalScore += score
}
averageScore := totalScore / len(diffs)
fmt.Println("\nTotal files compared:", len(diffs))
fmt.Printf("Overall match score: %d%%\n\n", averageScore)
}
} else if !info1.IsDir() && !info2.IsDir() {
// Both are files
hash1, err := ssdeep.HashFromFile(path1) // CORRECTED
if err != nil {
log.Fatalf("Failed to hash file %s: %v", path1, err)
}
hash2, err := ssdeep.HashFromFile(path2) // CORRECTED
if err != nil {
log.Fatalf("Failed to hash file %s: %v", path2, err)
}
score, err := ssdeep.Distance(hash1, hash2) // CORRECTED
if err != nil {
log.Fatalf("Failed to compare hashes: %v", err)
}
fmt.Printf("Overall match score: %d%%\n\n", score)
} else {
log.Fatal("Error: Both arguments must be files or both must be directories.")
}
}
// compareTrees recursively compares two directory trees.
func compareTrees(dir1, dir2 string, diffs *[]int) {
// 1. Read contents of both directories
entries1, err := os.ReadDir(dir1)
if err != nil {
log.Printf("Error reading directory %s: %v", dir1, err)
return
}
entries2, err := os.ReadDir(dir2)
if err != nil {
log.Printf("Error reading directory %s: %v", dir2, err)
return
}
// Create maps for quick lookup by name
map1 := make(map[string]os.DirEntry)
for _, e := range entries1 {
map1[e.Name()] = e
}
map2 := make(map[string]os.DirEntry)
for _, e := range entries2 {
map2[e.Name()] = e
}
// 2. Find and report unique files/dirs
reportUniques(map1, map2, dir1, " <<< unique ", diffs) // In dir1 only
reportUniques(map2, map1, dir2, " >>> unique ", diffs) // In dir2 only
// 3. Process common items
for name, e1 := range map1 {
if e2, ok := map2[name]; ok {
// Name exists in both directories
path1 := filepath.Join(dir1, name)
path2 := filepath.Join(dir2, name)
isDir1 := e1.IsDir()
isDir2 := e2.IsDir()
if isDir1 && isDir2 {
// Both are directories, recurse
compareTrees(path1, path2, diffs)
} else if !isDir1 && !isDir2 {
// Both are files, compare them
compareFiles(path1, path2, diffs)
} else {
// Mismatched types (file vs dir) or symlinks
fmt.Printf(" - ignored %s (symlink or type mismatch)\n", path1)
*diffs = append(*diffs, 100) // Original script counts this as 100
}
}
}
}
// reportUniques finds items in map1 not in map2 and reports them.
func reportUniques(map1, map2 map[string]os.DirEntry, baseDir, prefix string, diffs *[]int) {
for name, entry := range map1 {
if _, exists := map2[name]; !exists {
fullPath := filepath.Join(baseDir, name)
if !entry.IsDir() {
// It's a unique file
fmt.Println(prefix, fullPath)
*diffs = append(*diffs, 0)
} else {
// It's a unique directory, walk it and report all files within
filepath.WalkDir(fullPath, func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
}
if !d.IsDir() {
fmt.Println(prefix, path)
*diffs = append(*diffs, 0)
}
return nil
})
}
}
}
}
// compareFiles compares two files, first byte-by-byte, then with ssdeep if different.
func compareFiles(path1, path2 string, diffs *[]int) {
f1, err := os.Open(path1)
if err != nil {
log.Printf("Error opening file %s: %v", path1, err)
return
}
defer f1.Close()
f2, err := os.Open(path2)
if err != nil {
log.Printf("Error opening file %s: %v", path2, err)
return
}
defer f2.Close()
// Compare files chunk by chunk
for {
b1 := make([]byte, blockSize)
_, err1 := f1.Read(b1)
b2 := make([]byte, blockSize)
_, err2 := f2.Read(b2)
if err1 == io.EOF && err2 == io.EOF {
// Files are identical
fmt.Printf("%5s matches %s\n", "100", getRelativePath(path1))
*diffs = append(*diffs, 100)
return
}
if err1 != nil && err1 != io.EOF || err2 != nil && err2 != io.EOF {
log.Printf("Error reading from files: %v, %v", err1, err2)
return
}
if !bytes.Equal(b1, b2) {
// Files differ, use ssdeep
hash1, _ := ssdeep.HashFromFile(path1) // CORRECTED
hash2, _ := ssdeep.HashFromFile(path2) // CORRECTED
score, _ := ssdeep.Distance(hash1, hash2) // CORRECTED
fmt.Printf("%5s differs %s\n", strconv.Itoa(score), getRelativePath(path1))
*diffs = append(*diffs, score)
return
}
}
}
// getRelativePath tries to mimic the Python script's path trimming.
func getRelativePath(path string) string {
parts := strings.Split(filepath.ToSlash(path), "/")
if len(parts) > 1 {
return strings.Join(parts[1:], "/")
}
return path
}
-140
View File
@@ -1,140 +0,0 @@
package main
import (
"bufio"
"flag"
"fmt"
"log"
"os"
"regexp"
"strconv"
"strings"
)
func printUsage() {
fmt.Fprintf(os.Stderr, "Usage: %s [options] <range specification> [file]\n", os.Args[0])
fmt.Fprintln(os.Stderr, "Options:")
flag.PrintDefaults()
fmt.Fprintln(os.Stderr, "\nRange specification examples:")
fmt.Fprintln(os.Stderr, "10,20 Reads lines 10 to 20 from the input")
fmt.Fprintln(os.Stderr, "15:+5 Reads 5 lines starting from line 15")
fmt.Fprintln(os.Stderr, "3 Reads from line 3 to the end of the file")
fmt.Fprintln(os.Stderr, "+2 Reads the first 2 lines")
fmt.Fprintln(os.Stderr, "Please ensure you input valid range specifications and file paths.")
os.Exit(1)
}
func parseArgs(args []string) (int, int, string, error) {
var filename string
var start, length, end int
found := false
// Concatenate all arguments to a single string for easier parsing
joinedArgs := strings.Join(args, " ")
// Regular expression to match ranges and numbers
rangeRegex := regexp.MustCompile(`((\d+)?([ :,;-]))(\+)?(\d+)`)
range2Regex := regexp.MustCompile(`((\d+)([ :,;-]))`)
range3Regex := regexp.MustCompile(`(\+)?(\d+)`)
matches := rangeRegex.FindStringSubmatch(joinedArgs)
if matches != nil {
//check if start was defined
if matches[2] != "" {
start, _ = strconv.Atoi(matches[2]) // Convert start line to integer
} else {
start = 1
}
if matches[4] == "+" { // Check if it's a relative length
length, _ = strconv.Atoi(matches[5]) // Convert length to integer
end = start + length - 1
} else {
end, _ = strconv.Atoi(matches[5]) // Convert end line to integer
}
// Remove the matched part from the arguments
joinedArgs = strings.Replace(joinedArgs, matches[0], "", 1)
found = true
} else {
matches = range2Regex.FindStringSubmatch(joinedArgs)
if matches != nil {
start, _ = strconv.Atoi(matches[2]) // Convert start line to integer
end = -1
// Remove the matched part from the arguments
joinedArgs = strings.Replace(joinedArgs, matches[0], "", 1)
found = true
} else {
matches = range3Regex.FindStringSubmatch(joinedArgs)
if matches != nil {
if matches[1] == "+" { // Check if it's a relative length
length, _ := strconv.Atoi(matches[2]) // Convert length to integer
start = 1
end = start + length - 1
} else { // Otherwise convert it to an absolute length
start, _ = strconv.Atoi(matches[2]) // Convert start line to integer
end = -1
// Remove the matched part from the arguments
}
joinedArgs = strings.Replace(joinedArgs, matches[0], "", 1)
found = true
} else {
found = false
printUsage()
}
}
}
// Clean up and identify the filename, if present
joinedArgs = strings.TrimSpace(joinedArgs)
if joinedArgs != "" && !found {
// If we didn't find numbers, interpret the remaining as filename
filename = joinedArgs
} else if joinedArgs != "" {
// Otherwise, interpret any non-empty remaining part as filename
filename = joinedArgs
}
if !found {
return 0, 0, "", fmt.Errorf("no valid range or line number found")
}
return start, end, filename, nil
}
func main() {
flag.Parse()
args := flag.Args()
if len(args) < 1 {
printUsage()
os.Exit(1)
}
startIndex, endIndex, filename, err := parseArgs(args)
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing arguments: %v\n", err)
os.Exit(1)
}
// Detemine the input source (file or stdin)
inputSource := os.Stdin
if filename != "" {
inputSource, err = os.Open(filename)
if err != nil {
log.Fatalf("Failed to open file: %v\n", err)
}
defer inputSource.Close()
}
scanner := bufio.NewScanner(inputSource)
//print all lines from line buner start to linenumber end unless linenumber end is -1
for i := 1; scanner.Scan(); i++ {
if startIndex <= i && (i <= endIndex || endIndex == -1) {
fmt.Println(scanner.Text())
}
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
}
-75
View File
@@ -1,75 +0,0 @@
package main
import (
"bufio"
"flag"
"fmt"
"hash/fnv"
"log"
"os"
)
func hashLine(s string) uint32 {
hasher := fnv.New32a()
hasher.Write([]byte(s))
return hasher.Sum32()
}
func main() {
// Define command line flags
reverse := flag.Bool("d", false, "Print only lines that appear more than once.")
help := flag.Bool("h", false, "Display help and usage information.")
flag.Usage = func() {
fmt.Fprintf(flag.CommandLine.Output(), "Usage of %s:\n", os.Args[0])
fmt.Println("This program reads from a file or standard input, deduplicates lines, and outputs the results.")
fmt.Println("Options:")
flag.PrintDefaults()
fmt.Println("Example usage:")
fmt.Println("\t", os.Args[0], "[options] [filename]")
fmt.Println("\t", os.Args[0], "-d filename # Only print duplicates")
fmt.Println("\t", "cat /some/text/file |", os.Args[0], "# Read from standard input")
}
flag.Parse()
// Check for help flag
if *help {
flag.Usage()
os.Exit(0)
}
// Detemine the input source (file or stdin)
inputSource := os.Stdin
var err error
if flag.NArg() > 0 {
inputSource, err = os.Open(flag.Args()[0])
if err != nil {
log.Fatalf("Failed to open file: %v\n", err)
}
defer inputSource.Close()
}
seenLines := make(map[uint32]int)
scanner := bufio.NewScanner(inputSource)
//Readin lines
for scanner.Scan() {
line := scanner.Text()
hash := hashLine(line)
seenLines[hash]++
if *reverse {
// Print only lines that appear more than once
if seenLines[hash] > 1 {
fmt.Println(line)
}
} else {
// Normal mode, print only unique lines
if seenLines[hash] == 1 {
fmt.Println(line)
}
}
}
//Check for errors during scanning
if err := scanner.Err(); err != nil {
log.Fatalf("Failed to read input: %v\n", err)
}
}
@@ -1,4 +1,4 @@
dist/
ipgrep
/ipgrep
*.test
@@ -0,0 +1,418 @@
package main
import (
"context"
"encoding/csv"
"encoding/json"
"errors"
"flag"
"fmt"
"os"
"os/signal"
"runtime"
"sort"
"strings"
"syscall"
"time"
"git.ktf.ninja/tabledevil/gists/projects/go-tools/go/goipgrep/internal/cache"
"git.ktf.ninja/tabledevil/gists/projects/go-tools/go/goipgrep/internal/extract"
"git.ktf.ninja/tabledevil/gists/projects/go-tools/go/goipgrep/internal/ipinfo"
"git.ktf.ninja/tabledevil/gists/projects/go-tools/go/goipgrep/internal/normalize"
"git.ktf.ninja/tabledevil/gists/projects/go-tools/go/goipgrep/internal/ping"
"git.ktf.ninja/tabledevil/gists/projects/go-tools/go/goipgrep/internal/resolve"
)
var (
version = "dev"
commit = "unknown"
date = "unknown"
)
type stringSlice []string
func (s *stringSlice) String() string { return strings.Join(*s, ",") }
func (s *stringSlice) Set(v string) error {
*s = append(*s, v)
return nil
}
func main() {
var (
// legacy short flags (kept for compatibility)
shortUniq bool
shortSort bool
shortMAC bool
shortPing bool
shortResolve bool
shortLookup bool
shortFile string
// long flags
longUniq bool
longSort bool
longMAC bool
longPing bool
longResolve bool
longLookup bool
longFiles stringSlice
format string
jobs int
timeout time.Duration
pingMode string
ipv6 bool
cachePath string
noCache bool
clearCache bool
cacheTTL time.Duration
cacheMax int
ipinfoToken string
showVersion bool
)
flag.BoolVar(&shortUniq, "u", false, "only show uniq IPs/MACs (implies -s)")
flag.BoolVar(&shortSort, "s", false, "sort output")
flag.BoolVar(&shortMAC, "m", false, "grep MAC addresses instead of IP addresses")
flag.BoolVar(&shortPing, "p", false, "only show entries considered reachable (see --ping-mode)")
flag.BoolVar(&shortResolve, "r", false, "resolve: IP reverse DNS; MAC best-effort via neighbor/ARP table (Linux only)")
flag.BoolVar(&shortLookup, "l", false, "lookup ip info using ipinfo.io (IP only)")
flag.StringVar(&shortFile, "f", "", "input file (legacy; prefer --file)")
flag.BoolVar(&longUniq, "uniq", false, "only show uniq IPs/MACs (implies --sort)")
flag.BoolVar(&longSort, "sort", false, "sort output")
flag.BoolVar(&longMAC, "mac", false, "grep MAC addresses instead of IP addresses")
flag.BoolVar(&longPing, "pingable", false, "only show entries considered reachable (see --ping-mode)")
flag.BoolVar(&longResolve, "resolve", false, "resolve: IP reverse DNS; MAC best-effort via neighbor/ARP table (Linux only)")
flag.BoolVar(&longLookup, "lookup", false, "lookup ip info using ipinfo.io (IP only)")
flag.Var(&longFiles, "file", "input file (repeatable)")
flag.StringVar(&format, "format", "text", "output format: text|csv|json")
flag.IntVar(&jobs, "jobs", runtime.GOMAXPROCS(0), "max concurrent workers for network operations")
flag.DurationVar(&timeout, "timeout", 2*time.Second, "per-operation timeout for -p/-r/-l")
flag.StringVar(&pingMode, "ping-mode", "auto", "reachability mode for -p: auto|icmp|tcp")
flag.BoolVar(&ipv6, "ipv6", false, "also match IPv6 addresses (default: IPv4 only)")
flag.StringVar(&cachePath, "cache-path", "", "cache path for ipinfo lookups (default: OS cache dir)")
flag.BoolVar(&noCache, "no-cache", false, "disable reading/writing the ipinfo cache")
flag.BoolVar(&clearCache, "clear-cache", false, "clear the ipinfo cache then exit")
flag.DurationVar(&cacheTTL, "cache-ttl", 30*24*time.Hour, "ipinfo cache TTL (0 disables expiry)")
flag.IntVar(&cacheMax, "cache-max-entries", 50000, "max cached IPs for ipinfo lookups (0 disables pruning)")
flag.StringVar(&ipinfoToken, "ipinfo-token", "", "ipinfo.io token (also read from IPINFO_TOKEN)")
flag.BoolVar(&showVersion, "version", false, "print version and exit")
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: %s [flags] [file...]\n\n", os.Args[0])
flag.PrintDefaults()
}
flag.Parse()
if showVersion {
fmt.Printf("ipgrep %s (%s) %s\n", version, commit, date)
return
}
uniqFlag := shortUniq || longUniq
sortFlag := shortSort || longSort
macFlag := shortMAC || longMAC
pingable := shortPing || longPing
resolveFlag := shortResolve || longResolve
lookupFlag := shortLookup || longLookup
// keep old behavior: pingable/lookup implies uniq+sort
if pingable || lookupFlag {
sortFlag = true
uniqFlag = true
}
if uniqFlag {
sortFlag = true
}
if lookupFlag && macFlag {
fatalf("lookup mode (-l/--lookup) only works for IP addresses, not MAC addresses")
}
format = strings.ToLower(strings.TrimSpace(format))
switch format {
case "text", "csv", "json":
default:
fatalf("invalid --format %q (want text|csv|json)", format)
}
pingMode = strings.ToLower(strings.TrimSpace(pingMode))
switch pingMode {
case "auto", "icmp", "tcp":
default:
fatalf("invalid --ping-mode %q (want auto|icmp|tcp)", pingMode)
}
if jobs < 1 {
fatalf("--jobs must be >= 1")
}
if timeout <= 0 {
fatalf("--timeout must be > 0")
}
var files []string
if shortFile != "" {
files = append(files, shortFile)
}
files = append(files, longFiles...)
files = append(files, flag.Args()...)
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()
// cache management for lookup mode
if lookupFlag && ipinfoToken == "" {
ipinfoToken = os.Getenv("IPINFO_TOKEN")
}
// Resolve cache default path.
if cachePath == "" {
cachePath = cache.DefaultPath()
}
if clearCache {
if err := cache.Clear(cachePath); err != nil && !errors.Is(err, os.ErrNotExist) {
fatalf("clear cache: %v", err)
}
return
}
mode := extract.ModeIP
if macFlag {
mode = extract.ModeMAC
}
// Extraction stage (stream input, collect matches as needed).
var matches []string
seen := map[string]struct{}{}
emit := func(s string) {
if uniqFlag {
if _, ok := seen[s]; ok {
return
}
seen[s] = struct{}{}
}
matches = append(matches, s)
}
exOpts := extract.Options{Mode: mode, IPv6: ipv6}
if len(files) == 0 {
if err := extract.Grep(ctx, os.Stdin, exOpts, func(raw string) {
if mode == extract.ModeMAC {
if norm, ok := normalize.NormalizeMAC(raw); ok {
emit(norm)
}
return
}
if norm, ok := normalize.NormalizeIP(raw, ipv6); ok {
emit(norm)
}
}); err != nil {
fatalf("read stdin: %v", err)
}
} else {
for _, f := range files {
r, err := os.Open(f)
if err != nil {
fmt.Fprintf(os.Stderr, "ipgrep: open %s: %v\n", f, err)
continue
}
err = extract.Grep(ctx, r, exOpts, func(raw string) {
if mode == extract.ModeMAC {
if norm, ok := normalize.NormalizeMAC(raw); ok {
emit(norm)
}
return
}
if norm, ok := normalize.NormalizeIP(raw, ipv6); ok {
emit(norm)
}
})
_ = r.Close()
if err != nil {
fmt.Fprintf(os.Stderr, "ipgrep: read %s: %v\n", f, err)
}
}
}
if len(matches) == 0 {
return
}
if sortFlag {
sort.Strings(matches)
}
// -p reachability filter
if pingable {
popts := ping.Options{
Mode: pingMode,
Timeout: timeout,
Jobs: jobs,
}
if macFlag {
var err error
matches, err = ping.FilterMACs(ctx, matches, popts)
if err != nil {
fatalf("%v", err)
}
} else {
var err error
matches, err = ping.FilterIPs(ctx, matches, popts)
if err != nil {
fatalf("%v", err)
}
}
if sortFlag {
sort.Strings(matches)
}
}
// -l ipinfo lookup
if lookupFlag {
if format == "csv" {
w := csv.NewWriter(os.Stdout)
must(w.Write([]string{"ip", "country", "region", "city", "org", "hostname"}))
client := ipinfo.Client{
Token: ipinfoToken,
Timeout: timeout,
}
var store *cache.Store
if !noCache {
s, err := cache.Load(cachePath, cacheTTL, cacheMax)
if err != nil {
fmt.Fprintf(os.Stderr, "ipgrep: cache load: %v\n", err)
} else {
store = s
}
}
results := ipinfo.LookupAll(ctx, matches, client, store, jobs)
sort.Slice(results, func(i, j int) bool { return results[i].IP < results[j].IP })
for _, info := range results {
must(w.Write([]string{info.IP, info.Country, info.Region, info.City, info.Org, info.Hostname}))
}
w.Flush()
if err := w.Error(); err != nil {
fatalf("csv: %v", err)
}
if store != nil && store.Changed() {
if err := store.Save(); err != nil {
fmt.Fprintf(os.Stderr, "ipgrep: cache save: %v\n", err)
}
}
return
}
client := ipinfo.Client{
Token: ipinfoToken,
Timeout: timeout,
}
var store *cache.Store
if !noCache {
s, err := cache.Load(cachePath, cacheTTL, cacheMax)
if err != nil {
fmt.Fprintf(os.Stderr, "ipgrep: cache load: %v\n", err)
} else {
store = s
}
}
results := ipinfo.LookupAll(ctx, matches, client, store, jobs)
sort.Slice(results, func(i, j int) bool { return results[i].IP < results[j].IP })
if format == "json" {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
must(enc.Encode(results))
} else {
for _, info := range results {
fmt.Printf("%s\t%s\t%s\t%s\t%s\t%s\n", info.IP, info.Country, info.Region, info.City, info.Org, info.Hostname)
}
}
if store != nil && store.Changed() {
if err := store.Save(); err != nil {
fmt.Fprintf(os.Stderr, "ipgrep: cache save: %v\n", err)
}
}
return
}
// -r resolution
if resolveFlag {
if macFlag {
res, err := resolve.ResolveMACs(matches)
if err != nil {
fatalf("%v", err)
}
if format == "json" {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
must(enc.Encode(res))
return
}
for _, row := range res {
// one line per mapping
for _, ip := range row.IPs {
fmt.Printf("%s\t%s\n", row.MAC, ip)
}
if len(row.IPs) == 0 {
fmt.Println(row.MAC)
}
}
return
}
res := resolve.ReverseLookupAll(ctx, matches, timeout, jobs)
if format == "json" {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
must(enc.Encode(res))
return
}
for _, row := range res {
if row.Hostname != "" {
fmt.Printf("%s\t%s\n", row.IP, row.Hostname)
} else {
fmt.Println(row.IP)
}
}
return
}
// plain output
if format == "json" {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
must(enc.Encode(matches))
return
}
if format == "csv" {
// csv doesn't make much sense for plain extraction, but keep it consistent.
w := csv.NewWriter(os.Stdout)
must(w.Write([]string{"value"}))
for _, m := range matches {
must(w.Write([]string{m}))
}
w.Flush()
if err := w.Error(); err != nil {
fatalf("csv: %v", err)
}
return
}
for _, m := range matches {
fmt.Println(m)
}
}
func must(err error) {
if err != nil {
fatalf("%v", err)
}
}
func fatalf(format string, args ...any) {
fmt.Fprintf(os.Stderr, "ipgrep: "+format+"\n", args...)
os.Exit(1)
}
+5
View File
@@ -0,0 +1,5 @@
module pname
go 1.24.4
require github.com/erikdubbelboer/gspt v0.0.0-20210805194459-ce36a5128377
+2
View File
@@ -0,0 +1,2 @@
github.com/erikdubbelboer/gspt v0.0.0-20210805194459-ce36a5128377 h1:gT+RM6gdTIAzMT7HUvmT5mL8SyG8Wx7iS3+L0V34Km4=
github.com/erikdubbelboer/gspt v0.0.0-20210805194459-ce36a5128377/go.mod h1:v6o7m/E9bfvm79dE1iFiF+3T7zLBnrjYjkWMa1J+Hv0=
+29
View File
@@ -0,0 +1,29 @@
package main
import (
"fmt"
"os"
"os/exec"
"github.com/erikdubbelboer/gspt" // Import the new library
)
func main() {
// Change the process title using the library
gspt.SetProcTitle("notmyname")
fmt.Println("Process name changed. Press any key to exit...")
// Disable input buffering
exec.Command("stty", "-F", "/dev/tty", "cbreak", "min", "1").Run()
// Do not display entered characters on the screen
exec.Command("stty", "-F", "/dev/tty", "-echo").Run()
var b []byte = make([]byte, 1)
os.Stdin.Read(b)
// Restore the original terminal settings
exec.Command("stty", "-F", "/dev/tty", "sane").Run()
fmt.Println("\nKey pressed. Exiting.")
}
-55
View File
@@ -1,55 +0,0 @@
use std::env;
use std::fs::File;
use std::io::{self, BufRead, BufReader};
fn main() -> io::Result<()> {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
eprintln!("Usage: {} start-end [count] [file...]", args[0]);
std::process::exit(1);
}
let range = &args[1];
let (start, mut end) = if let Some(dash_pos) = range.find('-') {
(range[..dash_pos].parse().unwrap(), range[dash_pos + 1..].parse().unwrap())
} else {
(range.parse().unwrap(), 0)
};
let mut count = 1;
let mut files_start = 2;
if end == 0 {
if args.len() > 2 {
if let Ok(c) = args[2].parse::<usize>() {
count = c;
files_start = 3;
}
}
end = start + count - 1;
}
if args.len() > files_start {
for filename in &args[files_start..] {
let file = File::open(filename)?;
let reader = BufReader::new(file);
process_lines(reader, start, end)?;
}
} else {
// No files provided, read from stdin
let stdin = io::stdin();
let reader = stdin.lock();
process_lines(reader, start, end)?;
}
Ok(())
}
fn process_lines<R: BufRead>(reader: R, start: usize, end: usize) -> io::Result<()> {
reader.lines()
.enumerate()
.filter_map(|(i, line)| if i + 1 >= start && i + 1 <= end { line.ok() } else { None })
.for_each(|line| println!("{}", line));
Ok(())
}
-41
View File
@@ -1,41 +0,0 @@
use std::env;
use std::fs::File;
use std::io::{self, BufRead, BufReader};
use std::collections::HashSet;
use std::hash::{Hash, Hasher};
use std::collections::hash_map::DefaultHasher;
fn main() -> io::Result<()> {
let args: Vec<String> = env::args().collect();
if args.len() > 1 {
for filename in &args[1..] {
let file = File::open(filename)?;
let reader = BufReader::new(file);
remove_duplicates(reader)?;
}
} else {
// No files provided, read from stdin
let stdin = io::stdin();
let reader = stdin.lock();
remove_duplicates(reader)?;
}
Ok(())
}
fn remove_duplicates<R: BufRead>(reader: R) -> io::Result<()> {
let mut seen_hashes = HashSet::new();
for line in reader.lines() {
let line = line?;
let mut hasher = DefaultHasher::new();
line.hash(&mut hasher);
let hash = hasher.finish();
if seen_hashes.insert(hash) {
println!("{}", line);
}
}
Ok(())
}
-36
View File
@@ -1,36 +0,0 @@
use std::collections::HashSet;
use std::hash::{Hash, Hasher};
use std::collections::hash_map::DefaultHasher;
struct HashOnlySet {
set: HashSet<u64>,
}
impl HashOnlySet {
fn new() -> HashOnlySet {
HashOnlySet { set: HashSet::new() }
}
fn insert<T: Hash>(&mut self, item: &T) -> bool {
let hash = Self::hash_item(item);
self.set.insert(hash)
}
fn contains<T: Hash>(&self, item: &T) -> bool {
let hash = Self::hash_item(item);
self.set.contains(&hash)
}
fn hash_item<T: Hash>(item: &T) -> u64 {
let mut hasher = DefaultHasher::new();
item.hash(&mut hasher);
hasher.finish()
}
}
fn main() {
let mut set = HashOnlySet::new();
set.insert(&"Hello, world!");
println!("Contains 'Hello, world!': {}", set.contains(&"Hello, world!"));
println!("Contains 'Goodbye, world!': {}", set.contains(&"Goodbye, world!"));
}