EDIT: agh, typo in the question: How to loop through...
I have a random string of characters (e.g. an api token), and I want to redact all but the last 4 characters so I can safely print it in my app log for security reasons
e.g.
original: "gBluvaDzAIHg97ATvDxqgjtO"
desired: "********************gjtO"
What's the most efficient way to write this?
I came up with the below but it feels inefficient, so was hoping to get some guidance on how to do it more succinctly.
Thanks!
token := "gBluvaDzAIHg97ATvDxqgjtO"
var token_array []string;
// Loop through token
for i, _ := range token {
if i >= len(token) - 4 {
// If it's one of the last 4 digits, use the original character
token_array = append(token_array, string(token[i]))
} else {
// If it's not, use an asterisk
token_array = append(token_array, "*")
}
}
fmt.Println("Token: %v", strings.Join(token_array[:], ""))
**评论:**
joncalhoun:
AllThatJellyNoToast:Strings are immutable, meaning you can't alter them in Go.
Instead you will need to create a new string, much like you are doing. That said, you can do this a little simpler with something like this: https://play.golang.org/p/ZRKxTyLGhk
joncalhoun:Thanks, that's perfect!
1 small follow-up. You had used
b[i] = '*'
which I casually changed to
b[i] = "*"
Which throws error
cannot use "*" (type string) as type byte in assignment
What's the real difference between single and double quotes here? Are both not strings?
ar1819:No. The
'*'
is a byte, whereas"*"
is a string composed of a byte slice of length 1 (plus some other data).In other words,
"*"
is conceptually similar to[]byte{'*'}
.The difference is subtle, and most of the time you probably don't really need to think about it, but it is important in this case. It is also different than many other languages that allow you to use
'
and"
very similarly.
AllThatJellyNoToast:Actually
'*'
is untyped rune or int32. The interesting thing is that it can be implicitly cast to the int16 or int8 type. But you should also remember that symbol can be a UTF-8 symbol - and if you try to represent it using byte type. bad things will happen. Case in the point: https://play.golang.org/p/YthQootOojBUT! Go slices can be converted from strings and vice versa - the Unicode symbol will just occupy the 1-2-4 bytes it'll need.
natefinch:Thanks a bunch. Yeah I'm coming over from ruby where
'
and"
are almost identical (except that the latter allows interpolation).I have to get into the mindset of thinking about what type each variable is, which is probably been the most difficult part of the transition
dchapes:The strings package is your friend.
https://play.golang.org/p/E4k4zT9ATKpackage main import ( "fmt" "strings" ) func main() { str := "hello123abc" fmt.Println(mask(str)) } func mask(s string) string { if len(s) <= 4 { return s } return strings.Repeat("*", len(s)-4) + s[len(s)-4:] }
[Of course, unless something like this is in a critical hot-path the best solution is likely the one that is easiest to read, understand, and maintain rather than what a benchmark says; so unless you're curious ignore this completely :)]
Using
strings.Repeat
was my first thought as well, but I was curious so I ran some benchmarks and compared a few solutions. Usingstrings.Repeat
like this is slower and uses more memory in all cases compared with using[]byte
loop with a finalstring
cast as given previously.Minor gains can be made with fixed
"*****…"
constant string sliced in as needed (e.g.return mask[:i] + s[i:]
) but only between ~32 bytes and the length of the constant; if the input token is known to always be of a fixed size this may be the fastest and easiest solution. After ~96 bytes a variation on the[]byte
loop that starts withbytes.Repeat([]byte{'*'}, len(s))
and then overwrites the last 4 bytes froms
becomes faster.
