filepath.Abs ignores '~'

241 views
Skip to first unread message

Jacob Kopczynski

unread,
Jul 7, 2020, 7:44:53 PM7/7/20
to golang-nuts
I tried to state a path relative to the home directory, since in the context I am writing for the path from ~ to the Git repository will be consistent, though the meaning of ~ may change based on user. A snippet:

const specPath = "~/trunk/infra/metadata/config.cfg"
absPath, _ := filepath.Abs(specPath)                                     
if err := readJSONPb(absPath, &s); err != nil {                          
    fmt.Printf("specPath is %s, or as absolute %s\n", specPath, absPath)
    return nil, errors.Annotate(err, "extracting tests from spec").Err()
}
However, this produces this output:
specPath is ~/trunk/infra/metadata/config.cfg, or as absolute /home/jkop/trunk/infra/~/trunk/infra/metadata/config.cfg
extracting tests from spec: read JSON pb: open /home/jkop/trunk/infra/~/trunk/infra/metadata/config.cfg: no such file or directory

This is a) surprising, since I would expect a platform-sensitive absolute path function to deal with platform-global shortcuts like ~, and b) completely undocumented. The docs say that "Abs returns an absolute representation of path. If the path is not absolute it will be joined with the current working directory to turn it into an absolute path." It doesn't say "If the path is not rooted", but "not absolute". This is not an absolute representation but it does specify an absolute path unambiguously.

What's the process for submitting a change to a core library like this one? Ideally, to the code, but I'd settle for making the documentation clearer.

Robert Engels

unread,
Jul 7, 2020, 7:49:24 PM7/7/20
to Jacob Kopczynski, golang-nuts
The ~ is a feature of the OS shell. 

On Jul 7, 2020, at 6:44 PM, 'Jacob Kopczynski' via golang-nuts <golan...@googlegroups.com> wrote:

I tried to state a path relative to the home directory, since in the context I am writing for the path from ~ to the Git repository will be consistent, though the meaning of ~ may change based on user. A snippet:
--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/ee99cbb9-583f-487f-9e57-e0072d7a859an%40googlegroups.com.

Ian Lance Taylor

unread,
Jul 7, 2020, 7:51:33 PM7/7/20
to Jacob Kopczynski, golang-nuts
On Unix systems a leading ~ in a path is expanded by the shell.
Programs do not normally treat a leading ~ as being special in any
way. For example, the C glob function ignores ~ unless you explicitly
pass the GLOB_TILDE flag (and GLOB_TILDE is not even defined by POSIX,
it's a common extension). So I think the Go functions are acting in
the usual Unix way, and I don't yet see a reason to change them.
Whether the docs should be changed depends on what the suggested
change is.

Ian

burak serdar

unread,
Jul 7, 2020, 7:52:44 PM7/7/20
to Jacob Kopczynski, golang-nuts
On Tue, Jul 7, 2020 at 5:44 PM 'Jacob Kopczynski' via golang-nuts
<golan...@googlegroups.com> wrote:
>
> I tried to state a path relative to the home directory, since in the context I am writing for the path from ~ to the Git repository will be consistent, though the meaning of ~ may change based on user. A snippet:
>
> const specPath = "~/trunk/infra/metadata/config.cfg"
> absPath, _ := filepath.Abs(specPath)
> if err := readJSONPb(absPath, &s); err != nil {
> fmt.Printf("specPath is %s, or as absolute %s\n", specPath, absPath)
> return nil, errors.Annotate(err, "extracting tests from spec").Err()
> }
> However, this produces this output:
> specPath is ~/trunk/infra/metadata/config.cfg, or as absolute /home/jkop/trunk/infra/~/trunk/infra/metadata/config.cfg
> extracting tests from spec: read JSON pb: open /home/jkop/trunk/infra/~/trunk/infra/metadata/config.cfg: no such file or directory
>
> This is a) surprising, since I would expect a platform-sensitive absolute path function to deal with platform-global shortcuts like ~, and b) completely

