一:文件名
到目前为止我们都是用字符串表示文件名,因为不同的系统的文件命名方案不一样,这样就会导致代码捆绑到特定的文件系统。
为了实现可移植,Common lisp定义了另一种文件表示方式:路径名(pathname)
而以本地语法写成的字符串即名字字符串(namestring)
二:路径名如何表示文件名
路径名使用6个组件来表示文件名的结构化对象:主机(host),设备(device),目录(directory)
名称(name),类型(type),以及版本(version).这些组件都接受原子值,通常字符串。
只有目录有进一步的结构和描述,有目录的列表,其带有关键字:absolute或:relative为前缀。
通常让具体实现来把一个文件系统相关的名字字符串解析到一个路径名对象。
通过从一个已有路径名中取得其多数组件来创建新路径名,从而得到路径名对象。
在windows上,驱动器字母要么被放置到设备中,要么放在主机组件中。其他名字元素除最后一个之外都被放置在一个以:absolute或:relative开始的列表中,具体取决于该名字是否以一个路径分隔符开始。列表成为路径名的目录组件。
CL-USER> (pathname-directory (pathname "/foo/bar/baz.txt")) (:ABSOLUTE "foo" "bar")
CL-USER> (pathname-directory (pathname "foo/bar/baz.txt")) (:RELATIVE "foo" "bar")
如果有的话,最后一个元素将会被一个点分拆开,分别放到路径名的名称和类型组件,比如下面就是一个文件形式的目录表示,会把最后一个/后面的单元当成name,如果有type的话,根据点号来区别开。
CL-USER> (pathname-name (pathname "foo/bar/baz.txt")) "baz"
CL-USER> (pathname-type (pathname "foo/bar/baz.txt")) "txt"
注:许多基于Unix的实现特别对待那些最后一个元素以点开始切不含任何其他点的文件名,将整个元素包括点在内放置在名称组件中,类型组件为NIL
CL-USER> (pathname-name (pathname "/foo/.emacs")) ".emacs"
CL-USER> (pathname-type (pathname "/foo/.emacs")) NIL
比如(list 2 1)可以用'(2 1)表示,(vector 2 3)可以用#'(2 3)。所以路径名也有自身的读取语法,使lisp读取器知道这个是一个路径名: #p后接双引号字符串。
CL-USER> (pathname "/foo/bar/baz.txt") #P"/foo/bar/baz.txt"
上面是一种把路径名分解的方式,即把目录名用列表形式存放,然后name和type也都分别放起来。有的时候一些处理必须要用string类型,#P对象有时不识别,然后就通过下面函数把路径名变为string类型。
CL-USER> (namestring #p"/foo/bar/baz.txt") "/foo/bar/baz.txt"
CL-USER> (directory-namestring #p"/foo/bar/baz.txt") "/foo/bar/"
CL-USER> (file-namestring #p"/foo/bar/baz.txt") "baz.txt"
1:构建新路径名
1:我们上面分解路径名的时候,首先是拆分目录为列表形式,然后name/type也都各自存值。注意格式name/type后面必须是个string对象,比如你说他的后缀是1,一个数字,自求值,同样需要写成格式:"1"
CL-USER> (make-pathname :directory '(:absolute "foo" "bar")
:name "baz"
:type "txt")
#P"/foo/bar/baz.txt"
2:根据提供的默认路径进行路径名构建
像上面这种形式,都是自己键入的参数的值,有的时候可移植性就不行,比如你在windows里面你是路径/home/peter/foo.txt 但是如果你移植到linux上面,/home/被称为/User/,这时就不太好,所以你可以通过一个已有的路径名作为Make-pathname 的关键字参数:default来构造一个新路径名。:default参数的组件会填充任何没有被其它参数指定的组件。虽然例子中只是指定了相对目录,但是也就是目录指定了,但是name和type都没有指定,所以就用了默认的了。
如果在make-pathname中指定了Name/type,不轮什么值,都会无视default中的指定。
CL-USER> (make-pathname :directory '(:relative "backups")
:defaults #p"/foo/bar/baz.txt")
#P"backups/baz.txt"
CL-USER> (make-pathname :name "ryu"
:defaults #p"/foo/bar/baz.txt")
#P"/foo/bar/ryu.txt"
3:Merge-Pathname
还有一种情况就是你知道一个相对路径和一个绝对路径,现在你需要将他们拼接到一块这个时候就需要merge-pathname了。它接受两个路径并且合并他们,用第二个路径名的对应值填充第一个路径名中的任何NIL组件。
注意点:当第一个目录为相对目录时,生成的路径名的目录都是第一个路径名的目录相对于第二个路径名的目录。不论第二个目录是相对的还是绝对的。因为你想呀,因为路径的话肯定是相对的要比绝对的低一个阶层,所以就分两种情况了
当第一个路径名为相对的,最后结果都是第一个相对第二个的路径名。比在defaults指定的要宽松一些
CL-USER> (merge-pathnames #p"foo/bar.html" #p"/www/html/") #P"/www/html/foo/bar.html"
CL-USER> (merge-pathnames #p"foo/bar.html" #p"www/html/") #P"www/html/foo/bar.html"
当第一个路径名为绝对的目录的时候,就忽视第二个路径名的目录。因为毕竟第二个是用来补充的。
CL-USER> (merge-pathnames #p"/foo/bar.html" #p"www/html/") #P"/foo/bar.html"
CL-USER> (merge-pathnames #p"/foo/bar.html" #p"/www/html/") #P"/foo/bar.html"
CL-USER> (merge-pathnames "sbcl/adsf" "d:\\foo\\bar.zip") #P"d:/foo/sbcl/adsf.zip"
4:相对于某一个根目录的路径
还有一种需求就是你现在有个目录,然后如何得到相对这个目录的文件名。注意比较下面例子3跟4的区别。对于一个路径名字符串,加入最后一个字段后面没有正斜杠,那么他就会被当成是一个pathname-name成分,这个就是目录的文件形式,因为看着像我们平常进行文件路径的描述,但实际上它是一个目录。你可以验证结果,比如:Enough-namestring的结果跟第二个参数组合正好是第一个路径名。
CL-USER> (enough-namestring #p"/www/html/foo/bar.html" #p"/www/") "html/foo/bar.html"
CL-USER> (enough-namestring #p"/www/html/foo/bar.html" #p"www/") "/www/html/foo/bar.html"
CL-USER> (enough-namestring #p"/www/html/foo/bar.html" #p"/www") "www/html/foo/bar.html"
CL-USER> (merge-pathnames #p"html/foo/bar.html" #p"/www/") #P"/www/html/foo/bar.html"
Merge-pathname与make-pathname组合构建相同名字根目录不同的路径。
CL-USER> (merge-pathnames
(enough-namestring #p"/www/html/foo/bar/baz.html" #p"/www/")
#p"/www-backups/")
#P"/www-backups/html/foo/bar/baz.html"
5:目录名的两种表示方法
1:文件形式,将名字字符串中的最后一个元素放在名称和类型组件中。
2:目录形式,将名字中所有元素放到目录组件中,而名称与类型组件为NIL
文字形式就是我们平常用的表示方法,比如:"foo/bar"或"foo/bar/tar.txt"都是我们平常的书写习惯,但是对于"foo/bar"就会有个问题,就是他会把bar当做一个name来处理,这是一个很现实的问题,比方说,你现在在文件夹foo就是有一个没有类型的文件bar,这也是很正常,并且现在假设bar同样是一个文件夹,你说"foo/bar"这样写错不错? 不错,就是有点让人不习惯,如果是对人的话,你一看就知道怎么回事,如果foo下有bar这个文件就当成name,如果是文件夹的话,就当成一个目录,但是对于计算机而言,他需要一个统一性的标准,如果你说把"foo/bar"都当成目录处理,那万一bar是个文件怎么办?所以最合理的方式就是碰见foo/bar形式把bar当成一个name来定,如果本来就像让他表示成名字的话,正好,你可以通过pathname-name得到它,如果你想让他表示目录的话,处理的时候就给他在后面加个斜杠就行了。
注意点,
如果是像上面说的这样,如果是一个名字字符串最后一个元素后面没有斜杠的话,就把他认为是name,就会下面这个问题,其实你也可以类比"d:/emacs/file.lisp"他是如何得到pathname-name的,同样是最后一个斜杠后面的那个,但是有点号把它又跟type区分开了。比方说我默认给你了一个/home/peter路径名作为:default,然后你会发现他存起来的情况是/home/foo.txt而不是你想要的/home/peter/foo.txt.解决办法只能是调用一个函数让他把/home/peter转化成/home/peter/形式,这个是书中的解决方案,但是又有个问题就是,如果你把所有的string都按照这种方式把file类型的转化为目录类型的,那么当:defaults 为"/home/peter/a.txt"你这个时候就只想让他被读成名字,怎么办?前提是你也不知道将会写入什么样的:defaults形式,
CL-USER> (make-pathname :name "foo" :type "txt"
:defaults "/home/peter")
#P"/home/foo.txt"
下面是pathname-as-directory中的一段逻辑,注意or语句返回第一个不为假的对象,也就是说如果pathname-directory有值就行。并且指定了name/type所以就不用管default中的指定了,你可以看结果file.lisp/并不是我期待的结果,我想要的是它能清楚这个是一个文件而不是目录。我感觉是不是可以加一段说如果有type就按照文件名来处理,但是你反过来想,既然foo/peter中peter是文件但是被当成目录处理foo/peter/这种形式可以接受,为啥就不能接受一个多了type的文件呢?他们应该是一致的。
现在关键就是什么时候用pathname-as-directory函数,或者你就注意了在利用函数pathname-as-directory 的时候别往default里面填写一个具体执行某一个文件的路径。
CL-USER> (make-pathname :directory (append (or (pathname-directory "d:/emacs/file.lisp") (list :relative))
(list (file-namestring "d:/emacs/file.lisp")))
:name nil
:type nil
:defaults "d:/emacs/file.lisp")
#P"d:/emacs/file.lisp/"