纠结的链接——ln、ln -s、fs.symlink、require

纠结的链接——ln、ln -s、fs.symlink、require



提交

我的留言

加载中



已留言

inode

我们首先来看看 linux 系统里面的一个重要概念:inode。

我们知道,文件存储在硬盘上,硬盘存储的最小单位是扇区(sector,每个扇区 512 B)。而操作系统读取文件时,按块读取(连续的多个扇区),也就是说文件存取的最小单位是块(block,块通常是 4 KB)。

除了文件数据,我们还必须存储文件的元信息(如:文件大小、文件创建者、文件数据的块位置、文件读/写/执行权限、文件时间戳等等),这种存储文件元信息的结构就称为 inode。我们可以使用 stat 命令查看文件的 inode 信息。在 Node 中,调用 fs.stat 后返回的结果中也有相关信息

每个 inode 都有一个唯一的号码标志,linux 系统内部使用 inode 的号码来识别文件,并不使用文件名。我们打开一个文件时,系统首先找到文件名对应的 inode 号码,然后通过 inode 号码获取 inode 信息,最后根据 inode 信息中的文件数据所在的 block 读出数据。

实际上,在 linux 系统中,目录也是一种文件。目录文件包含一系列目录项,每个目录项由两部分组成:所包含文件的文件名,以及该文件名对应的 inode 号码。我们可以使用 ls -i 来列出目录中的文件以及它们的 inode 号码。这其实也解释了仅更改目录的读权限,并不能实现读取目录下所有文件内容的原因,通常需要 chmod -R 来进行递归更改。

总结下:

  • 硬盘存取的最小单位是扇区,文件存取的最小单位是块(连续的扇区)
  • 存储文件元信息(文件大小、创建者、块位置、时间戳、权限等非数据信息)的结构称为 inode
  • 每个 inode 拥有一个唯一号码,系统内部通过它来识别文件
  • 目录也是一种文件,其内容包含一系列目录项(每个目录项由文件的文件名和文件对应的 inode 号码组成)


硬链接和软链接

硬链接

一般情况,一个文件名“唯一”对应一个 inode。但是,linux 允许多个文件名都指向同一个 inode。这表示我们可以使用不同的文件名访问同样的内容;对文件内容进行修改将“反映”到所有文件;删除一个文件不影响另一个文件的访问 。这种机制就被称为“硬链接”。

我们可以使用 ln source target 来建立硬链接(注意:source 是本身已存在的文件,target 是将要建立的链接)。

形象化的表示为下图:

需要注意的是,只能给文件建立硬链接,而不能给目录建立硬链接。另外,source 文件必须存在,否则将会报错。

删除一个文件为什么不影响另一个文件的访问呢?实际上,文件 inode 中还有一个链接数的信息,每多一个文件指向这个 inode,该数字就会加 1,每少一个文件指向这个 inode,该数字就会减 1,当值减到 0,系统就自动回收 inode 及其对应的 block 区域。很像是一种引用计数的垃圾回收机制。

当我们对某个文件建立了硬链接后,对应的 inode 的链接数会是 2(原文件本身已经有一个指向),当删除一个文件时,链接数变成 1,并没达到回收的条件,所以我们还是可以访问文件。

软链接

软链接类似于 windows 中的”快捷方式“。两个文件虽然 inode 号码不一样,但是文件 A 内部会指向文件 B 的 inode。当我们读取文件 A 时,系统就自动导向文件 B,文件 A 就是文件 B 的软链接(或者叫符号链接)。这表示我们同样可以使用不同的文件名访问同样的内容;对文件内容修改将”反映“到所有文件。但是当我们删除掉源文件 B 时,再访问文件 A 时会报错 “No such file or directory”。

我们可以使用 ln -s source target 来建立软链接(注意:表示让 target “指向”source)。

形象化的表示为下图:

和硬链接不同,我们可以给目录建立软链接,这带来许多便利。比如我们有一个模块有很多个版本,分别存放在 1.0.0、2.0.0 这样的目录下面,当更新模块时,只需要建立一个软链接指向最新版本号的目录就能很方便的切换版本。

另外,建立软链接时,source 是可以不存在的。这很像一种”运行时“机制,而不是“编译时”机制,建立的时候不报错,等执行的时候发现找不到就报错了。


