MIT_Missing_Semester_02_shell
Shell & Script
shell的使用
因为有一定的基础,因此就跟着课程代码直接走一遍。
变量赋值
foo=bar |
函数使用
mkdircd(){ |
$0
- 脚本名$1
到$9
- 脚本的参数。$1
是第一个参数,依此类推。$@
- 所有参数$#
- 参数个数$?
- 前一个命令的返回值$$
- 当前脚本的进程识别码!!
- 完整的上一条命令,包括参数。常见应用:当你因为权限不足执行命令失败时,可以使用sudo !!
再尝试一次。$_
- 上一条命令的最后一个参数。如果你正在使用的是交互式 shell,你可以通过按下Esc
之后键入 . 来获取这个值。
返回码
命令通常使用 STDOUT
来返回输出值,使用STDERR
来返回错误及错误码,便于脚本以更加友好的方式报告错误。 返回码或退出状态是脚本/命令之间交流执行状态的方式。返回值0表示正常执行,其他所有非0的返回值都表示有错误发生。
退出码可以搭配 &&
(与操作符)和 ||
(或操作符)使用,用来进行条件判断,决定是否执行其他程序。它们都属于短路运算符(short-circuiting) 同一行的多个命令可以用;
分隔。程序 true
的返回码永远是0
,false
的返回码永远是1
。
false || echo "Oops, fail" |
命令替换
当您通过 $( CMD )
这样的方式来执行CMD
这个命令时,它的输出结果会替换掉 $( CMD )
。例如,如果执行 for file in $(ls)
,shell首先将调用ls
,然后遍历得到的这些返回值。还有一个冷门的类似特性是 进程替换(process substitution), <( CMD )
会执行 CMD
并将结果输出到一个临时文件中,并将 <( CMD )
替换成临时文件名。这在我们希望返回值通过文件而不是STDIN传递时很有用。例如, diff <(ls foo) <(ls bar)
会显示文件夹 foo
和 bar
中文件的区别。
下面这个例子展示了一部分上面提到的特性。这段脚本会遍历我们提供的参数,使用grep
搜索字符串 foobar
,如果没有找到,则将其作为注释追加到文件中。
!/bin/bash |
使用帮助
在使用命令时,利用-h
或者是--help
来获取相关命令的使用文档。
或者使用下面的网站在线查询。
查找文件
find
查找所有名称为src的文件夹 |
例如,为了查找满足模式 PATTERN
的文件,您需要执行 find -name '*PATTERN*'
(如果您希望模式匹配时是不区分大小写,可以使用-iname
选项)
fd
相较于 find
复杂的语法,fd
就是一个更简单、更快速、更友好的程序,它可以用来作为find
的替代品。它有很多不错的默认设置,例如输出着色、默认支持正则匹配、支持unicode并且我认为它的语法更符合直觉。以模式PATTERN
搜索的语法是 fd PATTERN
。
locate
我们是不是可以有更高效的方法,例如不要每次都搜索文件而是通过编译索引或建立数据库的方式来实现更加快速地搜索。
locate
使用一个由 updatedb
负责更新的数据库,在大多数系统中 updatedb
都会通过 cron
每日更新。这便需要我们在速度和时效性之间作出权衡。而且,find
和类似的工具可以通过别的属性比如文件大小、修改时间或是权限来查找文件,locate
则只能通过文件名。 这里有一个更详细的对比。
查找代码
grep
grep
有很多选项,这也使它成为一个非常全能的工具。其中我经常使用的有 -C
:获取查找结果的上下文(Context);-v
将对结果进行反选(Invert),也就是输出不匹配的结果。举例来说, grep -C 5
会输出匹配结果前后五行。当需要搜索大量文件的时候,使用 -R
会递归地进入子目录并搜索所有的文本文件。
但是,我们有很多办法可以对 grep -R
进行改进,例如使其忽略.git
文件夹,使用多CPU等等。
rg
因此也出现了很多它的替代品,包括 ack, ag 和 rg。它们都特别好用,但是功能也都差不多,我比较常用的是 ripgrep (rg
) ,因为它速度快,而且用法非常符合直觉。例子如下:
查找所有使用了 requests 库的文件 |
查找shell命令
history
history
命令允许您以程序员的方式来访问shell中输入的历史命令。这个命令会在标准输出中打印shell中的里面命令。如果我们要搜索历史记录,则可以利用管道将输出结果传递给 grep
进行模式搜索。 history | grep find
会打印包含find子串的命令。
你可以修改 shell history 的行为,例如,如果在命令的开头加上一个空格,它就不会被加进shell记录中。当你输入包含密码或是其他敏感信息的命令时会用到这一特性。 为此你需要在.bashrc
中添加HISTCONTROL=ignorespace
或者向.zshrc
添加 setopt HIST_IGNORE_SPACE
。 如果你不小心忘了在前面加空格,可以通过编辑bash_history
或 .zhistory
来手动地从历史记录中移除那一项。
HISTCONTROL=ignorespace |
Ctrl+R
可以使用 Ctrl+R
对命令历史记录进行回溯搜索。敲 Ctrl+R
后您可以输入子串来进行匹配,查找历史命令行。
文件夹导航
我们可以使用fasd
和 autojump 这两个工具来查找最常用或最近使用的文件和目录。
fasd
Fasd 基于 frecency对文件和文件排序,也就是说它会同时针对频率(frequency)和时效(recency)进行排序。默认情况下,fasd
使用命令 z
帮助我们快速切换到最常访问的目录。例如, 如果您经常访问/home/user/files/cool_project
目录,那么可以直接使用 z cool
跳转到该目录。
autojump
对于 autojump,则使用j cool
代替即可。
还有一些更复杂的工具可以用来概览目录结构,例如 tree
, broot
或更加完整的文件管理器,例如 nnn
或 ranger
。
课后练习
-
阅读
man ls
,然后使用ls
命令进行如下操作:-
所有文件(包括隐藏文件)
ls -l
-
文件打印以人类可以理解的格式输出 (例如,使用454M 而不是 454279954)
ls -l --block-size=M
-
文件以最近访问顺序排序
ls -c
ls -lt -
以彩色文本显示输出结果
ls --color=always
-
-
编写两个bash函数
marco
和polo
执行下面的操作。 每当你执行marco
时,当前的工作目录应当以某种形式保存,当执行polo
时,无论现在处在什么目录下,都应当cd
回到当时执行marco
的目录。 为了方便debug,你可以把代码写在单独的文件marco.sh
中,并通过source marco.sh
命令,(重新)加载函数。klose@K1ose-LT:~/cs/missing-semester/lab2/homework$ marco(){
MARCO_PATH=$(pwd)
}
klose@K1ose-LT:~/cs/missing-semester/lab2/homework$ polo(){
cd $MARCO_PATH
}
klose@K1ose-LT:~/cs/missing-semester/lab2/homework$ marco
klose@K1ose-LT:~/cs/missing-semester/lab2/homework$ cd ~
klose@K1ose-LT:~$ polo
klose@K1ose-LT:~/cs/missing-semester/lab2/homework$ -
假设您有一个命令,它很少出错。因此为了在出错时能够对其进行调试,需要花费大量的时间重现错误并捕获输出。 编写一段bash脚本,运行如下的脚本直到它出错,将它的标准输出和标准错误流记录到文件,并在最后输出所有内容。 加分项:报告脚本在失败前共运行了多少次。
!/usr/bin/env bash
n=$(( RANDOM % 100 ))
if [[ n -eq 42 ]]; then
echo "Something went wrong"
&2 echo "The error was using magic numbers"
exit 1
fi
echo "Everything went according to plan"solve.sh
!/bin/bash
count=1
while true
do
./bug.sh 2> out.log
if [[ $? -ne 0 ]]; then
echo "failed after $count times"
cat out.log
break
fi
((count++))
done
-
您的任务是编写一个命令,它可以递归地查找文件夹中所有的HTML文件,并将它们压缩成zip文件。注意,即使文件名中包含空格,您的命令也应该能够正确执行
递归找到当前文件夹及其子目录下的所有.html文件,并以\n为分隔,将对应文件打包到html.zip中
find . -type f -name "*.html" | xargs -d '\n' tar -czvf html.zip -
编写一个命令或脚本递归的查找文件夹中最近使用的文件。更通用的做法,你可以按照最近的使用时间列出文件吗?
问题在于需要递归地查询,因此不能简单地用ls来解决,需要配合使用find
find . -type f -print0 | xargs -0 ls -lt
当文件数量过多时可能会报错,因此需要用
-mmin
参数来限定传入的文件数量。find . -type f -mmin -60 -print0 | xargs -0 ls -lt