起因:最近我在用Python做了一个随机大小写的脚本,由于第一次涉及命令行程序的编写,走了很多弯路,并没有使用 argparse 的这样nb的第三方库,就自己撸了一个参数解析,好在最后完成了!

项目地址:up-low-letter

如果喜欢喵,给个star吧喵(战术去世.jpg)

注意:本人对Python的理解不深,这次刚完成一个项目,感想颇多,写一个文章总结一下做个笔记,方便以后复习,如果出现错误,大佬请轻喷,欢迎在评论区指正,谢谢!

可以argparse这样的大的第三方库,不用这样搞。这里是因为走了弯路,就把思路写出来了

如果你正在学argparse等高级东西,请绕行,这篇文章在浪费你的时间

argv的使用

大佬可以直接跳到下一段落

sys模块是与python解释器交互的一个接口。sys 模块提供了许多函数和变量来处理 Python 运行时环境的不同部分。

首先,请导入模块(废话)

1
import sys

在python脚本被执行的时候,sys.argv列表中就会存着运行时的参数,如:

1
['test.py','arg1','arg2','arg3']

由此观之:

sys.argv[0] 表示程序自身的名称
sys.argv[1] 表示程序的第一个参数
sys.argv[2] 表示程序的第二个参数

网上流传过这样一个事:

一个用爱发电的免费加速器作者,发现有奸商把他的软件加了壳去某宝上卖,他把软件加壳、资源混淆搞了一路十三招,还是有人拿去卖,最后没有办法直接连文件名改掉都不让运行。在这里,我们也可以达到这样的效果:

1
2
3
4
5
6
7
8
9
10
11
import sys

if sys.argv[0] != 'test.py':
print(f'你下到假传奇了:{sys.argv[0]}')
sys.exit(0) # 终止程序
else:
print(f'恭喜你,你下到了真传奇:{sys.argv[0]}')


print('汤碗拦月loding......')
print('大扎好,我系轱天乐,我四渣渣辉,汤碗拦月,介四里没有挽过的船新版本,挤需体验三番钟,里造会干我一样,爱象节款游戏。')

运行结果:

1
2
3
4
5
6
7
8
9
------------名字为test.py------------
$ python test.py
恭喜你,你下到了真传奇:test.py
汤碗拦月loding......
大扎好,我系轱天乐,我四渣渣辉,汤碗拦月,介四里没有挽过的船新版本,挤需体验三番钟,里造会干我一样,爱象节款游戏。
------------名字为flase.py------------
$ mv test.py flase.py #linux的移动命令,这里在改名
$ python flase.py
你下到假传奇了:flase.py

好样的,我们已经帮助用户判断是否他的传奇是真传奇了,是不是成就感满满?(逃)

实战

我们现在已经初步了解了argv的用法了,现在开始实战:

我们要做一个类似cat命令的功能,预计的输出:

1
2
3
4
5
$ ls
flag.txt
$ python cat.py flag.txt
Hi,here!
There is't a flag,Find it in the folder root

分析

从输出可以看出,程序直接读取运行脚本时的第一个参数(继文件名的第一个参数,sys.argv的第二个参数)作为路径读取文件并输出

实现

1
2
3
4
5
6
import sys

path = sys.argv[1]
with open(path, 'r') as f:
cont = f.read()
print(cont)

使用argv来枚举

我们得到了argv这个大杀器,现在我们可以搞点事了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import sys

def gethelp():
print("帮助信息\nNO INFO")
def example1(parm):
print(f'ex1:{parm}')
def example2(parm):
print(f'ex2:{parm}')
if len(sys.argv) == 1:
gethelp()
target = sys.argv[1]
if target == '-h':
gethelp() #获取帮助,需要在前面定义函数
elif target == '-ex1':
example1(sys.argv[2]) #同样是前面定义的函数,传入参数为命令的第二个参数
elif target == '-ex2':
example2(sys.argv[2])
else:
print(f'No such parm:{sys.argv[2]}')

如果你想写全称,就这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import sys

def gethelp():
print("帮助信息\nNO INFO")
def example1(parm):
print(f'ex1:{parm}')
def example2(parm):
print(f'ex2:{parm}')

if len(sys.argv) == 1:
gethelp()
target = sys.argv[1]
if target == '-h' or target == '--help':
gethelp() #获取帮助,需要在前面定义函数
elif target == '-ex1' or target == '--example1':
example1(sys.argv[2]) #同样是前面定义的函数,传入参数为命令的第二个参数
elif target == '-ex2' or target == '--example2':
example2(sys.argv[2])
else:
print(f'No such parm:{sys.argv[2]}')