总结

  • 使用 ln source target 建立硬链接;使用 ln -s source target 建立软链接
  • 硬链接不会创建额外 inode,和源文件共用同一个 inode;软链接会创建额外一个文件(额外 inode),指向源文件的 inode
  • 建立硬链接时,source 必须存在且只能是文件;建立软链接时,source 可以不存在而且可以是目录
  • 删除源文件不会影响硬链接文件的访问(因为 inode 还在);删除源文件会影响软链接文件的访问(因为指向的 inode 已经不存在了)
  • 对于已经建立的同名链接,不能再次建立,除非删掉或者使用 -f 参数

关于软链接的补充

上面的例子 ln -s file file-soft 给我们的感觉像是 file-soft 是“凭空”出现的。当我们跨目录来创建软链接时,可能会“幻想”这样的命令也是可以生效的:ln -s ~/development/mod ~/production/dir-not-exits/mod

这里并没有 ~/production/dir-not-exits/ 这个目录,而软链接本质上是一个新的“文件”,所以,我们不可能正确建立软链接(会报错说 “no such file or directory”)。

如果我们先通过 mkdir 建立好目录 ~/production/dir-not-exits/,再进行软链接,即可达到预期效果。

fs.symlink

在 node 中,我们可以使用方法 fs.symink(target, path) 建立软链接(符号链接),没有直接的方法建立硬链接(就算通过子进程的方式直接指向 shell 命令也不能跨平台)。

如果是对目录建立链接,请总是传递第三个参数 dir(虽然第三个参数只在 windows 下生效,这可以保证代码跨平台):fs.symlink(target, path, ‘dir‘)

为啥这个接口的参数会是 target 和 path。实际上这是一个 linux 的 API,symlink(target, linkpath)。它是这样描述的:建立一个名为 linkpath 的符号链接并且含有内容 target。其实就是让 linkpath 指向 target,和 ln -s source target 功能一样,让target 指向 source

是不是有点晕?其实我们只需要明白 ln -s 和 fs.symlink 后面传递的两个参数顺序是一致的,只是叫法不一样,使用起来也就没那么纠结了:


ln -s file file-soft # file-soft -> file

ln -s dir dir-soft # dir-soft -> dir


fs.symlinkSync(‘file‘, ‘file-soft‘); // file-soft -> file

fs.symlinkSync(‘dir‘, ‘dir-soft‘, ‘dir‘); // dir-soft -> dir

require

在 Node 中,我们经常通过 require 来引用模块。非常有趣的是,require 引用模块时,会“考虑”符号链接,但是却使用模块的真实路径作为 __filename__dirname,而不是符号链接的路径。

考虑下面的目录结构:


- app

- index.js // require(‘dep1‘)

- node_modules

- dep1 -> ../../mods/dep1 //符号链接

- mods

- dep1

- index.js

以及下面的文件内容:


// index.js

console.log(‘index.js‘, __dirname, __filename);

require(‘dep1‘);

// dep1/index.js

console.log(‘dep1‘, __dirname, __filename);

console.log(module.paths);

执行 node index.js 后输出是下面这样:


index.js /Users/kohpoll/Workspace/test/app /Users/kohpoll/Workspace/test/app/index.js

dep1 /Users/kohpoll/Workspace/test/mods/dep1 /Users/kohpoll/Workspace/test/mods/dep1/index.js
[ ‘/Users/kohpoll/Workspace/test/mods/dep1de_modules‘,
  ‘/Users/kohpoll/Workspace/test/modsde_modules‘,
  ‘/Users/kohpoll/Workspace/testde_modules‘,
  ‘/Users/kohpoll/Workspacede_modules‘,
  ‘/Users/kohpollde_modules‘,
  ‘/Usersde_modules‘,
  ‘de_modules‘ ]

我们发现,index.js 可以成功的 require(‘dep1‘)。这很好啊,这让我们调试本地开发中的 npm 模块很方便。我们只需要去require 模块的文件所在的 node_modules 下面建立一个符号链接就行了。

但是在模块 dep1 中,__dirname__filename 都变成了模块实际的路径,更要命的是模块查找路径 module.paths 也变成了从实际路径开始查找。

这会带来什么问题?

