Skip to content

Instantly share code, notes, and snippets.

@josharian
Created July 28, 2025 00:35
Show Gist options
  • Save josharian/ac8672063887b10cbbd8741b1e70b300 to your computer and use it in GitHub Desktop.
Save josharian/ac8672063887b10cbbd8741b1e70b300 to your computer and use it in GitHub Desktop.
compare_shells_nul.go
package main
import (
"bufio"
"flag"
"fmt"
"os"
"os/exec"
"sort"
"strings"
)
type ShellResult struct {
shell string
stdout string
stderr string
err error
}
func main() {
var commandsFile = flag.String("c", "commands.txt", "File containing commands to test")
flag.Parse()
if len(flag.Args()) < 2 {
fmt.Fprintf(os.Stderr, "Usage: %s [-c commands.txt] shell1 shell2 [shell3...]\n", os.Args[0])
fmt.Fprintf(os.Stderr, "Example: %s -c commands.txt bash zsh gosh\n", os.Args[0])
os.Exit(1)
}
shells := flag.Args()
commands, err := readCommands(*commandsFile)
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading commands file: %v\n", err)
os.Exit(1)
}
fmt.Printf("Testing %d commands across %d shells: %s\n\n", len(commands), len(shells), strings.Join(shells, ", "))
differences := 0
totalCommands := 0
for _, command := range commands {
if strings.TrimSpace(command) == "" || strings.HasPrefix(strings.TrimSpace(command), "#") {
continue // Skip empty lines and comments
}
totalCommands++
results := make(map[string]ShellResult)
// Run command in each shell
for _, shell := range shells {
result := runCommand(shell, command)
results[shell] = result
}
// Check for differences
if hasDifferences(results) {
differences++
printDifferences(command, results, shells)
}
}
fmt.Printf("\n=== SUMMARY ===\n")
fmt.Printf("Commands tested: %d\n", totalCommands)
fmt.Printf("Commands with differences: %d\n", differences)
fmt.Printf("Commands with identical behavior: %d\n", totalCommands-differences)
if differences == 0 {
fmt.Printf("🎉 All shells behave identically!\n")
} else {
fmt.Printf("💡 %d commands need attention\n", differences)
}
}
func readCommands(filename string) ([]string, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
var commands []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
commands = append(commands, scanner.Text())
}
return commands, scanner.Err()
}
func runCommand(shell, command string) ShellResult {
var cmd *exec.Cmd
// Handle different shell invocation patterns
switch shell {
case "gosh":
// Assume gosh is in current directory or PATH
cmd = exec.Command("./gosh", "-c", command)
if _, err := os.Stat("./gosh"); os.IsNotExist(err) {
cmd = exec.Command("gosh", "-c", command)
}
default:
cmd = exec.Command(shell, "-c", command)
}
stdout, err := cmd.Output()
var stderr []byte
if exitErr, ok := err.(*exec.ExitError); ok {
stderr = exitErr.Stderr
}
return ShellResult{
shell: shell,
stdout: string(stdout),
stderr: string(stderr),
err: err,
}
}
func hasDifferences(results map[string]ShellResult) bool {
var outputs []string
for _, result := range results {
// Combine stdout and stderr for comparison, plus exit status
exitCode := 0
if result.err != nil {
if exitErr, ok := result.err.(*exec.ExitError); ok {
exitCode = exitErr.ExitCode()
} else {
exitCode = -1 // Command failed to run
}
}
combined := fmt.Sprintf("stdout:%s|stderr:%s|exit:%d", result.stdout, result.stderr, exitCode)
outputs = append(outputs, combined)
}
// Check if all outputs are identical
if len(outputs) == 0 {
return false
}
first := outputs[0]
for _, output := range outputs[1:] {
if output != first {
return true
}
}
return false
}
func printDifferences(command string, results map[string]ShellResult, shells []string) {
fmt.Printf("=== COMMAND: %s ===\n", command)
// Sort shells for consistent output
sortedShells := make([]string, len(shells))
copy(sortedShells, shells)
sort.Strings(sortedShells)
for _, shell := range sortedShells {
result := results[shell]
fmt.Printf("--- %s ---\n", shell)
if result.err != nil {
if exitErr, ok := result.err.(*exec.ExitError); ok {
fmt.Printf("Exit code: %d\n", exitErr.ExitCode())
} else {
fmt.Printf("Failed to run: %v\n", result.err)
}
} else {
fmt.Printf("Exit code: 0\n")
}
if result.stdout != "" {
fmt.Printf("Stdout: %q\n", result.stdout)
if containsNullBytes(result.stdout) {
fmt.Printf("Stdout (hex): % x\n", []byte(result.stdout))
}
} else {
fmt.Printf("Stdout: (empty)\n")
}
if result.stderr != "" {
fmt.Printf("Stderr: %q\n", result.stderr)
}
fmt.Println()
}
fmt.Println()
}
func containsNullBytes(s string) bool {
return strings.Contains(s, "\x00")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment