Search code examples
zshrc

GOBIN root setting with var multi GOPATH in .zshrc config


export GOPATH=~/mygo:~/go
export GOBIN=$GOPATH/bin

I expected the $GOBIN equals ~/mygo/bin:~/go/bin but it is ~/mygo:~/go/bin instead. how could I set them a better way? thx


Solution

  • Solution

    export GOPATH=~/mygo:~/go
    export GOBIN=${(j<:>)${${(s<:>)GOPATH}/%//bin}}
    

    Explanation

    Although whatever program uses GOPATH might interprete it as an array, for zsh it is just a scalar ("string").

    In order to append a string (/bin) to every element the string "$GOPATH" first needs to be split into an array. In zsh this can be done with the parameter expansion flag s:string:. This splits a scalar on string and returns an array. Instead of : any other character or matching pairs of (), [], {} or <> can be used. In this case it has to be done because string is to be :.

    GOPATH_ARRAY=(${(s<:>)GOPATH)
    

    Now the ${name/pattern/repl} parameter expansion can be used to append /bin to each element, or rather to replace the end of each element with /bin. In order to match the end of a string, the pattern needs to begin with a %. As any string should be matched, the pattern is otherwise empty:

    GOBIN_ARRAY=(${GOPATH_ARRAY/%//bin})
    

    Finally, the array needs to be converted back into a colon-separated string. This can be done with the j:string: parameter expansion flag. It is the counterpart to s:string::

    GOBIN=${(j<:>)GOBIN_ARRAY}
    

    Fortunately, zsh allows Nested Substitution, so this can be done all in one statement, without intermediary variables:

    GOBIN=${(j<:>)${${(s<:>)GOPATH}/%//bin}}
    

    Alternative Solution

    It is also possible to do this without parameter expansion flags or nested substitution by simply appending /bin to the end of the string and additionally replace every : with /bin::

    export GOBIN=${GOPATH//://bin:}/bin
    

    The ${name//pattern/repl} expansion replaces every occurence of pattern with repl instead of just the first like with ${name/pattern/repl}.

    This would also work in bash.

    Personally, I feel that it is a bit "hackish", mainly because you need to write /bin twice and also because it completely sidesteps the underlying semantics. But that is only personal preference and the results will be the same.


    Note:

    When defining GOPATH like you did in the question

    export GOPATH=~/mygo:~/go
    

    zsh will expand each occurence of ~/ with your home directory. So the value of GOPATH will be /home/kevin/mygo:/home/kevin/go - assuming the user name is "kevin". Accordingly, GOBIN will also have the expanded paths, /home/kevin/mygo/bin:/home/kevin/go/bin, instead of ~/mygo/bin:~/go/bin

    This could be prevented by quoting the value - GOPATH="~/mygo:~/go" - but I would recommend against it. ~ as synonym for the home directory is not a feature of the operating system and while shells usually support it, other programs (those needing GOPATH or GOBIN) might not do so.