~ is a symbol the shell expands to home directory. Outside a shell, ~
means ~. If you mkdir ~, you'll get a directory named ~ in the current
directory.




undocumented. The docs say that "Abs returns an absolute
representation of path. If the path is not absolute it will be joined
with the current working directory to turn it into an absolute path."
It doesn't say "If the path is not rooted", but "not absolute". This
is not an absolute representation but it does specify an absolute path
unambiguously.
>
> What's the process for submitting a change to a core library like this one? Ideally, to the code, but I'd settle for making the documentation clearer.
>

Ian Lance Taylor

unread,
Jul 7, 2020, 8:00:47 PM7/7/20
to Jacob Kopczynski, golang-nuts
On Tue, Jul 7, 2020 at 4:44 PM 'Jacob Kopczynski' via golang-nuts
<golan...@googlegroups.com> wrote:
>
> What's the process for submitting a change to a core library like this one? Ideally, to the code, but I'd settle for making the documentation clearer.

Sorry, I didn't answer this question. For changes that don't affect
the API, see https://golang.org/doc/contribute.html. For changes to
the API, see https://golang.org/s/proposal.

Ian

Bakul Shah

unread,
Jul 7, 2020, 8:14:52 PM7/7/20
to Jacob Kopczynski, golang-nuts
You can do this:

home, _ := os.UserHomeDir()
absPath := home + "/trunk/infra/metadata/config.cfg"

bugpowder

unread,
Jul 7, 2020, 8:41:11 PM7/7/20
to Jacob Kopczynski, golang-nuts
On Wed, Jul 8, 2020 at 2:44 AM 'Jacob Kopczynski' via golang-nuts <golan...@googlegroups.com> wrote:
I tried to state a path relative to the home directory, since in the context I am writing for the path from ~ to the Git repository will be consistent, though the meaning of ~ may change based on user. A snippet:

const specPath = "~/trunk/infra/metadata/config.cfg"
absPath, _ := filepath.Abs(specPath)                                     
if err := readJSONPb(absPath, &s); err != nil {                          
    fmt.Printf("specPath is %s, or as absolute %s\n", specPath, absPath)
    return nil, errors.Annotate(err, "extracting tests from spec").Err()
}
However, this produces this output:
specPath is ~/trunk/infra/metadata/config.cfg, or as absolute /home/jkop/trunk/infra/~/trunk/infra/metadata/config.cfg
extracting tests from spec: read JSON pb: open /home/jkop/trunk/infra/~/trunk/infra/metadata/config.cfg: no such file or directory

This is a) surprising, since I would expect a platform-sensitive absolute path function to deal with platform-global shortcuts like ~, and

~ is not a "platform global shortcut". It's an expansion a shell (bash, zsh, etc) does for convenience.

open() and similar C-level (or Go level etc) function are not opening files through a shell.
 
b) completely undocumented. The docs say that "Abs returns an absolute representation of path. If the path is not absolute it will be joined with the current working directory to turn it into an absolute path." It doesn't say "If the path is not rooted", but "not absolute". This is not an absolute representation but it does specify an absolute path unambiguously.

Not really unambiguously. Consider a function like:

listDirContents("~")

Is "~" a file/dir called "~" in the current working path (which is totally legal to create) or "/home/<current user>"? 

Note that this is how almost all standard libraries behave, except some that have expansion out of convenience, in higher level languages (not something like C or Go).

Even Python, which has all shorts of conveniences, doesn't expand tildes:

>>> open("~")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 2] No such file or directory: '~'

And the equilavent abspath treats the "~" as a "~" file in the local dir (/Users/username/ was not expanded from ~ -- I just happened to run the command from there):

>>> import os
>>> os.path.abspath("~")
'/Users/username/~'
>>>
Reply all
Reply to author
Forward
0 new messages