如何在Go中覆盖符号链接?

5
我希望使用Go覆盖符号链接,但我找不到如何实现的方法。
如果我尝试创建符号链接并且它已经存在,则会返回错误。
我的代码:
err := os.Symlink(filePath, symlinkPath)
if err != nil {
    fmt.Println(err)
}

我猜符号链接必须先被删除,然后再重新创建。是这样吗?如果是的话,如何取消符号链接呢?

3个回答

8

在创建新符号链接之前,请检查该符号链接是否存在并删除它。

if _, err := os.Lstat(symlinkPath); err == nil {
  os.Remove(symlinkPath)
}

没错,我的应用程序出现了一个 bug,它总是返回"没有这个文件或目录",这就是为什么在 err != nil 的情况下它能正常工作。该文件已经被删除了……非常抱歉给您带来困扰。 - Skywalker13
我了解问题,您使用的是Stat 而不是 Lstat,现在我使用 if _, err := os.Lstat(symlinkPath); err == nil { 来解决它。 我需要检查符号链接是否存在,而不是目标文件。 - Skywalker13

6
请注意,@Vadyus的答案在运行lstat时隐藏了实际的文件系统错误。例如,如果您的磁盘损坏并且Lstat失败,您仍将运行os.Remove并忽略其错误(除非您喜欢为此调试几个小时)。
以下代码片段正确检查文件是否存在以及其他错误:
if _, err := os.Lstat(symlinkPath); err == nil {
  if err := os.Remove(symlinkPath); err != nil {
      return fmt.Errorf("failed to unlink: %+v", err)
  }
} else if os.IsNotExist(err) {
    return fmt.Errorf("failed to check symlink: %+v", err)
}

6
这里的其他答案都是正确的...但有两个小问题:
  • 存在一个微小的数据竞争,新的符号链接可能在此处之前在其他地方创建,但在删除后,它将处于潜在的不一致状态。
  • 如果程序在创建符号链接之前死机/崩溃,但在删除先前的符号链接之后,它可能会再次使事情处于不一致状态。
更原子化的处理方式是创建一个临时符号链接,然后将其重命名为原始符号链接:
symlinkPathTmp := symlinkPath + ".tmp"
if err := os.Remove(symlinkPathTmp); err != nil && !os.IsNotExist(err) {
  return err
}

if err := os.Symlink(filePath, symlinkPathTmp); err != nil {
  return err
}

if err := os.Rename(symlinkPathTmp, symlinkPath); err != nil {
  return err
}

删除临时链接并重新创建之间仍然存在一小段时间窗口,但不会冒险使主要链接处于不一致状态。理想情况下,我们可以通过为临时链接使用随机名称来解决这个问题,但是 Go 的 TempFile 总是会创建一个新文件,因此它不太有用。(您可能会调用 TempFile,然后删除文件名并重复使用该名称,这样做的风险更大,但仍然比仅追加常数.tmp后缀更安全。)
即使存在这种竞争,您仍然可以获得原子性,在任何中断下都不会导致丢失链接。
请注意,这取决于 POSIX 行为,可能无法在 Windows 上工作(反正你为什么会在 Windows 上使用符号链接呢?),但这是许多需要原子符号链接替换的 macOS/Linux 工具共享的一种技术。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接