Skip to content

Commit

Permalink
Additional README for shellescape
Browse files Browse the repository at this point in the history
Signed-off-by: Kimmo Lehto <[email protected]>
  • Loading branch information
kke committed Mar 10, 2024
1 parent 1c7c60f commit 540912d
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 15 deletions.
57 changes: 57 additions & 0 deletions sh/shellescape/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# shellescape

A drop-in replacement for [alessio/shellescape](https://github.com/alessio/shellescape) / ["gopkg.in/alessio/shellescape.v1"]("gopkg.in/alessio/shellescape.v1").

It's a bit faster and allocates a little bit less. It's quite unlikely that anyone will notice any difference in a real-world application, the is here just to reduce dependencies of the `rig` package.

To use, replace `alessio/shellescape` with `github.com/k0sproject/rig/v2/sh/shellescape` in your imports.

## Benchmarks

Just out of curiosity.

### Quote

```text
BenchmarkQuote/#00-12 382379658 3.037 ns/op 0 B/op 0 allocs/op
BenchmarkQuote/"double_quoted"-12 14906526 79.10 ns/op 24 B/op 1 allocs/op
BenchmarkQuote/with_spaces-12 16851456 71.15 ns/op 16 B/op 1 allocs/op
BenchmarkQuote/'single_quoted'-12 8453859 154.4 ns/op 72 B/op 2 allocs/op
BenchmarkQuote/;-12 25321892 45.73 ns/op 3 B/op 1 allocs/op
BenchmarkQuote/;${}-12 22446418 54.00 ns/op 8 B/op 1 allocs/op
BenchmarkQuote/foo.example.com-12 39565838 29.85 ns/op 0 B/op 0 allocs/op
BenchmarkQuoteAlessio/#00-12 678336361 1.773 ns/op 0 B/op 0 allocs/op
BenchmarkQuoteAlessio/"double_quoted"-12 10867801 129.4 ns/op 24 B/op 1 allocs/op
BenchmarkQuoteAlessio/with_spaces-12 6283456 187.8 ns/op 16 B/op 1 allocs/op
BenchmarkQuoteAlessio/'single_quoted'-12 6507007 180.3 ns/op 56 B/op 2 allocs/op
BenchmarkQuoteAlessio/;-12 11453983 163.8 ns/op 3 B/op 1 allocs/op
BenchmarkQuoteAlessio/;${}-12 10179727 123.2 ns/op 8 B/op 1 allocs/op
BenchmarkQuoteAlessio/foo.example.com-12 3416793 357.8 ns/op 0 B/op 0 allocs/op
```

### QuoteCommand

```text
BenchmarkQuoteCommand/Basic_Command-12 7548391 159.4 ns/op 88 B/op 2 allocs/op
BenchmarkQuoteCommandAlessio/Basic_Command-12 2256212 572.1 ns/op 96 B/op 3 allocs/op
```

### StripUnsafe

Looks like there isn't much difference between the two.

```text
BenchmarkStripUnsafe/Hello,_World!-12 33246037 34.29 ns/op 0 B/op 0 allocs/op
BenchmarkStripUnsafe/\x00\x01\x02Test\x03\x04\x05-12 16221404 71.40 ns/op 16 B/op 1 allocs/op
BenchmarkStripUnsafe/SpecialChars\x1f\x7f-12 10196348 113.1 ns/op 24 B/op 1 allocs/op
BenchmarkStripUnsafe/中文测试-12 14912823 79.82 ns/op 0 B/op 0 allocs/op
BenchmarkStripUnsafe/#00-12 673622737 1.777 ns/op 0 B/op 0 allocs/op
BenchmarkStripUnsafeAlessio/Hello,_World!-12 24444872 48.44 ns/op 0 B/op 0 allocs/op
BenchmarkStripUnsafeAlessio/\x00\x01\x02Test\x03\x04\x05-12 17340309 68.57 ns/op 16 B/op 1 allocs/op
BenchmarkStripUnsafeAlessio/SpecialChars\x1f\x7f-12 13657556 80.99 ns/op 24 B/op 1 allocs/op
BenchmarkStripUnsafeAlessio/中文测试-12 13895878 85.13 ns/op 0 B/op 0 allocs/op
BenchmarkStripUnsafeAlessio/#00-12 473620983 2.509 ns/op 0 B/op 0 allocs/op
```
39 changes: 24 additions & 15 deletions sh/shellescape/shellescape.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@
//
// It is a drop-in replacement for gopkg.in/alessio/shellescape.v1.
//
// There are no regular expressions and it avoids allocations by using a strings.Builder.
//
// Additionally an Unquote function is provider.
// Additionally an Unquote function is provided.
package shellescape

import (
"strings"
"unicode"
"unicode/utf8"
)

// classify returns whether the string is empty, contains single quotes, or contains special characters.
Expand Down Expand Up @@ -43,14 +42,21 @@ func wrapTo(str string, builder *strings.Builder) {

// wrap in single quotes and escape single quotes and backslashes.
func escapeTo(str string, builder *strings.Builder) {
builder.Grow(len(str) + 3) // there will be at least 1 extra char and 2 quotes
builder.Grow(len(str) + 3) // there will be at least 1 extra char and 2 quotes - otherwise we wouldn't be here
builder.WriteByte('\'')
for _, c := range str {
for _, c := range str { //nolint:varnamelen
if c == '\'' {
builder.Grow(5) // quoting single quotes requires 4 extra chars
builder.WriteString(`'"'"'`)
continue
}
builder.WriteRune(c)
// According to strings.Map source code, this is faster than
// always using WriteRune.
if c < utf8.RuneSelf {
builder.WriteByte(byte(c))
} else {
builder.WriteRune(c)
}
}
builder.WriteByte('\'')
}
Expand Down Expand Up @@ -126,18 +132,21 @@ func QuoteCommand(args []string) string {
return Join(args...)
}

func isPrint(r rune) rune {
if unicode.IsPrint(r) {
return r
}

return -1
}

// StripUnsafe removes non-printable runes from a string.
func StripUnsafe(s string) string {
builder, ok := builderPool.Get().(*strings.Builder)
if !ok {
builder = &strings.Builder{}
}
defer builderPool.Put(builder)
defer builder.Reset()
for _, r := range s {
if unicode.IsPrint(r) {
builder.WriteRune(r)
if isPrint(r) == -1 {
// Avoid allocations by only stripping when the string contains non-printable runes.
return strings.Map(isPrint, s)
}
}
return builder.String()
return s
}

0 comments on commit 540912d

Please sign in to comment.