missing-semester(2)-shell工具和脚本

missing-semester(2)-shell工具和脚本
Holishell脚本
在shell中,空格十分重要。
$ foo=bar %可以执行
$ foo = bar %无法执行
注意单引号和双引号的区别。
$ echo "Value is $foo" %$foo会转变为其对应的变量值
$ echo 'Value is $foo' %$foo不会转换
下面是一个bash脚本的例子mcd.sh
(创建以第一个参数为名的文件夹并进入):
mcd() {
ppmkdir -p "$1"
ppcd "$1"
}
其中$0
表示脚本自身的名字,$1
到$9
依次表示各个输入参数,$_
是上一个指令的最后一个参数,$?
可以获得上一个指令的错误代码,!!
可以指代上一条指令。可以通过如下指令来加载新定义的函数:
$ source mcd.sh %脚本中包含的函数会在当前shell中加载
逻辑符号:
$ false || echo "Oops fail" %如果第一条指令出错,则执行第二条指令
$ true && echo "Things went well" %如果第一条指令正确,才会执行第二条指令
$ false ; echo "This will alwanys print" %分号只是将两条指令分割开来
将某个指令的输出赋值给一个变量:
$ foo=$(pwd)
$ echo "We are in $(pwd)"
冷门特性——进程替换:
$ cat <(ls) <(ls ..) %<(命令)相当于把该命令的输出作为一个临时文件
下面展示一个具体的例子(使用 grep
搜索字符串 foobar
,如果没有找到,则将其作为注释追加到文件中):
#!/bin/bash
echo "Starting program at $(date)" # date会被替换成日期和时间
echo "Running program $0 with $# arguments with pid $$"
# $#表示输入参数的数目,$$表示当前进程的id
for file in "$@"; do # $@表示所有的参数
grep foobar "$file" > /dev/null 2> /dev/null
# 如果模式没有找到,则grep退出状态为 1
# 我们将标准输出流(第一个>)和标准错误流(2>)重定向到Null,因为我们并不关心这些信息
if [[ $? -ne 0 ]]; then
echo "File $file does not have any foobar, adding one"
echo "# foobar" >> "$file"
fi
done
直接使用如下方式执行上述脚本:
$ ./example.sh [文件1] [文件2]
通配符
shell的通配(globbing)允许我们方便地进行文件匹配:
- 通配符 - 当你想要利用通配符进行匹配时,你可以分别使用
?
和*
来匹配一个或任意个字符。例如,对于文件foo
,foo1
,foo2
,foo10
和bar
,rm foo?
这条命令会删除foo1
和foo2
,而rm foo*
则会删除除了bar
之外的所有文件。 - 花括号
{}
- 当你有一系列的指令,其中包含一段公共子串时,可以用花括号来自动展开这些命令。这在批量移动或转换文件时非常方便。
$ convert image.{png,jpg} %会展开为convert image.png image.jpg
$ cp /path/to/project/{foo,bar,baz}.sh /newpath %会展开为cp /path/to/project/foo.sh /path/to/project/bar.sh /path/to/project/baz.sh /newpath
$ mv *{.py,.sh} folder %移动所有以.py和.sh结尾的文件
$ touch {foo,bar}/{a..j} %展开为foo/a, foo/b, ...
脚本并不是一定只有用bash写才能在终端中调用,如下的Python脚本也可以直接在终端中调用:
#!/usr/local/bin/python
import sys
for arg in reversed(sys.argv[1:]):
print(arg)
脚本中第一行的shebang
使得终端知道应该调用python解释器。(也可以写为#!/usr/bin/env python
)
编写 bash
脚本有时候会很别扭和反直觉,shellcheck 这样的工具可以帮助你定位 sh/bash 脚本中的错误。
$ shellcheck mcd.sh
shell 函数和脚本有如下一些不同点:
- 函数只能与 shell 使用相同的语言,脚本可以使用任意语言。因此在脚本中包含
shebang
是很重要的。 - 函数仅在定义时被加载,脚本会在每次被执行时加载。这让函数的加载比脚本略快一些,但每次修改函数定义,都要重新加载一次。
- 函数会在当前的 shell 环境中执行,脚本会在单独的进程中执行。因此,函数可以对环境变量进行更改,比如改变当前工作目录,脚本则不行。脚本需要使用
export
将环境变量导出,并将值传递给环境变量。 - 与其他程序语言一样,函数可以提高代码模块性、代码复用性并创建清晰性的结构。shell 脚本中往往也会包含它们自己的函数定义。
shell工具
查看命令实例
convert
和ffmpeg
这两个命令可以分别用于处理图片和视频。可以使用如下指令获得一些命令的简单例子:
$ tldr convert
查找文件
通过如下指令可以找到所需的文件:
$ find . -name src -type d %在当前文件夹中迭代找到名称为src的文件夹
$ find . -path '**/test/*.py' -type f %在当前文件夹中找到某些文件夹下包含在test子目录中的py文件
$ find . -mtime -1 %在昨天被修改过的文件,mtime表示修改时间
$ find . -name "*.tmp" -exec rm {} \; %找到这些文件并移除它们
$ find . -name '*.png' -exec convert {} {}.jpg \; %查找全部的 PNG 文件并将其转换为 JPG
$ find . -size +500k -size -10M -name '*.tar.gz' %查找所有大小在500k至10M的tar.gz文件
$ fd "*.py" %具有和find相似的作用
也可以检索整个文件系统:
$ updatedb %在执行查找前需要更新数据库
$ locate tmp %找到整个文件系统中路径带有tmp的文件,注意是是路径中带有
查找内容
如下方式可以查找文件中特定内容:
$ grep foobar mcd.sh %查找mcd.sh文件中的foobar
$ grep -R foobar %在当前文件夹下查找文件中所有出现位置
$ rg "import requests" -t py ~/scratch %在该文件夹下迭代搜索所有出现该内容的py文件
$ rg "import requests" -t py -C 5 ~/scratch %输出时展示附近的5行内容
$ rg -u --files-without-match "^#\!" -t sh %找到没有出现"#!"的文件,即没有shebang的文件,-u表示不要忽略隐藏文件
$ rg "import requests" -t py -C 5 --stats PATTERN ~/scratch %打印更详细的检索信息
$
grep
有很多选项,-C
:获取查找结果的上下文(Context);-v
将对结果进行反选(Invert),也就是输出不匹配的结果。举例来说, grep -C 5
会输出匹配结果前后五行。当需要搜索大量文件的时候,使用 -R
会递归地进入子目录并搜索所有的文本文件。也出现了很多它的替代品,包括 ack, ag 和 rg。
查找shell命令
可以直接显示过往命令历史:
$ history
$ history | grep convert %先显示历史,再匹配对应的命令
也可以使用ctrl+R
,fzf
可以进行模糊的检索(可以将其与ctrl+R
绑定):
$ cat example.sh | fzf %进行可交互的查找
其他
之前对所有操作我们都默认一个前提,即您已经位于想要执行命令的目录下,但是如何才能高效地在目录间随意切换呢?有很多简便的方法可以做到,比如设置 alias,使用 ln -s 创建符号连接等。而开发者们已经想到了很多更为精妙的解决方案。
由于本课程的目的是尽可能对你的日常习惯进行优化。因此,我们可以使用 fasd
和 autojump 这两个工具来查找最常用或最近使用的文件和目录。
Fasd 基于 frecency 对文件和文件排序,也就是说它会同时针对频率(frequency)和时效(recency)进行排序。默认情况下,fasd
使用命令 z
帮助我们快速切换到最常访问的目录。例如, 如果您经常访问 /home/user/files/cool_project
目录,那么可以直接使用 z cool
跳转到该目录。对于 autojump,则使用 j cool
代替即可。
还有一些更复杂的工具可以用来概览目录结构,例如 tree
, broot
或更加完整的文件管理器,例如 nnn
或 ranger
。
$ tree %树状图显示文件结构
$ broot %可交互的树状图
命令行可以从参数或标准输入接受输入,在用管道连接命令时,我们将标准输出和标准输入连接起来,但是有些命令,例如 tar
则需要从参数接受输入,而xargs
可以使用标准输入中的内容作为参数,比如
$ ls | xargs rm %删除当前文件夹下的所有文件