Created
July 28, 2025 00:35
-
-
Save josharian/ac8672063887b10cbbd8741b1e70b300 to your computer and use it in GitHub Desktop.
compare_shells_nul.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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