那看起来很棒,但会出现问题:

  • 如果你的参数并没有出现在第一的位置,她将报错

    1
    $ python a.py unknown -ex1 114514

    结果:

    1
    No such parm:unknow

    她不会运行,她告诉了使用者出现的问题,但我们希望她忽视这段字符

  • 加入二级参数的判断十分冗杂

    我的项目的v1.0.2版本中加入了-o的参数,我对此犯了难,她的实现将会需要可能的if嵌套,那会使我的代码变得晦涩难懂。最后通过在完成转换后进行判断是否输出才勉强不完美地解决问题。

    相关的issues:Target中的逻辑错误 · Issue #6 · surrtr/up-low-letter (github.com)

那怎么办?

我们尝试使用枚举的方式解决问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
target = sys.argv[1:]

for i in target:
if i.startswith("-"):
key.append(i)

for i in key:
n = sys.argv.index(i)
if i == '-h' or i == '--help':
gethelp() #获取帮助,需要在前面定义函数
elif i == '-ex1' or i == '--example1':
example1(sys.argv[n+1]) #同样是前面定义的函数,传入参数为命令的第二个参数
elif i == '-ex2' or i == '--example2':
example2(sys.argv[n+1])
else:
print(f'No such parm:{i}')

继续优化,把参数放到key_list里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
key_list ={
"-ex1": example1,
"--example1": example1,
"-ex2": example2,
"--example2": example2,
}
target = sys.argv[1:]

for i in target:
if i.startswith("-"):
key.append(i)

for i in key:
n = sys.argv.index(i)
if i == '-h' or i == '--help':
gethelp()
elif i in key_list:
key_list[i](sys.argv[n+1])
else:
print(f'No such parm:{i}')

那么,有人就会问了:为什么把help单独提出来放到前面?那是因为直接放到key_list里,会出现访问下标越界。

当无附加参数的参数(不知道术语)较多的时候,也可以单独列个字典:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
key_list_0 ={
"-h": gethelp,
"--help": gethelp,
}

key_list_1 ={
"-ex1": example1,
"--example1": example1,
"-ex2": example2,
"--example2": example2,
}

target = sys.argv[1:]

for i in target:
if i.startswith("-"):
k = target.index(i)

for i in key:
n = sys.argv.index(i)
if i in key_list_0:
key_list_0[i]()
elif i in key_list_1:
key_list_1[i](sys.argv[n+1])
else:
print(f'No such parm:{i}')

当需要可选参数时,可以使用布尔值来确定是有调用可选参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
key_list_0 ={
"-h": gethelp,
"--help": gethelp,
}

key_list_1 ={
"-ex1": example1,
"--example1": example1,
"-ex2": example2,
"--example2": example2,
}

ex3 = False

target = sys.argv[1:]
for i in target:
if i.startswith("-"):
k = target.index(i)
key.append(i)

for i in key:
n = sys.argv.index(i)
if i in key_list_0:
key_list_0[i]()
if i in key_list_1:
key_list_1[i](sys.argv[n+1])
elif i == '-ex3' or i == '--example3':
ex3 == True
ex3_i = n
else:
print(f'No such parm:{i}')
if ex3:
example3(sys.argv[ex3_i+1])

这样,在调用-ex3--example3时,将会运行example3方法,不使用也不会报错

这里可能会出现问题:当只使用可选参数时,她将出现问题,我们这里给她做一个排除:

1
2
3
4
5
6
7
8
9
key = []
target = sys.argv[1:]
for i in target:
if i.startswith("-"):
key.append(i)
if len(key) < 2:
if key == '-o':
print("Error:不可只调用可选参数")
sys.exit(0)

也可以继续优化,将可选参数列为一个字典,但我并未使用到,就不作提及了。

完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import sys

def gethelp():
print("帮助信息\nNO INFO")
def example1(parm):
print(f'ex1:{parm}')
def example2(parm):
print(f'ex2:{parm}')
def example3(parm):
print(f"\n我是可选参数,ex3:{parm}")

key_list_0 ={
"-h": gethelp,
"--help": gethelp,
}

key_list_1 ={
"-ex1": example1,
"--example1": example1,
"-ex2": example2,
"--example2": example2,
}

if len(sys.argv) == 1:
gethelp()

key = []
ex3 = False

target = sys.argv[1:]

for i in target:
if i.startswith("-"):
key.append(i)

for i in key:
n = sys.argv.index(i)

if i.startswith("-"):
key.append(i)
if len(key) < 2 and '-ex3' in key:
print("Error:不可只调用可选参数")
sys.exit(0)
if i in key_list_0:
key_list_0[i]()

elif i in key_list_1:
key_list_1[i](sys.argv[n+1])
elif i == '-ex3' or i == '--example3':
ex3 = True
ex3_i = n
break
else:
print(f'No such parm:{i}')

if ex3:
example3(sys.argv[ex3_i+1])

总结

不如第三方库,学学思路就行了,不如argparse。