文件操作
一,文件的读写操作
1. I/O操作的概述
I/O在是指Input/Output,也就是Stream(流)的输入和输出。程序运行时,数据都是在内存中驻留,由CPU这个超快的计算核心来执行,涉及到数据交换的地方(通常是磁盘、网络操作)就需要IO接口。而操作系统屏蔽了底层硬件,向上提供通用接口。因此,操作I/O的能力是由操作系统的提供的,每一种编程语言都会把操作系统提供的低级C接口封装起来供开发者使用
2. 文件读写实现原理(文件描述符)
由于操作I/O的能力是由操作系统提供的,而操作系统不允许普通程序直接操作磁盘,所以读写文件时需要请求操作系统打开一个对象(通常被称为文件描述符–file descriptor, 简称fd),文件描述符在形式上是一个非负整数,实际上,它相当于一个指针,是一个用于指代被打开的文件索引值,所有执行i/o操作的系统调用都是通过文件描述符完成的,具体操作如下图:
文件操作时,在linux内核中通常会有个task_struct结构体来维护进程相关的表,叫进程控制块,这个块里面会有指针指向file_struct的结构体,称为文件描述表,文件描述符就是这个表的索引。
- 然后这个file_struct会指向一个file的结构体。
- file 有几个重要的结构体成员分别实现不同的功能。
file_operation: 这个指向的文件操作指针,file_operation里面包含了对文件 操作的内核函数指针,他指向内核操作函数,对文件进行操作。
dentry:目录项,它指向带有文件路径的dentry结构体指针 - dentry这个结构体会指向 inode,
- inode 保存着从磁盘inode读上来的信息,还有两个重要成员:
inode_opertions:它指向文件操作的内核函数,告诉内核文件能做那些操作
super_block: 保存着从磁盘分区的超级块读上来的信息,它还有个成员指向dentry,得到文件根目录被mount 到哪里的信息
简单总结:
-
每一个文件描述符会与一个打开文件相对应,同时,不同的文件描述符也会指向同一个文件。相同的文件可以被不同的进程打开也可以在同一个进程中被多次打开。系统为每一个进程维护了一个文件描述符表,该表的值都是从0开始的,所以在不同的进程中你会看到相同的文件描述符,这种情况下相同文件描述符有可能指向同一个文件,也有可能指向不同的文件
-
文件描述符默认1024个,所以最多只能同时打开1024个文件。
3 文件的打开与读取
3.1 文件的打开
单纯使用 open() 方式打开文件,如果在读写过程中出错会直接返回 IOError,从而没有关闭文件,造成资源占用。因此,无论读文件还是写文件,都需要再最后关闭文件流,为了避免麻烦,可以使用with方法,with语句会自动帮我们调用close方法.
with open('/path/to/file', 'r') as f:
print(f.read())
3.2 文件的打开模式
文件打开模式 | 描述 |
---|---|
r | 以只读模式打开文件,并将文件指针指向文件头;如果文件不存在会报错 |
w | 以只写模式打开文件,并将文件指针指向文件头;如果文件存在则将其内容清空,如果文件不存在则创建 |
a | 以只追加可写模式打开文件,并将文件指针指向文件尾部;如果文件不存在则创建 |
x | 创建一个新文件只写,如果文件存在则异常 |
+ | 补全读写功能;如r+ 是在r 的基础上增加了可写功能 |
b | 读写二进制文件(默认是t,表示文本),需要与上面几种模式搭配使用,如ab,wb, ab, ab+ |
注意:文件的操作一定要注意指针的位置,这点在下面代码中会体现
- 虽然r+, w+, a+ 都是读写功能,但是r+ 是覆盖写,w+ 是清空后再写, a+ 是追加写
- open()以a+模式开启了一个附加读写模式的文件,由于是a,所以指针在文件末尾。此时如果做read(),则Python发现指针位置就是EOF,读取到空字符串。
解决方法:读取之前将指针重置为文件头
3.3 文件的读取方式
-
read(): 使用read 方法一次性读取所有的数据使用,包括换行符。只能在文件不大的情况下使用。
-
readline(): 使用readline 方法每次只读取一行数据,包括换行符
-
readlines(): 使用readlines 方法一次性读完所有数据,包括换行符,并以列表的形式返回,列表中的每个元素就是原文件的一行。
4. 几个使用文件读取的代码
- 将程序参数和结果直接缓存,写入文件,下次调用直接打开文件调取缓存
import json
import time
def cacheRead(): #打开文件将文件中缓存读出
try:
with open("text.txt", "r") as f:
if not f.read():
d = {}
else:
f.seek(0)
d = json.load(f)
except FileNotFoundError: #文件不存在会报错
d = {}
return d
d =cacheRead()
def cache(fn):
def inner(*args):
if str(args) in d.keys(): #如果缓存中存在,直接使用
return d[str(args)]
else:
a = fn(*args) #如果不存在调用函数,再把这次的调用写入缓存
d[str(args)]=a
with open("tpt.txt", "w") as f: #将缓存写入文件
json.dump(d, f)
return a
return inner
@cache
def add(x,y,z=3):
time.sleep(z)
return x+y
print(add(5,5))
print(add(4.0,5))
print(add(4,6))
上面代码打开文件的方式是 r,所以如果是第一次打开文件,而文件不存在就会报错,所以要使用一个 try 保证程序不会中断。 我们也可以使用 a+ 方式打开,就可以省去外层的 try,打开文件函数如下,注意a 方式打开文件的指针位置。
def cacheRead():
with open("tpt.txt", mode="a+") as f: # a方式打开,所以指针跑到文件尾部,此时空白不能json,所以要指针复位
f.seek(0)
if not f.read():
d = {}
else:
f.seek(0) # read 判断后指针跑到了后边,所以复位
d = json.load(f)
return d
2.Linux 中过滤掉注释行和空行查看文件的小工具
import argparse,sys
parser = argparse._ArgumentGroup(prog="ck",add_help=True,\
description="this cammand is used to help your extract configgruation")
parser.add_argument("file",nargs="?",default="httpd.conf",help="you need to input your app config file")
parser.add_argument("-p","--port",action="store_true",help="check app liston port")
parser.add_argument("-i","--ip",action="store_true",help="check app liston IP")
parser.add_argument("-a","--all",action="store_true",help="check app liston configuration")
parser.add_argument("-n","--name",action="store_true",help="check app liston name") #可选参数
args = parser.parse_args()
def Readfile():
try:
with open(args.file,"r") as f: #打开文件并过滤注释和空行
lines = f.readlines()
lines = [lines[i-1].strip("\n")+"|"+str(i)+"\n" for i in range(1,len(lines)+1)] #在换行符前边加|和序号
lines = filter(lambda x: not x.startswith("#"),lines)
lines = filter(lambda x: not x.startswith("\n"),lines)
return list(lines)
except Exception as err:
print(err)
def displaty(lines,key,notice):
flag = False
for line in lines:
if line.upper().startswith(key):
lstr = line.split("|") #用| 分割成原行和序号
print("\033[33;40m{}\033[0m".format(lstr[1].strip("\n")),end=" ") #将序号写到前边
print(lstr[0])
flag = True
if not flag:
print(notice)
def Getip():
displaty(lines,"IP","Having no IP bind!")
def Getall():
for line in lines:
lstr = line.split("|")
print("\033[33;40m{}\033[0m".format(lstr[1].strip("\n")), end=" ")
print(lstr[0])
def Getport():
displaty(lines,"PORT","Having no port bind")
def Getname():
displaty(lines,"NAME","Having no name bind")
def main():
if args.ip and args.port:
Getip()
Getport()
sys.exit()
if args.all:
Getall()
if args.ip:
Getip()
if args.port:
Getport()
if args.name:
Getname()
lines = Readfile()
main()
二, 文件的路径操作
1. 模块介绍
在python对路径的操作中,在python3.4之前版本需要使用os.path模块,返回的都是字符串;python3.4版本开始使用pathlib模块,提供Path对象来操作,包括目录和文件. 本篇主要介绍pathlib库的使用。
语句 | 作用 |
---|---|
from pathlib import | 调用库 |
Path.cwd() | 获取当前路径 |
Path.stat() | 获取当前文件的信息 |
Path.exists() | 判断当前路径是否是文件或者文件夹 |
Path.glob(pattern) | 获取路径下的所有符合pattern的文件,返回一个generator |
Path.is_dir() | 判断该路径是否是文件夹 |
Path.is_file() | 判断该路径是否是文件 |
Path.iterdir() | 当path为文件夹时,通过yield产生path文件夹下的所有文件、文件夹路径的迭代器 |
Path.mkdir(mode=0o777,parents=Fasle) | 根据路径创建文件夹;parents=True时,会依次创建路径中间缺少的文件夹 |
Path.rename(target) | 当target是string时,重命名文件或文件夹;当target是Path时,重命名并移动文件或文件夹 |
Path.parents() | 获取path的所有上级路径 |
Path.is_absolute() | 判断path是否是绝对路径 |
Path.name | 获取path文件名 |
Path.suffix | 获取path文件后缀 |
Path.rmdir() | 当path为空文件夹的时候,删除该文件夹 |
2.通过几个代码对路径操作做详细说明
1.创建路径和文件
#!/usr/bin/python3
from pathlib import Path
import shutil
def CreatFile(ph,*args):
path = Path(ph)
# path.mkdir(mode=0o777,parents=True) #创建路径用,路径已经存在时需要注释掉
for p in path.parents:
if str(p) == ".":
break
name =(p.name + p.name + x for x in [x for x in args]) #创建cc.html等文件名
paths = [p/Path(x) for x in name] #p/x也可以
for p in paths:
p.touch(mode=0o777,exist_ok=True)
CreatFile("a/b/c/d",".index",".txt",".json",".html")
2.指定一个源文件,实现copy到目标目录
from pathlib import Path
def copyfile(sfile,dname): #文件名和目标目录
pathfile0 = Path(sfile)
pathfile1 = Path(dname)
pathdir = pathfile1.parent #判断父目录是否是目录
if pathfile0.is_file():
with open(sfile,"r") as f:
s = f.read()
if pathdir.is_dir():
with open(dname,"w+") as fw:
fw.write(s)
print("ok")
else:
print("error")
copyfile("3","a/b/3.txt")
3.传入一个路径,根据配置参数过滤,统计有效行数,然后写入日志
import argparse,sys
import logging
from pathlib import Path
import json
parser = argparse.ArgumentParser(prog="tjcode",add_help=True,\
description="this cammand is used to help your count lines")
parser.add_argument("file",nargs="?",default="/home/zhang/a",help="you need to input which is uesed to count")
parser.add_argument("-c","--count",action="store_true",help="use to count") #可选参数
parser.add_argument("-i","--ignore",action="store_true",help="count the file, but need ignore starts with '#' and so on")
def logger(logname): #日志功能
logger = logging.getLogger("tjcode")
logger.setLevel(logging.INFO) #日志记录级别
handler = logging.FileHandler(logname,mode= "a")
formater = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
handler.setFormatter(formater)
logger.addHandler(handler)
return logger
def Countlines(path,count): #传入路经
path = Path(path)
if not path.exists(): #判断当前目录是否正确
print("Your path is not exists, Please chenge path ")
sys.exit()
with open("tjcode.conf","r") as f: #读配置文件
s = "".join(list(filter(lambda x: not x.startswith("#"),f.readlines()))) #过滤掉配置文件的注释
d = json.loads(s)
if path.is_file(): # 如果传入的就是文件就直接统计
with open(path, "r") as ff:
lines = ff.readlines()
for k, v in d.items():
lines = list(filter(lambda x: not x.startswith(v), lines))
count += len(list(lines))
logger.info("path:{} count:{}".format(str(path), count))
return count
for p in path.iterdir(): #产生path文件夹下的所有文件,文件夹路径的迭代器
if p.is_file(): #判断该路径是否是文件
with open(p,"r") as F:
lines = F.readlines()
for k,v in d.items(): #按配置文件过滤
lines = list(filter(lambda x: not x.startswith(v), lines))
count += len(list(lines))
logger.info("path:{} count:{}".format(str(p), count)) # 写入日志
if p.is_dir(): # 判断是否是文件夹
count += Countlines(p, 0) # 递归去统计文件夹下行数
return count
args = parser.parse_args()
logger = logger("tjcode.log")
if args.file:
try:
count = Countlines(args.file, 0)
print("The total numlines is {}".format(count))
except Exception as err:
logger.info(err)
print("Having err:you kan chenge logfile")