1.1 使用传统方式
使用传统方式可以访问文件和路径,对文本文件和二进制文件进行读写。最常用的函数和命令如下。
Dir:用于列举路径下的文件和子文件夹名称。
GetAttr和SetAttr:获取和设置属性。
FileCopy、Name、MkDir等:对文件和路径复制、移动等。
Open...Write...Close:对文本文件、二进制文件进行打开、读写、关闭。
1.1.1 获取文件或路径的属性
右击文件、文件夹,在弹出菜单中选择属性命令,打开属性窗口后,可以设置只读属性和隐藏属性等。
GetAttr函数用来获取和判断文件或路径的属性,该函数的参数是一个路径字符串,返回值是由多个2的整数幂的组合相加的总和,如表1-1所示。
表1-1 文件、路径的属性常量
这里假定磁盘下的TE.txt文本文件已设置为“只读”并且“隐藏”,如图1-1所示。
图1-1 查看文件属性
此时,GetAttr("C:\temp\abcd\TE.txt")会返回一个整数35。其实,35=32(vbArchive)+2(vbHidden)+1(vbReadOnly)。
因此,把GetAttr函数的计算结果拆分为多个枚举常量值之和,就可以得知该文件的属性。
下面的过程用来把任何一个正整数拆分为多个2的乘方。
运行上述过程,可以看到13被拆分为8+4+1。根据这个思路,可以设计一个用来判断文件是否被设置为只读的自定义函数。
这个函数的原理就是把GetAttr的结果拆分为多个数字,拆分的过程中,看看是否有一个拆分恰好等于枚举常量vbReadOnly,如果有就提前退出函数,返回True。
运行Debug.Print IsReadOnly("C:\temp\abcd\TE.txt"),在立即窗口返回结果True,表明这是一个只读文件。
同理,把上述函数中的ReadOnly这个单词替换为Hidden,就形成了可以判断文件或路径是否设置了隐藏属性。
这里假定C:盘下的Build文件夹被设置了隐藏属性,那么Debug.Print IsHidden("C:\Build\")返回结果True。
下面的函数可以判断一个路径是否为文件夹。
Debug.Print IsDirectory("C:\Build\")返回True。Debug.Print IsDirectory("C:\Build\Hello.csv")返回False。
1.1.2 设置文件或路径的属性
与GetAttr相对应的函数是SetAttr,该函数可以设置文件、路径的属性。
SetAttr "C:\Build\", vbHidden + vbReadOnly
这条代码把Build文件夹的属性设置为只读,并且隐藏。
SetAttr "C:\Build\", vbNormal
这句代码去掉只读和隐藏属性,恢复为正常属性。
1.1.3 判断文件或路径是否存在
使用Dir函数可以列举出当前路径下所有文件和子文件夹的名称,从而间接地判断一个文件或文件夹是否存在。
Dir函数的语法为:
Dir(PathName,Attributes)
PathName是一个用来描述文件、路径的字符串,可以使用*、?通配符。Attributes可以使用如表1-2所示的值。
表1-2 Dir函数用的筛选常量
如果不规定Attributes属性,则默认为vbNormal。
代码分析:如果计算机中不存在Test.txt这个文件,那么Dir函数会返回空字符串;如果文件存在,则返回第一个符合模式的文件名称(不包含所在路径),据此可以判断磁盘或文件夹中是否有某个文件。此外,还可以使用Dir函数判断是否有某磁盘分区,或者是否有某个文件夹。
如果上述过程中的Path赋值为Path="M:"或者Path="M:\",则可以用来判断是否存在M:盘。
如果要判断是否存在某文件夹(路径),结尾必须加反斜杠。例如Dir("C:\build")用来判断C:盘下是否有build这个文件,而Dir("C:\build\")用来判断C盘下是否有build文件夹。
1.1.4 遍历文件和子文件夹
利用Dir函数和不带参数的Dir,可以遍历一个路径下的所有文件和子文件夹的名称。现在假定C:\CTEX文件夹中的内容如图1-2所示。
图1-2 文件夹中的内容
可以看到有7个子文件夹,4个文件。运行如下的过程,打印出所有的子文件夹名称和文件名称。
上述程序的打印结果如图1-3所示。
可以看出,第一行打印出一个小数点,第二行打印出两个小数点,从第三行起才是正式的内容。
如果把代码中的Path = Dir(parent, vbDirectory)修改为Path = Dir(parent),则只遍历文件,不遍历子文件夹。
那么如何只遍历子文件夹呢?这就需要在循环体中加入If语句来选择性地遍历。
图1-3 遍历子文件夹和文件
上述过程中,用集合Col来装载所有的文件和子文件夹的名称,最后,遍历Col的时候,首先过滤出所有的子文件夹,然后排除小数点,最后输出纯粹的子文件夹,共7个,如图1-4所示。
如果要遍历C:\Ctex下面的所有扩展名为.txt的文本文件,代码可以修改为如下形式。
图1-4 只列举子文件夹名称
注意,Dir函数中用到了通配符,*.txt可以匹配所有的文本文件,如图1-5所示。
图1-5 只遍历文本文件
1.1.5 文件的复制、移动和删除
文件的复制、移动和删除操作,分别用FileCopy、Name As和Kill语句。
FileCopy的语法为:
FileCopy Source, Destination
Source表示原文件,Destination表示复制的目标。
例如FileCopy Source:="C:\temp\a.xlsx", Destination:="D:\dist\goal.xlsx",表示把文件C:\temp\a.xls复制到D:\dist文件夹下,并且重命名为goal.xlsx。
文件的移动操作就是文件的剪切,也可以理解为文件的重命名。与复制文件的区别是,原文件不在原位置了。
Name "C:\temp\a.xlsx" As "D:\dist\b.xlsx",就相当于把a文件从原位置剪切到D:\dist文件夹中,并且设置名称为b.xlsx。
注意 针对文件的移动操作,如果D:\dist\下面原先就有一个b.xlsx文件,那么运行上述的Name语句会导致出错。也就是说,必须保证目标文件夹中还没有这个文件,才能进行移动操作。
Kill语句用于删除文件,如果文件处于打开、占用状态,运行该语句会出错。另外,用Kill语句删除掉的文件,不能通过回收站还原,要谨慎操作。
图1-6所示的代码连续两次删除同一个文件,第一句不会出错,但是运行到第二句时弹出“文件未找到”的错误。
图1-6 重复删除同一文件的错误
1.1.6 文件夹的创建和删除
文件夹的创建和删除分别用MkDir和RmDir语句,Mk是Make的缩写,Rm是Remove的缩写。
MkDir语句的语法很简单。
MkDir Path:="C:\temp\2017",会在temp文件夹下创建一个名为2017的文件夹。
RmDir语句用来删除一个空文件夹。
RmDir Path:="C:\temp\picture",表示删除picture文件夹,如果该文件夹不是空的,包含其他的文件和子文件夹,那么RmDir会提示错误,如图1-7所示。
也就是说,要删除一个文件夹,必须先把里面的内容清空后,才能使用RmDir语句删除。
文件夹的重命名也使用Name…As语句。例如Name "C:\temp\picture" As "C:\temp\pic",表示把文件夹picture重命名为pic。
图1-7 文件夹中有内容则不能删除
1.1.7 文本文件的读写
编程过程中,经常需要把程序运行的结果数据保存到文本文件,也需要从文本文件中读取数据供程序使用,这就涉及文本文件的读写操作了。
本节介绍一下用于文件读写的Open语句。
Open语句的语法如下。
Open textFile For mode As fileNum
参数textFile是一个表示文本文件路径的字符串。
参数mode表示Open语句的读写模式,使用最多的模式如下。
Append:追加模式。
Output:擦写模式。
Input:读取模式。
如果要把程序运行的结果输出到文本文件中,那么使用Append模式会把输出结果追加到文件已有内容之后,而使用Output模式,则会先清空文件原先的内容,再写入输出结果。
如果要从文本文件中读取内容,而不破坏文件,可以使用Input模式。
要注意的是,在使用Output或Append模式时,如果计算机中textFile文件不存在,则会自动创建一个文本文件;如果使用Input模式读取一个文本文件,文本文件不存在会导致出错。
参数fileNum是一个文件号,可以是#1到#511中的任何一个。读写文件操作结束后,一定要用Close fileNum关闭文件。
下面讲述一下导出数据到文本文件中的方法。
上述过程把三个字符串写入文本文件中,使用Print语句写入时,在末尾自动换行,如图1-8所示。
使用Print在同一行输出多个字符串时,每个字符串之间用半角分号隔开。
上述程序的运行结果如图1-9所示。
图1-8 向文件写入内容
图1-9 同一行输出多个结果
除了使用Print语句输出外,还可以使用Write语句输出内容到文本文件。
程序的运行结果如图1-10所示。
可以看出文本文件中的内容都带有双引号,这和Print语句有很大不同。
如果把Open "C:\temp\abc.txt" For Output As #1这句中的Output换成Append,则每次写入文件时,不删除文件原有内容。请读者自行测试。
接下来讲述如何从已有文本文件中读取内容,供程序调用。
读入文件内容涉及的常用术语有:
v=Input(c,fileNum),表示从文件当前位置读取c个字符,赋给字符串变量v。
图1-10 使用Write输出内容
Seek fileNum, c,把当前位置重设为c,c的最小值是1。
LOF(fileNum),返回文件的长度,也就是文件中字符总数。
EOF(fileNum),返回一个布尔值,当读取到文件尾部,返回True。经常使用EOF来判断是否读取完成。
现在假设文本文件auto.txt中的内容如图1-11所示。
图1-11 文本文件内容
代码分析:a = Input(1, #1),表示从文件的开头处读取1个字符,赋给a,因此变量a的取值为字符串h。
b = Input(2, #1),表示从上次读取的位置起,读入2个字符赋给变量b,因此b的取值为el。以此类推。
Seek #1, 1表示把读取位置重设为1,也就是文件开头,接下来d = Input(3, #1)表示从文件开头处读取3个字符,因此d的取值为Hel。
上述程序的运行结果如图1-12所示。
图1-12 从文件中读取字符
根据这个特点,可以把文本文件中的所有字符分发到字符串数组中。
代码分析:上述过程,打开文本文件后,根据文件字符总数重新定义数组的上下界,使得数组能恰好容纳文本中的字符,然后使用For循环,遍历文本文件中的每个字符,并分发到数组的每个元素。
运行到Stop那句,通过本地窗口可以清晰地看到数组s的各元素取值情况,如图1-13所示。
图1-13 本地窗口查看数组
可以看出,每个元素恰好取得文件中的一个字符。最后通过Join把数组用*重新连接并输出,如图1-14所示。
图1-14 数组连接为字符串
此外,还可以使用Line Input语句,每次读取一整行。
假设文件b.txt中有4行古诗,下面用Line Input读取内容。
代码分析:本例直接把读取到的每行打印到立即窗口,因此可以使用Do循环,利用EOF函数来判断是否读到文件尾部,如果到了尾部,就结束循环。
上述程序的运行结果如图1-15所示。
图1-15 使用Line Input读取内容
如果要一次性读取文本文件的所有内容,可以使用下面的自定义函数。
运行下面的Test过程,即可把文件中的所有内容打印到立即窗口。
上述代码的源文件为“实例文档01.xlsm”。