Go语言入门:目录与链接
目录与链接
17.2 创建和删除目录
在os包中,以下两个函数都可以用于创建新目录。
func Mkdir(name string, perm FileMode) error
func MkdirAll(path string, perm FileMode) error
这两个函数的区别在于:如果要创建多级目录,Mkdir
函数单次调用只能创建一级目录;MkdirAll
函数可以一次性创建多级目录。
例如,要创建目录fd01/fd02
,如果使用Mkdir函数,则需要调用两次。第一次调用创建fd01
目录,第二次调用创建fd01/fd02
目录。
err := os.Mkdir("fd01", 0644)
if err != nil {fmt.Println(err)
}
err = os.Mkdir("fd01/fd02", 0644)
......
如果使用的是MkdirAll
函数,那么只要调用一次即可。
err := os.MkdirAll("fd01/fd02", 0644)
Mkdir
和MkdirAll
函数的第二个参数均用于为新目录设置权限。在上述代码中,权限码0644(八进制)的含义为:目录所有者具有读写权限,用户所在的组和其他用户仅具有读的权限。
删除目录也有两个相关的函数。
func Remove(name string) error
func RemoveAll(path string) error
这两个函数不仅可以删除目录,也可以用于删除文件,这取决于传递的参数。如果被删除的目标是文件,那么被删除的对象是文件;如果被删除的目标是目录,那么被删除的对象是目录。
Remove
函数每次只能删除一个对象。假设有以下目录结构。
DR01
└─DR02└─hot
其中,DR01
、DR02
是目录,hot
是文件。若要使用 Remove
函数将 DR01
删除,就需要调用三次:第一次删除 DR01/DR02/hot
文件,第二次删除 DR01/DR02
目录,第三次删除 DR01
目录。
err := os.Remove("DR01/DR02/hot")
if err != nil {// 处理错误
}
err = os.Remove("DR01/DR02")
if err != nil {// 处理错误
}
err = os.Remove("DR01")
if err != nil {// 处理错误
}
RemoveAll
函数在调用时,如果被删除的目标是目录,会连同其包含的文件、子目录,以及子目录中的内容一起被删除。也就是说,RemoveAll
函数会删除目标路径中的所有内容。
例如,上面例子中的 DR01/DR02/hot
路径,调用 RemoveAll
函数时只要指定目标路径为 DR01
,就可以将 DR01
目录以及其子级的所有内容删除。
err := os.RemoveAll("DR01")
17.3 硬链接与符号链接
链接允许为一个文件创建一个或多个文件名(或者称为访问点),访问任意一个链接均可以访问到原始文件。链接可分为硬链接和符号链接。
17.3.1 硬链接
硬链接就是让原始文件拥有一个或多个文件名,这些文件名都指向原始文件的索引(文件系统为每个文件分配的唯一编号,标识文件在硬盘中的数据区域)。实际上,当用户创建一个文件时,这个新文件至少存在一个硬链接——因为文件至少得有一个文件名才能被访问。
硬链接具有以下特点:
- 为原始文件创建新的硬链接,实际上没有产生新的文件。硬链接的文件索引与原始文件相同。所以,硬链接仅仅是多增加一个文件名罢了。
- 指向同一文件的硬链接可以位于同一目录下(文件名不相同),也可以位于不同目录中。
- 可以以一个硬链接为源头创建新的硬链接,新链接依然指向原始文件。
- 删除一个硬链接对其他硬链接和原始文件无任何影响。
- 当指向同一个文件的最后一个硬链接被删除后,文件就会被删除。假设文件A(A是原始文件的名字,也属于硬链接)产生了B、C两个硬链接。此时若将A删除,文件仍然存在,因为它还有B、C两个文件名,若将B删除,文件就剩下一个文件名C,把C也删除,此时文件的链接计数为0,所以文件就会被删除。
- 重命名硬链接后,它所指向的文件不受影响。
在下面的代码中,先创建F1文件并写入一些内容,随后调用os.Link函数创建硬链接F2,再从F2创建硬链接F3。
// 创建F1文件
myfile, err := os.Create("F1")
if err != nil {fmt.Println(err)return
}
// 写入文件内容
myfile.WriteString("This is a file.\n")
// 关闭文件
myfile.Close()// 创建硬链接F2
err = os.Link("F1", "F2")
if err != nil {fmt.Println(err)return
}
// 创建硬链接F3
err = os.Link("F2", "F3")
if err != nil {fmt.Println(err)return
}
Link
函数用于创建硬链接,其定义如下:
func os.Link(oldname string, newname string) error
参数oldname
表示旧的链接(文件名),参数newname
表示新的链接(文件名)。若硬链接创建成功,函数返回nil,否则返回一个指向LinkError
实例的指针,包含错误信息。
上述代码执行后,会产生F1、F2、F3三个文件。此时无论访问哪个文件,读到的内容都相同。例如:
myfile, _ := os.Open("F3")
fmt.Println("F3的内容:")
bf := new(bytes.Buffer)
io.Copy(bf, myfile)
fmt.Printf("%s\n", string(bf.Bytes()))
从F3中读到的内容依然是“This is a file.”。
接下来尝试通过F2去修改文件,在文件内容的末尾追加文本。
myfile, _ := os.OpenFile("F2", os.O_WRONLY|os.O_APPEND, 0644)
myfile.WriteString("This is a text file.\n")
myfile.Close()
然后通过F1来读取文件内容。
myfile, _ := os.Open("F1")
bf.Reset() // 清空缓冲区中的数据
fmt.Println("F1的内容:")
io.Copy(bf, myfile)
fmt.Printf("%s\n", string(bf.Bytes()))
读出来的结果为:
This is a file.
This is a text file.
由于F1、F2、F3指向同一个文件,所以通过F2修改文件后,从F1中也能读取到文件的最新内容,毕竟读写的都是同一个文件,只是文件名不同而已。
17.3.2 符号链接
符号链接(也称为软链接)类似于 Windows 系统中的快捷方式,通过一种特殊的文件类型来保存对原始文件路径的引用。符号链接具有自己的文件索引号,简而言之就是符号链接是用一个文件来保存另一个文件的路径,这一点与硬链接不同。硬链接只是为文件多创建一个文件名,不会产生新的文件。当原始文件被删除后,符号链接将无法使用。
创建符号链接使用 Symlink
函数,其使用方法与 Link
函数相同。
下面的示例先创建文件 Srcfile
,并写入 5 字节,然后调用 Symlink
函数为 Srcfile
文件创建符号链接。
// 创建新文件
file, err := os.Create("Srcfile")
if err != nil {fmt.Println(err)return
}
// 写入内容
var data = []byte{1, 2, 3, 4, 5}
file.Write(data)
// 关闭文件
file.Close()// 创建符号链接
err = os.Symlink("Srcfile", "myLink")
if err != nil {fmt.Println(err)return
}
17.4 WriteFile函数与ReadFile函数
这两个函数位于io/iouti
l包中,WriteFile
函数封装了os.OpenFile
函数和os.File.Write
方法的调用,ReadFile
函数封装了os.Open
函数的调用。在读写文件的时候使用这两个函数可以化繁为简,提高编码效率。
ReadFile函数的签名如下:
func ReadFile(filename string) ([]byte, error)
此函数会读取文件中的所有内容并以[]byte
类型返回。
WriteFile
函数的签名如下:
func WriteFile(filename string, data []byte, perm os.FileMode) error
此函数会把data参数指定的数据一次性写入文件。要注意的是,同一个文件如果要分多次写入,就不能调用WriteFile
函数。因为该函数每次调用时都会将现有的文件内容清空。例如下面的例子,调用了三次WriteFile
函数向demo.txt
文件写入内容,执行完代码后,打开demo.txt
文件查看,其内容只有“KLMN”
,前两次写入的内容都会被清空。
ioutil.WriteFile("demo.txt", []byte("ABCD"), 0644)
ioutil.WriteFile("demo.txt", []byte("OPQRS"), 0644)
ioutil.WriteFile("demo.txt", []byte("KLMN"), 0644)
下面的示例使用WriteFile函数向文件写入若干字节,然后使用ReadFile函数将文件内容读出来。
var bts = []byte{0xa6, 0x57, 0xc5, 0xf3, 0xe9, 0x12}
// 将内容写入文件
ioutil.WriteFile("CKData", bts, 0666)
// 从文件中读取内容
var cnt, _ = ioutil.ReadFile("CKData")
fmt.Printf("从文件中读到的内容:% #x\n", cnt)
屏幕输出的文件内容如下:
从文件中读到的内容:0xa657c5f3e912
17.5 临时文件
在io/ioutil包中有两个与临时文件有关的函数:
func TempDir(dir, pattern string) (name string, err error)
func TempFile(dir, pattern string) (f *os.File, err error)
TempDir
函数调用成功后会在dir
参数指定的目录中创建临时目录,并返回临时目录的路径;TempFile
函数则会在dir
参数指定的目录下创建临时文件,然后返回可用于读写临时文件的File
对象。
pattern
参数用于指定临时文件(或临时目录)名称的生成方式,有两种格式可选:
- 在
pattern
参数所指定的字符串后面加上随机整数。例如,pattern
参数的值为“test”
,就会生成形如“test342543727”
的文件名。 - 如果pattern参数中出现“*”(星号)字符,那么会把最后一个“*”(替换为随机整数。例如,
pattern
参数为“tmp-*-dir”
,就会生成形如“tmp-846674147-dir”
的名称。
不管是临时文件还是临时目录,其用途都是临时的数据读写,因此,开发人员在编写程序代码时应当注意,当不再需要临时文件(或临时目录)时要将其删除——这项工作不应该留给用户去完成。
在下面的示例中,先调用TempFile
创建临时文件,再向临时文件中写入数据。在程序退出前删除临时文件。
// 创建新目录
os.Mkdir("tmp", 0700)
// 创建临时文件
tmpfile, err := ioutil.TempFile("./tmp", "Demo*.tp")
if err != nil {fmt.Println(err)return
}
fmt.Printf("临时文件%s创建完成\n", tmpfile.Name())
tmpfile.WriteString("abc")
tmpfile.Close()
fmt.Print("按任意键继续……")
fmt.Scanln()
// 删除文件
os.Remove(tmpfile.Name())
临时文件创建后,写入了字符串“abc”。随后,程序代码遇到fmt.Scanln
函数会暂停(等待输入)。接着按下回车键,程序继续运行,在退出之前调用了Remove
函数,临时文件被删除。
17.6 更改程序的工作目录
应用程序在使用相对路径读写文件时,默认会以工作目录为基准,即文件或子目录会存放到工作目录下。程序在启动时,会把程序可执行文件所在的目录设置为工作目录。在程序代码中,可以调用os.Chdir
函数来改变工作目录。
下面的代码首先调用Mkdir
函数在程序的当前目录下创建名为data的子目录,然后将data目录设置为工作目录。
err := os.Mkdir("data", 0700)
if os.IsNotExist(err) {return
}// 改变工作目录
err = os.Chdir("data")
if err != nil {fmt.Println(err)return
}
使用相对路径创建名为list
的文件,并写入数据。
file, err := os.Create("list")
if err != nil {fmt.Println(err)return
}
// 写入测试内容
file.WriteString("demo")
// 关闭文件
file.Close()
此时list
文件会存放在data
目录下,因为它被设置为工作目录。
【思考】
- 创建文件“abc.data”,并写入字符串“Hello, Go”。
- 如何创建和删除目录?
【答案】
- 创建文件“abc.data”,并写入字符串“Hello, Go”
package main
import ("fmt""os"
)func main() {// 创建文件file, err := os.Create("abc.data")if err != nil {fmt.Println("创建文件时出错:", err)return}// 确保文件在函数结束时关闭defer file.Close()// 写入字符串_, err = file.WriteString("Hello, Go")if err != nil {fmt.Println("写入文件时出错:", err)return}fmt.Println("文件写入成功")
}
代码解释
os.Create("abc.data")
:创建一个名为 “abc.data” 的文件,若文件已存在则会将其截断。file.WriteString("Hello, Go")
:向文件里写入字符串 “Hello, Go”。defer file.Close()
:保证文件在函数结束时关闭。
- 如何创建和删除目录?
package mainimport ("fmt""os"
)func main() {// 创建目录dirName := "testDir"err := os.Mkdir(dirName, 0755)if err != nil {fmt.Println("创建目录时出错:", err)return}fmt.Println("目录创建成功")// 删除目录err = os.Remove(dirName)if err != nil {fmt.Println("删除目录时出错:", err)return}fmt.Println("目录删除成功")
}
代码解释
os.Mkdir(dirName, 0755)
:创建一个名为testDir
的目录,权限为 0755。os.Remove(dirName)
:删除名为testDir
的目录。需要注意,若目录不为空,os.Remove
会报错,你可以使用os.RemoveAll
来删除非空目录。