再考虑下面的目录结构:


- app

- index.js // require(‘dep1‘)

- node_modules

- dep1 -> ../../mods/dep1 // require(‘dep2‘)

- dep2 -> ../../mods/dep2 // 符号连接

- mods

- dep1

- index.js

- dep2

- index.js

以及下面的文件内容:


// index.js

console.log(‘index.js‘, __dirname, __filename);

require(‘dep1‘);

// dep1/index.js

console.log(‘dep1‘, __dirname, __filename);

console.log(module.paths);

require(‘dep2‘);

// dep2/index.js

console.log(‘dep2‘, __dirname, __filename);

console.log(module.paths);

当我们再执行 node index.js 时,输出是下面这样:


index.js /Users/kohpoll/Workspace/test/app /Users/kohpoll/Workspace/test/app/index.js

dep1 /Users/kohpoll/Workspace/test/mods/dep1 /Users/kohpoll/Workspace/test/mods/dep1/index.js
[ ‘/Users/kohpoll/Workspace/test/mods/dep1de_modules‘,
  ‘/Users/kohpoll/Workspace/test/modsde_modules‘,
  ‘/Users/kohpoll/Workspace/testde_modules‘,
  ‘/Users/kohpoll/Workspacede_modules‘,
  ‘/Users/kohpollde_modules‘,
  ‘/Usersde_modules‘,
  ‘de_modules‘ ]
  
module.js:339
    throw err;
    ^
Error: Cannot find module ‘dep2‘
    at Function.Module._resolveFilename (module.js:337:15)
    at Function.Module._load (module.js:287:25)
    at Module.require (module.js:366:17)
    at require (module.js:385:17)
    at Object.<anonymous> (/Users/kohpoll/Workspace/test/mods/dep1/index.js:6:1)
    at Module._compile (module.js:435:26)
    at Object.Module._extensions..js (module.js:442:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:311:12)
    at Module.require (module.js:366:17)

发现了吗?dep1 根本就 require 不到 dep2,因为 dep2 不在它的查找路径里面!

关于这个问题,github 上有一个冗长的 issue 在讨论。问题解决起来确实很麻烦,而且会 break 掉一大堆已有功能,所以,最终的结论是在找到更好的方法前给 node v6 增加了一个 --preserve-symlinks 选项来禁止这种 require 的行为,而是使用全新的 require逻辑。有兴趣和闲情的可以去围观:https://github.com/nodejs/node/issues/3402(真的好长......)。

至于全新的 require 逻辑会不会有新的坑,在没有具体实践前,我也不知道。

那我们上面的情况有办法解决吗?其实也有,那就是将目录结构调整成下面这样,从而让 dep2 能在 dep1 的查找路径里面:


- app
  - index.js // require(‘dep1‘)
  - node_modules
    - dep1 -> ../../modsde_modules/dep1 // 符号链接
    - dep2 -> ../../modsde_modules/dep2 // 符号链接
- mods
  - node_modules
    - dep1
      - index.js
    - dep2
      - index.js

阅读

投诉

精选留言

该文章作者已设置需关注才可以留言

写留言

该文章作者已设置需关注才可以留言

写留言

加载中

以上留言由公众号筛选后显示

了解留言功能详情



微信扫一扫
关注该公众号

来自为知笔记(Wiz)

时间: 2024-10-03 07:31:23

纠结的链接——ln、ln -s、fs.symlink、require的相关文章

小蚂蚁学习Linux(4)——链接命令ln、文件搜索命令locate、whereis和which

链接命令ln    (取自link之意) ln  [源文件]    [目标文件]    功能:生成链接文件.    -s 创建软连接 ,不加是创建硬链接 硬链接的特征: 1. 拥有相同的i节点和存储block快,可以看做是同一个文件. 2. 可通过i节点识别 3. 不能跨分区 4. 不能针对目录使用 备注:硬链接只要不把所有文件名删除,删除其中一个不影响其他文件名的使用. 需要注意的是,不建议只用硬链接: 1. 硬链接太过隐蔽,只能通过查看inode节点id号识别 2. 自身的限制也比较多. 替

linux下添加链接与删除链接(ln命令的用法)

