最近拖延症和畏难情绪泛滥得厉害,todo 加了一条又一条,忙不过来索性开摆了,哎。工作确实是有那么亿点小忙,工作思路也有点不明确,还得看论文;漏洞复现也是障碍重重,POC 针对的是 HTTP2 我搭建好的环境偏偏是 HTTP1.1,还得学习下 HTTP, HTTP2, HTTP1.1。
现实总是那么骨感。最开始我只想学习 Race Condition,后来研究案例学了下 WebRTC,又看了 ruby, mvc, rails,之后了解到 websocket 也存在条件竞争,就简单看了下 websocket。主要是我基础太差,碰到一个名词总是无法直接给出具体概念,不过我觉得这种学习方法也蛮好的,深度优先,逐渐丰富自己的技术栈;但也有其缺点,容易跑偏,学着学着就不知道学哪去了,不及时总结的话甚至不知道自己学了点啥。
怎么办,我开始迷茫了,呜呜呜 T_T
牢骚到此为止,写完这篇文章起码能划掉一条 todo 了。
hacking for fun
工作中有遇到处理大量 url 的情况,就寻思着写个 shell 脚本,其实脚本去年就写好了,一直拖着没总结,拖延症晚期了我。相较于 Python,Shell 脚本有其天然优势,脚本中的每行代码都相当于是在命令行中执行,这也就允许我们方便地使用大量现有工具,简化实现,快速完成任务。此外,为了增加代码的灵活性,使用位置参数无疑首选方法,你也不想每次该参数都直接修改脚本吧!?But, shell 脚本的位置参数具体如何使用呢?希望这篇文章可以回答你的疑问。
先放几个符号:
Parameter(s) | Description |
---|---|
$0 |
第一个位置参数 |
$1 … $9 |
the argument list elements from 1 to 9 |
${10} … ${N} |
the argument list elements beyond 9 (note the parameter expansion syntax!) |
$* |
指代除了 $0 之外的所有参数 |
$@ |
指代除了 $0 之外的所有参数 |
$# |
指代除了 $0 之外的参数的数量 |
shift
如果将 $1
及之后的参数序列看成一个栈的话,shift
相当于是将 $1
出栈,之前的 $2
就成了新的 $1
,其他参数依次类推。如果仅使用 shift
的话,默认移一位,也可用 shift <n>
移动 n 个参数。
举个栗子:
1 |
|
运行结果如下,$0
是 shell 脚本的第一个参数,一般被设置为脚本的名字,剩下的参数从 $1
到 $4
依次排序。
shift <n>
可以移动 n 个参数,我们尝试一次移动两个。
1 |
|
报错了,不过也是意料之中。
虽然我 shift 2
报错了,但退一万步讲,shift 1
最后一次迭代的 $1
是 “caishao!”,echo
之后再使用 shift
不会报错嘛?
1 |
|
修改下代码,观察最后一次迭代后 $1
的值。
最后一次迭代,先是输出了 “caishao!” 之后 shift 1
,然后输出了一共空值。解释:**$#
的值非负,就不会报错**。最后 shift
之后, 可以把命令看成 sh ./shift1.sh
,只是没传参数罢了,这几行代码也没说非得要个参数不是?没报错也就不难理解了。
再看一个比较复杂的栗子。
1 |
|
经常配环境的小伙伴都知道,调用 bash 脚本(或者说其他命令行工具)的基础语法是 COMMAND [options] <params>
。我们简单跑一下脚本。
shift
之殇:
shift
处理位置参数可太死板了!事无巨细,全是用户自己处理。有一个参数的选项用 shift 2
,没参数的选项用 shift
,甚至没法解释组合使用的选项(如:-fu <USER>
),也没有简单的方式指定那些选项是必需的,参数多了维护起来也费劲。
众所周知,Linux 以“优雅”著称,那么位置参数传递有无“优雅”的解决方案呢? 下面有请 getopts
!!
getopts
getopts
is neither able to parse GNU-style long options (--myoption
) nor XF86-style long options (-myoption
)
getopts
是 shell 的内置命令,也是用来处理命令行参数的。只能解析一个字符的选项,无法解析长选项,如 --myoption
或 -myoption
。
基础语法如下:
1 | getopts OPTSTRING VARNAME [ARGS...] |
工作原理:每次从 OPTSTRING
中读一个选项,选项名称存储在 VARNAME
,如果需要参数,再读取对应参数 ARGS
,参数值存储在 $OPTARG
中。$OPTARG
总是存放下一个待处理的位置参数。简单来讲就是根据相应的语法规则,自动给你加了个 shift [1\2]
移位命令。
我们对上面 shift 部分的代码使用 getopts 进行修改。
1 |
|
运行结果演示:
因为 getopts
每次仅读取一个选项及其参数值,且在遇到第一个非选项的参数(不是以 ‘-‘ 开头的字符串)时,会返回 FALSE,这也使得我们可以使用 while 循环优雅地读取参数。
getopts ":c:hu:" opt;
中的 ":c:hu:"
就是上文提到的 OPTSTRING
。其中 c:
和 u:
表示选项 -c
和 -u
后会跟一个参数,其值存储在内置变量 $OPTARG
。对 OPTSTRING
的解析方式也说明了 getopts 无法解析长选项。
getopts 不会影响原来的位置参数序列,也就是说 getopts 处理完位置参数后,$1
表示的还是 -c
或 -u
(栗子,懂我意思吧)。如果要读取之后的参数,可以使用上文中提到的 shift
,但首先需要将 getopts 处理过的参数出栈,shift $((OPTIND-1))
。
回到 ":c:hu:"
,其中第一个 :
表示 getopts 的错误汇报(errot-reporting)模式使用 silent 模式,忽略错误。
getopts 的错误汇报(errot-reporting)模式:
- 详尽模式(verbose mode):事无巨细,啥都报
- 静默模式(silent mode):忽略错误,以
?
和:
后的内容汇报
echo "Invalid option: -$OPTARG" >&2
中的 >&2
表示将内容输出到 stderr
,这里的 2 是特殊的 [[文件描述符(fd)]] 。
所谓文件描述符(File descriptor),是一个非负整数,本质是一个索引值。当打开一个文件时,内核向进程返回一个文件描述符(open系统调用返回得到),后续read、write这个文件时,只需要用这个文件描述符来标识这个文件,将其作为参数传入read、write。0,1,2 这三个文件描述符值已经被赋予特殊含义,分别是标准输入(STDIN_FILENO),标准输出(STDOUT_FILENO),标准错误(STDERR_FILENO)。
linux 中如果你不想显示结果的话,可以使用 2> /dev/null
将错误重定向到一个空设备中。脚本中的 >&2
指明输出的是 stderr
才能跟 2> /dev/null
无缝衔接,不然 shell 咋知道输出的是报错呢。
剩下还有没搞懂的自己谷歌下吧,这次写的废话有那么亿点点多了。
参考资料: