【问题标题】:Expand tilde to home directory将波浪号展开到主目录
【发布时间】:2013-07-10 16:34:01
【问题描述】:

我有一个程序接受将在其中创建文件的目标文件夹。我的程序应该能够处理绝对路径和相对路径。我的问题是我不知道如何将~ 扩展到主目录。

我扩展目的地的功能如下所示。如果给定的路径是绝对路径,则它什么也不做,否则它将相对路径与当前工作目录连接。

import "path"
import "os"

// var destination *String is the user input

func expandPath() {
        if path.IsAbs(*destination) {
                return
        }
        cwd, err := os.Getwd()
        checkError(err)
        *destination = path.Join(cwd, *destination)
}

由于path.Join 不会扩展~,因此如果用户将~/Downloads 之类的内容作为目的地传递,则它不起作用。

我应该如何跨平台解决这个问题?

【问题讨论】:

    标签: path go


    【解决方案1】:

    通常情况下,~ 在您的程序看到它之前由 shell 扩展。
    调整您的程序如何以与 shell 扩展机制兼容的方式从命令行获取其参数。

    其中一个可能的问题是像这样使用exec.Command

    cmd := exec.Command("some-binary", someArg) // say 'someArg' is "~/foo"
    

    不会被扩展。例如,您可以改用:

    cmd := exec.Command("sh", "-c", fmt.Sprintf("'some-binary %q'", someArg))
    

    这将从 shell 中获得标准的 ~ 扩展。

    编辑:修复了 'sh -c' 示例。

    【讨论】:

    • 谢谢,我不知道 shell 会这样做。
    • @jnml,我想说格式字符串中的第一个单引号放错了位置,而应该在 %q 占位符之前读取 fmt.Sprintf("some-binary '%q'", someArg)
    • @kostix:我不这么认为。上例中从 Sprintf 返回的值应该是'some-binary "/home/login/foo"'。没测试过,可能外面的单引号其实应该去掉。不过,它们在命令行上是正确的。
    • @jnml,是的,你是对的:%q 本身会产生一个单引号字符串(我错过了)。但我认为我最初的想法仍然成立:对fmt.Sprintf 的调用产生的任何字符串都将是一个参数,因此无需进一步引用它。
    • 如果您将路径作为 CLI 参数获取,这可能是正确的,但如果您从其他任何地方获取路径字符串,则情况并非如此。
    【解决方案2】:

    Go 提供了 os/user 包,它允许你获取当前用户,以及任何用户的主目录:

    usr, _ := user.Current()
    dir := usr.HomeDir
    

    然后,使用path/filepath 将两个字符串组合成一个有效路径:

    if path == "~" {
        // In case of "~", which won't be caught by the "else if"
        path = dir
    } else if strings.HasPrefix(path, "~/") {
        // Use strings.HasPrefix so we don't match paths like
        // "/something/~/something/"
        path = filepath.Join(dir, path[2:])
    }
    

    (注意 user.Current() 没有在 go playground 中实现(可能是出于安全原因),所以我不能给出一个易于运行的示例)。

    【讨论】:

    • 还要注意,shell 支持~user/foo,它也指的是user 的主目录中的foo
    • 代码应该是dir+"/",否则结果少了一个/
    • 您可以使用filepath.Join(usr.HomeDir, path[2:]) 而不是strings.Replace 来确保正确添加了目录分隔符。在 Windows 上,至少 HomeDir 没有以斜线结尾。
    • 这个答案 99% 正确。但是if strings.HasPrefix(path, "~/")if path[:2] == "~/" 更准确。如果路径长度为零或一个字符,则后者将失败。路径“~”也没有正确处理——它也应该扩展到 $HOME。
    • 好收获;固定。
    【解决方案3】:

    一般来说,~ 会被你的 shell 扩展进入你的程序。但是有some limitations

    一般是ill-advised to do it manually in Go

    在我的一个程序中遇到了同样的问题,我的理解是如果我使用标志格式为--flag=~/myfile,它不会被扩展。但是如果你运行--flag ~/myfile,它会被shell 扩展(= 丢失并且文件名显示为一个单独的“单词”)。

    【讨论】:

    • 这比接受的答案或最受好评的答案更简单、更正确。
    【解决方案4】:

    如果您要扩展波浪号 '~' 以与 exec.Command() 一起使用,您应该使用用户本地 shell 进行扩展。

    // 'sh', 'bash' and 'zsh' all respect the '-c' argument
    cmd := exec.Command(os.Getenv("SHELL"), "-c", "cat ~/.myrc")
    cmd.Stdout = os.Stdout
    if err := cmd.Run(); err != nil {
        fmt.Fprintln(os.Stderr, err)
    }
    

    但是;在加载诸如~./myrc 之类的应用程序配置文件时,此解决方案是不可接受的。以下在多个平台上对我来说效果很好

    import "os/user"
    import "path/filepath"
    
    func expand(path string) (string, error) {
        if len(path) == 0 || path[0] != '~' {
            return path, nil
        }
    
        usr, err := user.Current()
        if err != nil {
            return "", err
        }
        return filepath.Join(usr.HomeDir, path[1:]), nil
    }
    

    注意: usr.HomeDir 不尊重 $HOME,而是通过 (osx/linux) 上的 getpwuid_r 系统调用读取 /etc/passwd 文件来确定主目录。在 Windows 上,它使用 OpenCurrentProcessToken 系统调用来确定用户的主目录。

    【讨论】:

    • 太棒了!感谢您提供完整的示例(包括错误检查和所有功能!)但您还需要 import "path/filepath" 否则您的代码将无法正常工作。
    【解决方案5】:

    我知道这是一个老问题,但现在有另一种选择。您可以使用go-homedir 将标题扩展至用户的homedir:

    myPath := "~/.ssh"
    fmt.Printf("path: %s; with expansion: %s", myPath, homedir.Expand(myPath))
    

    【讨论】:

      猜你喜欢
      • 2011-08-10
      • 1970-01-01
      • 1970-01-01
      • 2019-05-03
      • 2011-06-06
      • 2010-11-03
      • 2013-03-29
      相关资源
      最近更新 更多