添加链接使用ln命令用法:#ln --help用法:ln [选项]... 目标 [链接名]或:ln [选项]... 目标... 目录或:ln [选项]... --target-directory=目录 目标...创建连至指定<目标>的链接,并可选择性指定<链接名>.如果没有指定<链接名>,会在目前的目录中创建一个和<目标>名称一样的链接.当使用第二种格式而<目标>多於一个时,最后的参数必须是目录:这样会在指定的<目录>中分别创建连至每

硬链接:ln: failed to create hard link `link1&#39; =&gt; `1.txt&#39;: Operation not permitted提示

[已解决]硬链接:ln: failed to create hard link `link1' => `1.txt': Operation not permitted 软连接:ln: failed to create symbolic link `link1': Operation not supported [环境]VmWare的Linux + Windows 7 文件共享 [问题]在编译VMware下的Linux系统对从Windows中共享过来的文件 进行硬链接编译的时候,遇到:ln: fa

Linux的链接文件-ln命令

Linux的链接文件 使用ln命令来创建链接文件(link) Linux链接分两种:硬链接(Hard Link),符号链接(Symbolic Link) 默认情况下,ln命令产生硬链接. [[email protected] lianxi]# ln  -s  xiaotong   tongtong    #符号链接才能为目录建立链接 源文件     目标文件(链接文件) [[email protected] lianxi]# cd tongtong/ [[email protected] ton

Linux磁盘和文件系统管理(6)_链接文件 ln

文件系统上的链接文件: hard link 硬链接:    多个路径指向同一个inode 当指向一个inode的多个路径时,创建文件的硬链接时会增加inode的引用计数: 当删除硬链接时,仅是删除一个访问路径,文件还存在,inode和block还是存在的,只有删除最后一个路径时,将找不到文件了.   注意: 硬链接不能对目录进行:    硬链接不能跨分区进行         symbolic link 符号链接:相当于快捷方式      链接文件的数据指向另一个文件路径,只是利用这个文件作为指向

1-文件链接指令-----ln

ln指令是在文件之间创建链接. 也就是说,给系统中已有的某个文件制定另外一个可用于访问它的名称,对于这个新的名称,可以指定访问权限. 如果链接指定了目录,用户就可以根据该链接直接进入被链接的目录而不用使用较长的路径名,如果删除了这个链接,对原来的目录不会造成影响. 链接分为两种:硬链接和符号链接(软链接) 建立硬链接时,链接文件和被链接文件必须位于同一个文件系统中,并且不能建立指向目录的硬链接,建立符号链接不存在这个问题. 使用格式 ln [option] file link 默认是建立硬链接,

对linux中ln命令创建硬链接和软链接的分析

在使用linux的过程中,最让用户头疼的应该就是linux独有的命令行了,且不说那么多的命令行就已经让我们叫苦不迭,关键每个命令还有一大堆的选项,当然,这也是我们常说linux命令行之所以功能强大的一个很重要的原因,正是这些选项让我们的每一个命令充满了变数,而不再是单调的一个画面.今天就来说说命令中的一个比较重要的命令:ln (既link的简写). 为什么同一个命令的执行结果会有软硬之分呢?那就要说到他们的本质了.软链接本质上犹如windows上的一个桌面快捷方式,而这个快捷方式中包含了从桌面到

linux命令:ln 链接文件--硬链接,符号链接(软链接)

   ln命令简介: 默认创建硬链接,当使用-s 时创建符号链接. 1.命令格式:   ln [option] 原文件 链接文件   文件路径最好都用绝对路径   ln 原文件 链接文件   不带参数表示创建硬件链接     -s表示创建软链接    option(选项): -s  创建软链接 -v  显示创建过程. 硬链接:        1.只能对文件创建,不能应用于目录:        2.不能跨文件系统:        3.创建硬链接会增加文件被链接的次数:       符号链接(软链接

symlink在ln的过程中究竟占用了多少耗时

今天看到群里的童鞋们在讨论ln命令的使用,天花乱坠说到了symlink,有同学用估算的口气说symlink耗时很低的,肯定ln总耗时是symlink的好几百倍,他猜中了吗? 现在针对ln -s ~/install install来验证下strace -tt -T输出: 14:08:20.190334 execve("/bin/ln", ["ln", "-s", "/home/linxp/install", "inst