missing-semester-2
Shell工具和脚本
- 今天住进了峨眉山山里,网速感人,环境感人,所以这几天的更新就随缘咯
一点小知识
- bash是Mac或Linux系统中默认的Shell,与其他Shell兼容
Shell脚本
- 在bash中为变量赋值的语法是
foo=bar
,访问变量中存储的数值,语法为$foo
- 定义变量时,注意不能使用空格
- 定义字符串时,可用双引号/单引号定义字符串
- 对于纯文本来说,两种定义等价
- 对于变量的引用,双引号内会将变量值进行替换;单引号内不会替换变量值
- 下面会让shell记住sh文件中的函数,之后你就可以调用了
1
source xxx.sh
- 和大多数的编程语言一样,
bash
也支持if,case,while和for这些控制流关键字。bash
也支持函数,可接受参数并基于参数进行操作。 bash
使用很多特殊的变量来表示参数、错误代码和相关变量。完整参考链接:这里- $0: 脚本名
- $1到$9: 脚本参数
- $@: 所有参数
- $#: 参数个数
- $?: 前一个命令的返回值
- $$: 当前脚本的进程识别码
- !!: 完整的上一条命令,包括参数。
sudo !!
很有用 - $_: 上一条命令的最后一个参数。交互式shell可通过按下
Esc
之后按.
来获取这个值
- 同一行的多个命令可以用
;
分隔 - 以变量的形式获取一个命令的输出,可通过以下方法
- 命令替换
- 通过
$(CMD)
执行CMD这个命令时,输出结果会替换掉$(CMD)
- 执行
for file in $(ls)
, shell首先调用ls,然后遍历得到的这些返回值 foo=$(pwd)
将pwd的执行结果存到foo中
- 通过
- 进程替换
<(CMD)
会执行CMD
并将结果输出到一个临时文件中,并将<(CMD)
替换成临时文件名cat <(ls) <(ls ..)
会在内部执行,然后将输出放到一个类似临时文件的东西中,并将文件标识符提供给最左边的命令
- 命令替换
- example1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
echo "Starting program at $(date)" # date会被替换成日期和时间
echo "Running program $0 with $# arguments with pid $$"
for file in "$@"; do
grep foobar "$file" > /dev/null 2> /dev/null
# 如果模式没有找到,则grep退出状态为1
# 我们将标准输出流和标准错误流重定向到null,因为我们并不关心这些信息,/dev/null被称为黑洞文件,2>用于重定向标准错误流
if [[ $? -ne 0 ]]; then
# -ne表示“not equal”
echo "File $file does not have any foobar, adding one"
echo "# foobar" >> "$file"
fi
done
- 使用
source x.sh
来调用shell脚本
- 在bash中进行比较时,尽量使用双方括号[[]]而不是单方括号[],这样会降低犯错的几率,尽管这样并不能兼容sh。
- 当执行脚本时,我们经常需要提供形式类似的参数。bash使我们可以轻松的实现这一操作,它可以基于文件拓展名展开表达式。这一技术被称为shell的通配
- 通配符:
?
或*
可匹配一个或任意个字符 - 花括号{}:一系列指令中包含一段公共字串时,可用花括号来自动展开这些命令。在批量移动或转换文件时非常方便。还可以多层操作,做笛卡尔积
- 通配符:
- 检查sh/bash脚本中的错误的工具:shellcheck 。bash并不现代化,有时候调试起来很麻烦
- 脚本并不一定只有用bash写才能在终端里调用。如下的Python脚本(将输入的参数倒序输出),内核知道用python解释器而不是shell命令来运行这段脚本(因为脚本的开头第一行的shebang。实际上可以用许多不同语言来实现与shell交互的脚本。python默认情况下不会与shell交互
1
2
3
4#!/usr/local/bin/python
import sys
for arg in reversed(sys.argv[1:]):
print(arg)- 开头的魔法行叫做shebang,是让shell知道如何运行这个脚本,shebang展示的就是运行这个脚本的程序所在路径
- 你可能无法提前知道python或者其他解释器安装在哪里,所以你可以使用“env”,
#!/usr/bin/env python
,env在/usr/bin
中 - 使用
python x.py
来调用py脚本
- 在shebang行中使用env 命令会利用环境变量中的程序来解析该脚本,提高了可移植性
- shell函数和脚本的不同点
- shell函数只能与shell使用相同的语言,脚本可以使用任何语言。脚本中包含
shebang
是很重要的 - 函数仅在定义时被加载,脚本会在每次被执行时加载。使得函数的加载比脚本略快一些,但每次修改函数定义,都要重新加载一次
- 函数会在当前的shell环境中执行,脚本会在单独的进程中执行。因此函数可以对环境变量进行更改,如改变当前工作目录,脚本则不行。脚本需要使用export 将环境变量导出,并将值传递给环境变量
- 与其他程序语言一样,函数可以提高代码模块性、代码复用性并创建清晰性的结构。shell脚本中往往也会包含那它们自己的函数定义
- shell函数只能与shell使用相同的语言,脚本可以使用任何语言。脚本中包含
- 在bash中有一系列的比较运算符,可通过
man test
查看
Shell工具
查看命令如何使用
man
有时候没有实例,而且太过详细,所以不太方便- 可以安装一个很好的,叫做
tldr
的工具,可以得到一些工具很好的例子
查找文件
find
用于查找文件很好用-name
指定搜索名称-type
指定搜索类型-path
指定搜索路径-mtime
指定修改时间,-x
表示x天之内- 还可指定大小、所有者、权限等
find
不仅可以查找东西,也可以在找到这些文件时执行一些操作-exec 操作
fd
可以快捷查找文件名包含某些字符的文件,默认使用正则表达式,甚至还能忽略搜索你的git文件- 如果一天中要进行多次
find
,采用数据库方式并首先构建一个索引,然后让该索引以某种方式进行更新。大多数Unix系统已经通过“locate”命令完成了这一点。locate
只会查找你的文件系统中包含你想要的子字符串的路径。因为为文件系统建立了索引,所以检索速度比较快。为了时期保持更新,可以使用updatedb
命令来更新此数据库
查找代码
- 有时你实际上关心的不是文件本身,而是文件的内容,因此,可使用我们之前看到的“grep”命令。
- 递归地搜索当前结构并查找更多文件,使用grep的
-R
标志,它将遍历整个目录1
grep -R 查找内容 查找基目录
-C
:获取查找结果的上下文(Context)-v
对结果进行反选(Invert),输出不匹配的结果
- 递归地搜索当前结构并查找更多文件,使用grep的
ripgrep
,它与grep
的想法有点相同,但是ripgrep
更好看,如彩色编码或文件处理等,也支持unicode,有许多有用的标志
查找shell命令
- 如何查找你已经使用过的命令,不是找文件或代码
- 显然的方法就是使用上箭头,浏览后寻找
- 更轻松的方法,
history
会打印出所有命令的历史记录,结合管道和grep可筛选特定命令
- 几乎所有的shell默认都会将“Ctrl+R”这个键绑定为反向搜索
- 这里我们有一个反向(回溯)搜索,我们可以输入“convert”,一直按“Ctrl+R”会浏览这些匹配项,让我们在原地重新执行
fzf
是一个模糊查找器,就像它可以让你进行交互式的“grep”,如果启用了默认绑定,它会绑定到你的“Ctrl+R”- 用来避免重新输入非常长的命令的工具,叫做基于历史的自动补全,动态地在历史记录中搜索具有相同前缀的相同命令,它会随着匹配列表停止工作而改变,然后当你按右箭头时,你可以选择那个命令,然后重新执行它
文件夹导航
快速目录列表和目录导航:总是可以使用“ls -R”来递归地列出某个目录结构。但这可能不是最优的,这不是很好理解
- 有一个叫做“tree”的工具,它更友好地打印目录结构
- 有一个“broot”的工具,它可以做同样的事情
其他好用的工具
nnn
比
cd
更好用、更方便的快速到达一些地方的方法- 设置
alias
,使用ln -s
创建符号连接等 - 使用
fasd
和autojump
两个工具来查找最常用或最近使用的文件和目录- fasd基于frecency对文件和文件排序,会同时针对频率和时效进行排序
- 设置
习题
- 阅读
man ls
,然后使用ls命令进行如下操作- 所有文件(包括隐藏文件):
ls -a
或者ls --all
- 文件打印以人类可以理解的格式输出:
ls -l -h
或者ls -l -human-readable
- 文件以最近访问顺序排序:
ls -t -u
- 以彩色文本显示输出结果:
ls --color=auto
或者ls --color=always
- 所有文件(包括隐藏文件):
- 编写两个
bash
函数marco
和polo
执行下面的操作。每当你执行marco
时,当前的工作目录应当以某种形式保存,当执行polo
时,无论现在处在什么目录下,都应当cd
回到当时执行marco
的目录。为了方便debug,你可以把代码写在单独的文件marco.sh
中,并通过source marco.sh
命令,(重新)加载函数。或者1
2
3
4
5
6
7
8
9
marco(){
echo "$(pwd)" > $HOME/marco_history.log
echo "save pwd $(pwd)"
}
polo(){
cd "$(cat "$HOME/marco_history.log")"
}之后执行1
2
3
4
5
6
7
marco(){
export MARCO=$(pwd)
}
polo(){
cd "$MARCO"
}source marco.sh
,就可直接执行这两个函数 - 假设您有一个命令,它很少出错。因此为了在出错时能够对其进行调试,需要花费大量的时间重现错误并捕获输出。 编写一段
bash
脚本,运行如下的脚本直到它出错,将它的标准输出和标准错误流记录到文件,并在最后输出所有内容。 加分项:报告脚本在失败前共运行了多少次。
待测试脚本使用while循环实现1
2
3
4
5
6
7
8
9
10
11
12
n=$(( RANDOM % 100 ))
if [[ n -eq 42 ]]; then
echo "Something went wrong"
>&2 echo "The error was using magic numbers"
#>&2可以定义“错误”输出到STDERR指定的文件
exit 1
fi
echo "Everything went arrording to plan"1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
count=0
echo > out.log
while true
do
./buggy.sh &>> out.log
#$>>将输出或错误信息都追加到out.log
if [[ $? -ne 0 ]]; then
cat out.log
echo "failed after $count times"
break
fi
((count++))
done - 解答
- 关键点:
xargs
命令可以将输入中的内容作为参数,可使用-d 值
检查xargs中的值
- 关键点:
- 编写一个命令或脚本递归的查找文件夹中最近使用的文件。更通用的做法,你可以按照最近的使用时间列出文件吗?解答
最后
因为在山里,网络不太好,再加上最近在读另一本有趣的书《学会呼吸:重新掌握天生本能》 ,所以更新的比较慢,其实主要是拖延。
- 标题: missing-semester-2
- 作者: 敖炜
- 创建于 : 2023-07-01 09:29:07
- 更新于 : 2024-04-19 09:32:13
- 链接: https://ao-wei.github.io/2023/07/01/missing-semester-2/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论