实现功能
- 实现ls 命令功能-l 、 -a 、 -a 和–all 、-h选项
- 显示路径下的文件列表
- -a 和–all 显示包含 “.”'开头的文件
- -l 显示详细列表显示
- -h 和 -l 配合, 个性化显示文件大小, 如1K, 1G, 1T,等, 可以按照1G = 1000M处理
- 类型字符
- c 字符
- d 目录
- 普通文件
- l 软连接
- b块设备
- s socket文件
- p pipe文件, 即FIFO
按照文件名排序输出, 要求详细列表显示时, 时间可以按照"年-月-日 时:分:秒"格式显示, 如下图
| -rw-rw-r– | 1 | python | python | 5 | 2019 - 5 - 7 00:07:00 | test.txt |
|---|---|---|---|---|---|---|
| mode | 硬链接 | 属主 | 属组 | 字节 | 时间 | 文件名 |
解决这个问题之前先了解下argparse 模块做起来更轻松
argparse 模块
- 从3.2 版本开始Python 提供了参数分析的功能模块 - argparse, 解决给程序传递参数的问题
参数分类
一个可执行文件或者脚本都可以接收参数
$ ls -l /etc
# /etc 是位置参数
# -l 是短选项
- 位置参数 : 参数放在那里就要对应一个参数位置
- 选项参数 : 必须通过前面是 "-“的短选项或者”–"的长选项, 后面的才算该选项的参数, 当然选项后面也可以没有参数
上例中, /etc 对应的就是位置参数, -l 是选项参数
基本解析
import argparse
parser = argparse.ArgumentParser() # 获取参数解析器
args = parser.parse_args() # 分析参数
parser.print_help() # 打印帮助
运行结果
usage: Python.py [-h]
optional arguments:
-h, --help show this help message and exit
- argparse 不仅仅做了参数的定义和解析, 还自动生成了帮助信息尤其是usage , 可以看到现在定义的参数是否是自己想要的
解析器的参数
| 参数名称 | 说明 |
|---|---|
| prog | 程序的名字, 缺省使用sys.argv[0] 的basename |
| add_help | 自动为解析器增加-h 和 --help 选项, 默认True |
| description | 为程序增加描述 |
import argparse
parser = argparse.ArgumentParser(prog='ls', add_help=True, description='list directory contents')
args = parser.parse_args()
parser.print_help()
运行结果
usage: ls [-h]
list directory contents
optional arguments:
-h, --help show this help message and exit
感觉稍微有那么一点样子了 …
位置参数解析
考虑到ls 基本功能是解决目录内容打印, 打印的时候应该指定目录的路径, 需要位置参数
import argparse
parser = argparse.ArgumentParser(prog='ls', add_help=True, description='list directory contents') # 获取参数解析器
parser.add_argument('path')
args = parser.parse_args() # 分析参数
parser.print_help() # 打印帮助
运行结果
usage: ls [-h] path
ls: error: the following arguments are required: path
- 这里拿到的结果出现了错误, 但这个错误正好告诉我们需要输入path 对应的位置参数
- " ls [-h] path " 中-h 为帮助选项,可有可无, path 为位置参数, 必须提供, 下面我们解决传参问题 …
传参
parse_args(args=None, namespace=None)
args为参数列表, 是一个可迭代对象, 内部会把可迭代对象转成list . 如果为None 则使用命令行传入参数, 非None 则使用args 参数的可迭代对象
import argparse
parser = argparse.ArgumentParser(prog='ls', add_help=True, description='list directory contents') # 获取参数解析器
parser.add_argument('path') # 位置参数输入
args = parser.parse_args(('/etc', )) # 分析参数, 同时传入可迭代参数
print(args, args.path) # 打印名词空间收集的参数
parser.print_help() # 打印帮助
运行结果
Namespace(path='/etc') /etc
usage: ls [-h] path
list directory contents
positional arguments:
path
optional arguments:
-h, --help show this help message and exit
- Namespace(path=’/etc) 里面的path参数储存在了一个Namespace对象的属性上, 可以通过Namespace对象属性访问, 例如args.path
非必须位置参数
上面的代码必须输入位置参数, 否则会报错
usage: ls [-h] path
ls: error: the following arguments are required: path
有时候, ls 命令不输入任何路径的话就表示列出当前目录的文件列表
import argparse
parser = argparse.ArgumentParser(prog='ls', add_help=True, description='list directory contents') # 获取参数解析器
parser.add_argument('path', nargs='?', default='.', help='path help') # 增加缺省值路径, 帮助
args = parser.parse_args() # 分析参数, 同时传入可迭代参数
print(args, args.path) # 打印名词空间收集的参数
parser.print_help() # 打印帮助
运行结果
Namespace(path='.') .
usage: ls [-h] [path]
list directory contents
positional arguments:
path path help
optional arguments:
-h, --help show this help message and exit
可以看出在此处path 也变成可选的位置参数, 没有提供参数就使用缺省值"." 表示当前路径
-
help 表示帮助文档中这个参数的描述
-
nargs 表示这个参数接收结果参数
- ? 表示可有可无
- + 表示至少一个
- * 可以是任意个
- 数字表示必须是指定数目个
-
default 表示如果不提供该参数就使用这个值, 一般会配合 ? 、* 使用
选项参数
-l 的实现
- parser.add_argument(’-l’) 就增加了选项参数, 参数定义为 ls [-h] [-l L] [path] , 和我们想要得到的有点出入, 我们期望的是 [-l] 不带 L选项的
- nargs能解决吗 ? nargs=? 还是 nargs=0 ?
得到两次运行结果 :
Namespace(l='.', path='.') .
usage: ls [-h] [-l [L]] [path]
list directory contents
positional arguments:
path path help
optional arguments:
-h, --help show this help message and exit
-l [L] path help
raise ValueError("length of metavar tuple does not match nargs")
ValueError: length of metavar tuple does not match nargs
- 根据运行结果得出nargs 是控制位置参数和选项参数的,并不能解决这里 -l 的问题, 为了解决这个问题引出了action 参数 …
- parser.add_argument(’-l’, action=‘store_ture’)
import argparse
parser = argparse.ArgumentParser(prog='ls', add_help=True, description='list directory contents') # 获取参数解析器
parser.add_argument('path', nargs='?', default='.', help='path help') # 增加缺省值路径, 帮助
parser.add_argument('-l', action='store_true')
args = parser.parse_args() # 分析参数, 同时传入可迭代参数
print(args)
parser.print_help() # 打印帮助
运行结果
Namespace(l=False, path='.')
usage: ls [-h] [-l] [path]
list directory contents
positional arguments:
path path help
optional arguments:
-h, --help show this help message and exit
-l
- 这里已经解决了上面-l 选项传参的问题
- ls -l 得到Namespace(l=True, path=’.’), 提供-l 值是True
- ls得到Namespace(l=False, path=’.’), 提供-l 值是False
- parser.add_argument(’-l’, action=‘store_const’, const=20), 表示如果提供-l 选项则对应的值是20, 如果不提供, 对应值是None
-a 的实现
- parser.add_argument(’-a’, ‘–all’, action=‘store_ture’)
- 长短选项可以同时给出, 与上面-l 类似
无参数传入运行
import argparse
parser = argparse.ArgumentParser(prog='ls', add_help=True, description='list directory contents')
parser.add_argument('path', nargs='?', default='.', help='dictory')
parser.add_argument('-l', action='store_true', help='use a long listing format')
parser.add_argument('-a', '--all', action='store_true', help='show all files, do not ignore entries starting with.')
args = parser.parse_args()
print(args)
parser.print_help()
运行结果
Namespace(all=False, l=False, path='.')
usage: ls [-h] [-l] [-a] [path]
list directory contents
positional arguments:
path dictory
optional arguments:
-h, --help show this help message and exit
-l use a long listing format
-a, --all show all files, do not ignore entries starting with.
带参传入运行
import argparse
parser = argparse.ArgumentParser(prog='ls', add_help=True, description='list directory contents')
parser.add_argument('path', nargs='?', default='.', help='dictory')
parser.add_argument('-l', action='store_true', help='use a long listing format')
parser.add_argument('-a', '--all', action='store_true', help='show all files, do not ignore entries starting with.')
args = parser.parse_args(('-l', '-a', '/tmp'))
print(args)
parser.print_help()
运行结果
Namespace(all=True, l=True, path='/tmp')
usage: ls [-h] [-l] [-a] [path]
list directory contents
positional arguments:
path dictory
optional arguments:
-h, --help show this help message and exit
-l use a long listing format
-a, --all show all files, do not ignore entries starting with.
ls 业务功能的实现
-
到目前为止, 已经解决了参数的定义和传参的问题, 下面解决下面问题
- 列出指定路径文件, 默认不递归
- - a 显示所有文件, 包括隐藏文件
- -l 详细列表模式显示
功能代码实现
def listdir(path, all=False):
"""列出本目录文件"""
p = Path(path)
# for f in p.iterdir():
# if not all and f.name.startswith('.'):
# continue
# yield f.name
# yield from filter(lambda f : all or not f.name.startswith('.'), p.iterdir())
yield from map(str, filter(lambda f : all or not f.name.startswith('.'), p.iterdir()))
def _getfiletype(f:Path):
"""获取文件类型"""
if f.is_dir():
return 'd'
elif f.is_block_device():
return 'b'
elif f.is_char_device():
return 'c'
elif f.is_socket():
return 's'
elif f.is_symlink():
return 'l'
else:
return '-'
def listdirdetail(path, all=False):
"""详细列出本目录"""
p = Path(path)
for f in p.iterdir():
if not all and f.name.startswith('.'):
continue
stat = f.stat()
t = _getfiletype(f)
mode = oct(stat.st_mode)[-3:]
mtime = datetime.fromtimestamp(stat.st_mtime).strftime('%Y %m %d %H:%M:%S')
yield (t, mode, stat.st_nlink, stat.st_uid, stat.st_gid, stat.st_size, mtime, f.name)
modelist = dict(zip(range(9), ['r', 'w', 'x', 'r', 'w', 'x', 'r', 'w', 'x']))
# modelist = list('rwx'*3)
def _getmodestr(mode:int):
# 二进制位移
mstr = ''
for i in range(8, -1, -1):
mstr += modelist[8-i] if mode >> i & 1 else '-'
# mstr = ''
# for i, v in enumerate('{:09b}'.format(mode)[-9:]):
# mstr += modelist[i] if v == '1' else '-'
return mstr
合并列出文件的两个函数
listdirdetail 和 listdir 几乎一样, 太多重复, 考虑合并
def listdir(path, all=False, detail=False):
"""合并后"""
p = Path(path)
for i in p.iterdir():
if not all and i.name.startswith('.'):
continue
if not detail:
yield (i.name,)
else:
stat = i.stat()
mode = _getfiletype(i) + _getmodestr(stat.st_mode)
mtime = datetime.fromtimestamp(stat.st_mtime).strftime('%Y %m %d %H:%M:%S')
yield (mode, stat.st_nlink, stat.st_uid, stat.st_gid, stat.st_size, mtime, i.name)
排序
ls 的显示是把文件名按照升序排序输出
sorted(listdir(args.path, detail=True), key=lambda x: x[-1])
-h 的实现
-h , --human-readable, 如果-l 存在, -h 有效
- 增加选项参数
parser.add_argument('-h', '--human-readable', action='store_true', help='with -l, print sizes in human readable format ')
- 增加一个函数, 解决单位转换
def _gethuman(size:int):
units = ' KMGTP'
depth = 0
while size > 1000 and depth + 1 < len(units): # size大于1000且depth不是最后一个
size = size // 1000 # 若按照1024处理, 整除1024即可
depth += 1
return "{}{}".format(size, units[depth])
- 在-l 逻辑部分增加处理
size = stat.st_size if not human else _gethuman(stat.st_size)
改进mode 的方法
import stat
from pathlib import Path
stat.filemode(Path().stat().st_mode)
其他的完善
uid、gid 的转换
- pwd 模块, Thepassword database, 提供访问Linux 、Unix 的password文件的方式, windows下没有.
- pwd.getpwuid(Path().stat.st_uid).pw_name, grp模块, Linux、Unix 获取信息的模块, windows没有
- grp.getgrgid(Path().stat().st_gid).gr_name, pathlib 模块, Path().group()也可以, 本质上它们就是调用pwd模块和grp模块
- 由于windows 不支持这里不加入uid、gid的转换
最终代码
import stat
import argparse
from pathlib import Path
from datetime import datetime
parser = argparse.ArgumentParser(prog='ls', add_help=False, description='list directory contents')
parser.add_argument('path', nargs='?', default='.', help='dictory')
parser.add_argument('-l', action='store_true', help='use a long listing format')
parser.add_argument('-a', '--all', action='store_true', help='show all files, do not ignore entries starting with.')
parser.add_argument('-h', '--human-readable', action='store_true', help='with -l, print sizes in human readable format ', dest='human') # dest改变属性姓名
def listdir(path, all=False, detail=False, human=False):
def _gethuman(size:int):
units = ' KMGTP'
depth = 0
while size > 1000 and depth + 1 < len(units):
size = size // 1000
depth += 1
return "{}{}".format(size, units[depth])
def _listdir(path, all=False, detial=False, human=False):
p = Path(path)
for i in p.iterdir():
if not all and i.name.startswith('.'):
continue
if not detail:
yield (i.name,)
else:
st = i.stat()
# mode = _getfiletype(i) + _getmodestr(stat.st_mode)
mode = stat.filemode(st.st_mode)
mtime = datetime.fromtimestamp(st.st_mtime).strftime('%Y %m %d %H:%M:%S')
yield (mode, st.st_nlink, st.st_uid, st.st_gid, st.st_size, mtime, i.name)
yield from sorted(_listdir(path, all, detail, human), key=lambda x: x[-1])
if __name__ == '__main__':
args = parser.parse_args()
print(args)
parser.print_help()
files = listdir(args.path, args.all, args.l, args.human)
print(list(files))
运行结果
Namespace(all=True, human=True, l=True, path='.')
usage: ls [-l] [-a] [-h] [path]
list directory contents
positional arguments:
path dictory
optional arguments:
-l use a long listing format
-a, --all show all files, do not ignore entries starting with.
-h, --human-readable with -l, print sizes in human readable format
[('-rw-rw-rw-', 1, 0, 0, 2578, '2019 05 08 00:57:19', '*****.py'), ('-rw-rw-rw-', 1, 0, 0, 1804, '2019 05 06 15:20:51', '******.py'), ('-rw-rw-rw-', 1, 0, 0, 421, '2019 05 07 23:32:55', 'Python.py')]
测试
$ python xxx.py -lah
也可以在RUN -> Run / Debug Configurations -> Python
找到Paramters栏输入参数
本文详细介绍了如何使用Python的argparse模块来实现类似Linux中的ls命令,包括-l、-a、-h等选项,展示了如何处理位置参数、选项参数,以及实现ls命令的各种功能,如显示详细列表、显示所有文件、人性化显示文件大小等。

被折叠的 条评论
为什么被折叠?



