`os.symlink`与`ln -s`的区别

59

我需要为dir2中的每个项目(文件或目录)创建一个符号链接。 dir2已经存在且不是符号链接。在Bash中,我可以轻松实现这一点:

ln -s /home/guest/dir1/* /home/guest/dir2/

但是在 Python 中使用 os.symlink 时我遇到了错误:

>>> os.symlink('/home/guest/dir1/*', '/home/guest/dir2/')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OSError: [Errno 17] File exist

我知道可以使用subprocess来运行ln命令。但我不想采用那个解决方案。

我也知道可以使用os.walkglob.glob的变通方法,但我想知道是否可能使用os.symlink来实现。


4
我认为 os.symlink 只是对相应系统调用的简单封装(或多或少),因此不会提供与使用该系统调用的完整工具相同的语义。 - 0xC0000022L
2
“使用os.symlink”需求的原因是什么?如果正确的方法是对glob进行“for”循环,为什么不想做正确的事情呢? - abarnert
4
你仍然没有抓住关键点:你要找的不是 ln -s 命令可以完成的事情,而是 shell 可以完成的事情。 - abarnert
@0xC0000022L:稍微有点复杂(请参见源代码),但只是一点点;基本上,它最终调用symlinksymlinkatCreateSymbolicLinkW。而且语义明显是设计成与POSIX symlink匹配的,即使它最终没有使用该函数。 - abarnert
1
@0xC0000022L:我并不是在纠正你,而是消除你“我 _认为_”前缀中的任何疑虑。 - abarnert
显示剩余3条评论
3个回答

78

os.symlink 创建一个符号链接。

ln -s 可以创建多个符号链接(如果其最后一个参数是目录,并且有多个源)。Python的等效方式大致如下:

dst = args[-1]
for src in args[:-1]:
    os.symlink(src, os.path.join(dst, os.path.dirname(src)))

那么,当你执行ln -s /home/guest/dir1/* /home/guest/dir2/时,它是如何工作的呢?你的shell通过将通配符转换为多个参数来使其正常工作。如果你只是使用通配符执行ln命令,它将在/home/guest/dir1/中查找一个名为*的单个源文件,而不是该目录中的所有文件。

在Python中,相当于以下代码(如果您不介意混合两个层级并忽略许多其他情况——比如波浪线、环境变量、命令替换等,这些在shell中都是可能的):

dst = args[-1]
for srcglob in args[:-1]:
    for src in glob.glob(srcglob):
        os.symlink(src, os.path.join(dst, os.path.dirname(src)))

仅使用os.symlink是无法实现这个目标的,因为它本身并不支持。就像说“我希望使用os.walk查找文件夹中所有名为foo的文件”,但又不想筛选文件名一样。或者说,“我想要实现类似ln -s /home/guest/dir1/* /home/guest/dir2/的功能,但又不想使用shell的globbing。”

正确的答案是使用globfnmatchos.listdir加上正则表达式,或者你喜欢使用的其他方法。

不要使用os.walk,因为这将进行递归的文件系统遍历,所以它与Shell的*扩展完全不同。


"ln" 是 ln [target] [link],但是你所描述的有几个目标怎么可能存在呢?我认为你指的是有几个链接指向一个目标吧? - Timo

17

* 是一个 shell 扩展模式,它在你的情况下指定“以 /home/guest/dir1/ 开头的所有文件”。

但这是你的shell的角色来扩展此模式以匹配它所表示的文件,而不是ln命令的角色。

os.symlink不是一个 shell,它是一个操作系统调用 - 因此,它不支持 shell 扩展模式。您需要在脚本中完成这项工作。

要做到这一点,您可以使用os.walkos.listdir。如其他答案中所示,适当的调用取决于您想要做什么。(os.walk不会相当于*


为了让自己信服:在 Unix 机器上的终端中运行此命令:python -c "import sys; print sys.argv" *。您将看到是 shell 在进行匹配。


1
关于globbing的实验加1分。但请记住,Windows shell(cmd.exe)在传递给调用的程序之前不会执行此操作。 - 0xC0000022L
@0xC0000022L:说实话,Windows没有ln。而MSVCRT(他们的libc)带有一种模拟shell globbing在argv上的hack,可以在你的main开始之前或者当你明确要求时使用,我相信大多数Unix程序的端口都会使用它或者类似的东西(例如,Cygwin使用的任何东西)。 - abarnert
@0xC0000022L 我必须承认,我没有考虑到在 Windows 上是否能正常运行 - 我会进行更新! - Thomas Orozco
@abarnert:嗯,他们称之为 mklink,就像在*nix世界中已知的 mkdir 被简单地称为 Windows 世界中的 md - 0xC0000022L
@0xC0000022L:但是 mklink 的语法与 ln 不同。特别是,mklink 无法将源列表链接到目录中;它只能接受单个源和单个目标。 - abarnert
@abarnert:说得对。我原以为你指的是整个工具的缺失。 - 0xC0000022L

9

正如 @abarnert 所建议的那样,是 shell 识别 * 并将其替换为 dir1 内的所有项目。因此,我认为使用 os.listdir 是最佳选择:

for item in os.listdir('/home/guest/dir1'):
    os.symlink('/home/guest/dir1/' + item, '/home/guest/dir2/' + item)

12
建议使用 os.path.join 而不是 + - Radon Rosborough

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