9

This:

label := string([]byte{97, 98, 99, 0, 0, 0, 0})
fmt.Printf("%s\n", label)

does this (^@ is the null-byte):

go run test.go 
abc^@^@^@
0

5 Answers 5

17

There's this function hidden inside Go's syscall package that finds the first null byte ([]byte{0}) and returns the length. I'm assuming it's called clen for C-Length.

Sorry I'm a year late on this answer, but I think it's a lot simpler than the other two (no unnecessary imports, etc.)

func clen(n []byte) int {
    for i := 0; i < len(n); i++ {
        if n[i] == 0 {
            return i
        }
    }
    return len(n)
}

So,

label := []byte{97, 98, 99, 0, 0, 0, 0}
s := label[:clen(label)]
fmt.Println(string(s))

What that ^ says is to set s to the slice of bytes in label from the beginning to the index of clen(label).

The result would be abc with a length of 3.

Sign up to request clarification or add additional context in comments.

4 Comments

This is actually the cleanest, simplest and more efficient solution.
And, unlike the string.Index answer, this won't panic if the provided []byte does not contain a zero byte.
This is the only correct solution so far. This should be the accepted answer.
I'm getting "clen" undefined and "imported and not used: syscall"
12

Note that the first answer will only work with strings that have only a run of zeroes after the null terminator; however, a proper C-style null-terminated string ends at the first \0 even if it's followed by garbage. For example, []byte{97,98,99,0,99,99,0} should be parsed as abc, not abc^@cc.

To properly parse this, use string.Index, as follows, to find the first \0 and use it to slice the original byte-slice:

package main

import (
    "fmt"
    "strings"
)

func main() {
    label := []byte{97,98,99,0,99,99,0}
    nullIndex := strings.Index(string(label), "\x00")
    if (nullIndex < 0) {
        fmt.Println("Buffer did not hold a null-terminated string")
        os.Exit(1)
    }
    fmt.Println(string(label[:nullIndex]))
}

EDIT: Was printing the shortened version as a []byte instead of as a string. Thanks to @serbaut for the catch.

EDIT 2: Was not handling the error case of a buffer without a null terminator. Thanks to @snap for the catch.

4 Comments

Shouldnt that be string(label[:bytes.IndexByte(label, 0)])?
@serbaut, you're very correct; should have run that in play, and I would have noticed the []byte formatting. Edited
This panics if the input does not contain nul terminator. Not nice.
@snap Good catch! Editing my answer.
1

You can use the sys package:

package main
import "golang.org/x/sys/windows"

func main() {
   b := []byte{97, 98, 99, 0, 0, 0, 0}
   s := windows.ByteSliceToString(b)
   println(s == "abc")
}

Or you can just implement it yourself:

package main
import "bytes"

func byteSliceToString(s []byte) string {
   n := bytes.IndexByte(s, 0)
   if n >= 0 {
      s = s[:n]
   }
   return string(s)
}

func main() {
   b := []byte{97, 98, 99, 0, 0, 0, 0}
   s := byteSliceToString(b)
   println(s == "abc")
}

Comments

1

1. strings .TrimSpace .TrimRight

//trim tail '\0', but can't handle bytes like "abc\x00def\x00".

can't edit @orelli answer, so wrote here:

package main

import (
    "fmt"
    "strings"
)

func main() {
    label := string([]byte{97, 98, 99, 0, 0, 0, 0})

    s1 := strings.TrimSpace(label)
    fmt.Println(len(s1), s1)

    s2 := strings.TrimRight(label, "\x00")
    fmt.Println(len(s2), s2)
  }

output:

7 abc????
3 abc

// ? is '\0' which can't display here.


So
.TrimSpace can't trim '\0', but
.TrimRight with "\x00" can.



2. bytes.IndexByte

search for first '\0', maybe not support utf-8

package main

import (
    "bytes"
    "fmt"
    "strings"
)

func main() {
    b_arr := []byte{97, 98, 99, 0, 100, 0, 0}
    label := string(b_arr)

    s1 := strings.TrimSpace(label)
    fmt.Println(len(s1), s1)   //7 abc?d??

    s2 := strings.TrimRight(label, "\x00")
    fmt.Println(len(s2), s2)   //5 abc?d

    n := bytes.IndexByte([]byte(label), 0)
    fmt.Println(n, label[:n])  //3 abc

    s_arr := b_arr[:bytes.IndexByte(b_arr, 0)]
    fmt.Println(len(s_arr), string(s_arr)) //3 abc
}

equivalent

n1 := bytes.IndexByte(b_arr, 0)
n2 := bytes.Index(b_arr, []byte{0})

n3, c := 0, byte(0)
for n3, c = range b_arr {
    if c == 0 {
        break
    }
}

Comments

0

You can use bytes.SplitN and have it return the first subslice:

import (
    "bytes"
)

func bytesToStr(in []byte) string {
    str := bytes.SplitN(in, []byte{0}, 2)[0]
    return string(str)
}

In go 1.18+, you can also use bytes.Cut:

func bytesToStr(in []byte) string {
    str, _, _ := bytes.Cut(in, []byte{0})
    return string(str)
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.