RegExp vs String Manipulation in Go language
Source: Dev.to
What is wrong?
In i, the tool I wrote this function to replace the placeholder x in a command with the package name supplied by the user.
func executeCommand(template string, pkgName string) {
if template == "" {
fmt.Println("Command not defined for this package manager.")
return
}
re := regexp.MustCompile(`\bx\b`)
cmdStr := re.ReplaceAllStringFunc(template, func(s string) string {
return pkgName
})
if verbose {
fmt.Printf("Executing: %s\n", cmdStr)
}
parts := strings.Fields(cmdStr)
if len(parts) == 0 {
return
}
head := parts[0]
args := parts[1:]
cmd := exec.Command(head, args...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
if verbose {
fmt.Printf("Error executing command: %v\n", err)
}
os.Exit(1)
}
}
Using regular expressions for such a simple substitution is overkill.
Getting rid of regular expressions
The task is to replace the placeholder x with the actual package name. Most commands end with that x, which makes strings.TrimSuffix() a good fit:
sudo apt install x
sudo apt remove x
sudo apt install --only-upgrade x
apt search x
apt show x
brew install x
brew uninstall x
brew upgrade x
brew search x
brew info x
sudo port install x
sudo port uninstall x
sudo port upgrade x
port search x
port info x
sudo flatpak install x
sudo flatpak uninstall x
sudo flatpak update x
flatpak search x
flatpak info x
sudo snap install --classic x
sudo snap remove x
sudo snap refresh x
snap find x
snap info x
sudo dnf install -y x
sudo dnf remove -y x
sudo dnf upgrade -y x
dnf search x
dnf info x
sudo pacman -S --noconfirm x
sudo pacman -Rs --noconfirm x
sudo pacman -Syu --noconfirm x
pacman -Ss x
pacman -Qi x
A special case is when the command ends with x as part of the package manager’s name (e.g., guix). We don’t want to replace that x. Likewise, the Nix package manager uses a dot before the package name (nix-env -iA nixpkgs.x). Therefore we need to handle both " x" and ".x" suffixes.
The revised function uses plain string manipulation:
func executeCommand(template string, pkgName string) {
if template == "" {
fmt.Println("Command not defined for this package manager.")
return
}
cmdStr := template
// If the template ends with ".x" or " x", replace the trailing "x" with pkgName
if strings.HasSuffix(template, ".x") || strings.HasSuffix(template, " x") {
cmdStr = strings.TrimSuffix(template, "x") + pkgName
}
if verbose {
fmt.Printf("Executing: %s\n", cmdStr)
}
parts := strings.Fields(cmdStr)
if len(parts) == 0 {
return
}
head := parts[0]
args := parts[1:]
cmd := exec.Command(head, args...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
if verbose {
fmt.Printf("Error executing command: %v\n", err)
}
os.Exit(1)
}
}
I was curious about the performance impact.
Benchmarking RegExp vs. string replacement
I wrote a benchmark (see the source on GitHub: performance_test.go):
$ go test -bench=. -run=NONE -benchmem
goos: linux
goarch: amd64
pkg: github.com/abanoubha/i
cpu: Intel(R) Core(TM) i5-1035G1 CPU @ 1.00GHz
BenchmarkRegexpReplacement-8 541910 2207 ns/op 1609 B/op 21 allocs/op
BenchmarkRegexpPrecompiledReplacement-8 1986163 609.2 ns/op 88 B/op 4 allocs/op
BenchmarkStringReplacement-8 23621672 50.48 ns/op 24 B/op 1 allocs/op
PASS
ok github.com/abanoubha/i 3.608s
Results
- String Manipulation: ~50 ns/op
- Regexp (pre‑compiled): ~609 ns/op (≈ 12× slower)
- Regexp (compiled on‑the‑fly): ~2207 ns/op (≈ 44× slower)
Takeaways
- Prefer Go’s
stringsfunctions whenever possible. - Avoid regular expressions for simple substitutions; they add unnecessary complexity and overhead.
- Using
stringsleads to clearer, more maintainable code with better performance.
If you found this useful, feel free to share it. Follow me for more content on YouTube, Twitter (X), LinkedIn, and GitHub.