98

For example, I have a string, consists of "sample.zip". How do I remove the ".zip" extension using strings package or other else?

Donald Duck
  • 8,409
  • 22
  • 75
  • 99
Coder
  • 1,923
  • 4
  • 18
  • 18

7 Answers7

273

Try:

basename := "hello.blah"
name := strings.TrimSuffix(basename, filepath.Ext(basename))

TrimSuffix basically tells it to strip off the trailing string which is the extension with a dot.

strings#TrimSuffix

Zombo
  • 1
  • 62
  • 391
  • 407
Keith Cascio
  • 2,868
  • 2
  • 11
  • 5
  • 17
    Note that `filepath.Ext("test.tar.gz")` returns `.gz` which may or may not be what you want. – Charles L. Oct 04 '16 at 18:30
  • 1
    If you want the filename in case of multiple extensions (e.g. `tar.gz`) use `strings.Split` with dot separator https://play.golang.com/p/nyhC6-YJ7PE – Anton Zhukov Dec 13 '19 at 13:53
  • `Ext` is non reliable method, it also includes parameter. given `hello.blah?foo=bar` , it would return `.blah?foo=bar` – TomSawyer Aug 08 '20 at 16:31
  • 3
    @TomSawyer You should always use the `net/url` package to parse your URL before doing anything with the path. You likely also want to use `path.Ext` rather than `filepath.Ext`, as it's a path received over HTTP rather than one designed for use with the filesystem. – squirl Nov 04 '20 at 23:25
  • in term of getting ext from string , it simply doesn't work. and this is not the good answer. i have to write my own method to solve it. using above code gives you error – TomSawyer Nov 05 '20 at 07:15
  • 1
    The answer by [Paul Ruane](https://stackoverflow.com/a/13027975/5728991) avoids an unnecessary string comparison in strings.TrimSuffix. – Charlie Tumahai May 22 '21 at 05:26
99

Edit: Go has moved on. Please see Keith's answer.

Use path/filepath.Ext to get the extension. You can then use the length of the extension to retrieve the substring minus the extension.

var filename = "hello.blah"
var extension = filepath.Ext(filename)
var name = filename[0:len(filename)-len(extension)]

Alternatively you could use strings.LastIndex to find the last period (.) but this may be a little more fragile in that there will be edge cases (e.g. no extension) that filepath.Ext handles that you may need to code for explicitly, or if Go were to be run on a theoretical O/S that uses a extension delimiter other than the period.

Community
  • 1
  • 1
Paul Ruane
  • 37,459
  • 12
  • 63
  • 82
  • strings.TrimSuffix, underneath, does the same array math/indices :) – rogerdpack May 29 '14 at 06:58
  • 2
    @rogerdpack, yes people should use Keith's answer. `TrimSuffix` didn't exist in Go when I wrote this answer (it was [added in Go 1.1 in February 2013](https://code.google.com/p/go/source/diff?spec=svnc7353c98694b135ba941cea93b01e5c0e6a4dd55&old=c0f7c53dcfce43f4921cce7bb2ef7800c752bf4c&r=c7353c98694b135ba941cea93b01e5c0e6a4dd55&format=unidiff&path=%2Fsrc%2Fpkg%2Fstrings%2Fstrings.go)). – Paul Ruane Oct 15 '14 at 10:06
  • `filepath.Ext(filename)` did not work for me, I needed to use `path.Ext(filename)` instead. Thanks tho. – Derk Jan Speelman Apr 02 '19 at 13:04
  • 2
    `strings.TrimSuffix` would make an unnecessary/redundant comparison compared to pure string slicing. Not a big deal in terms of performance but there is a tiny difference and both solutions are easy to understand. – pasztorpisti Mar 12 '20 at 14:21
3

This is just one line more performant. Here it is:

filename := strings.Split(file.Filename, ".")[0]
Bitfinicon
  • 1,045
  • 1
  • 8
  • 22
2

This way works too:

var filename = "hello.blah"
var extension = filepath.Ext(filename)
var name = TrimRight(filename, extension)

but maybe Paul Ruane's method is more efficient?

Allan Ruin
  • 5,229
  • 7
  • 37
  • 42
  • 7
    I think TrimRight here removes a "cutset" (i.e. character set) from the end, which may not be what you want here: http://golang.org/pkg/strings/#TrimRight (see also TrimSuffix) – rogerdpack May 29 '14 at 06:55
2

I am using go1.14.1, filepath.Ext did not work for me, path.Ext works fine for me

var fileName = "hello.go"
fileExtension := path.Ext(fileName)
n := strings.LastIndex(fileName, fileExtension)
fmt.Println(fileName[:n])

Playground: https://play.golang.org/p/md3wAq_obNc

Documnetation: https://golang.org/pkg/path/#Ext

Caffeines
  • 169
  • 2
  • 5
2

Here is example that does not require path or path/filepath:

func BaseName(s string) string {
   n := strings.LastIndexByte(s, '.')
   if n == -1 { return s }
   return s[:n]
}

and it seems to be faster than TrimSuffix as well:

PS C:\> go test -bench .
goos: windows
goarch: amd64
BenchmarkTrimFile-12            166413693                7.25 ns/op
BenchmarkTrimPath-12            182020058                6.56 ns/op
BenchmarkLast-12                367962712                3.28 ns/op

https://golang.org/pkg/strings#LastIndexByte

Zombo
  • 1
  • 62
  • 391
  • 407
0

Summary, including the case of multiple extension names abc.tar.gz and performance comparison.

temp_test.go

package _test
import ("fmt";"path/filepath";"strings";"testing";)
func TestGetBasenameWithoutExt(t *testing.T) {
    p1 := "abc.txt"
    p2 := "abc.tar.gz" // filepath.Ext(p2) return `.gz`
    for idx, d := range []struct {
        actual   interface{}
        expected interface{}
    }{
        {fmt.Sprint(p1[:len(p1)-len(filepath.Ext(p1))]), "abc"},
        {fmt.Sprint(strings.TrimSuffix(p1, filepath.Ext(p1))), "abc"},
        {fmt.Sprint(strings.TrimSuffix(p2, filepath.Ext(p2))), "abc.tar"},
        {fmt.Sprint(p2[:len(p2)-len(filepath.Ext(p2))]), "abc.tar"},
        {fmt.Sprint(p2[:len(p2)-len(".tar.gz")]), "abc"},
    } {
        if d.actual != d.expected {
            t.Fatalf("[Error] case: %d", idx)
        }
    }
}

func BenchmarkGetPureBasenameBySlice(b *testing.B) {
    filename := "abc.txt"
    for i := 0; i < b.N; i++ {
        _ = filename[:len(filename)-len(filepath.Ext(filename))]
    }
}

func BenchmarkGetPureBasenameByTrimSuffix(b *testing.B) {
    filename := "abc.txt"
    for i := 0; i < b.N; i++ {
        _ = strings.TrimSuffix(filename, filepath.Ext(filename))
    }
}

run cmd: go test temp_test.go -v -bench="^BenchmarkGetPureBasename" -run=TestGetBasenameWithoutExt

output

=== RUN   TestGetBasenameWithoutExt
--- PASS: TestGetBasenameWithoutExt (0.00s)
goos: windows
goarch: amd64
cpu: Intel(R) Core(TM) i7-8565U CPU @ 1.80GHz
BenchmarkGetPureBasenameBySlice
BenchmarkGetPureBasenameBySlice-8               356602328                3.125 ns/op
BenchmarkGetPureBasenameByTrimSuffix
BenchmarkGetPureBasenameByTrimSuffix-8          224211643                5.359 ns/op
PASS
Carson
  • 6,105
  • 2
  • 37
  • 45