Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I convert a null-terminated string in a byte buffer to a string in Go?

Tags:

c

string

go

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^@^@^@
like image 515
knarf Avatar asked Sep 10 '12 21:09

knarf


5 Answers

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.

like image 118
Eric Lagergren Avatar answered Oct 06 '22 05:10

Eric Lagergren


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.

like image 38
azernik Avatar answered Oct 06 '22 03:10

azernik


use the strings package.

package main

import (
    "fmt"
    "strings"
)

func main() {
    label := string([]byte{97, 98, 99, 0, 0, 0, 0})
    fmt.Println(strings.TrimSpace(label))
}
like image 30
jorelli Avatar answered Sep 19 '22 15:09

jorelli


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")
}
  • https://pkg.go.dev/golang.org/x/sys/unix#ByteSliceToString
  • https://pkg.go.dev/golang.org/x/sys/windows#ByteSliceToString
like image 36
Zombo Avatar answered Oct 06 '22 04:10

Zombo


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
    }
}
like image 22
yurenchen Avatar answered Oct 06 '22 03:10

yurenchen