目录
Shell脚本
变量、赋值、字符串
bash中变量的赋值 语法:foo=bar
访问 变量中存储的数值:"$foo"
单引号和双引号的区别如下:
1 2 3 4 5 6 (base) chenyubin@chenyubindeMacBook-Pro ~ % cd /Users/chenyubin/Desktop/no_emo/github/MSofCS (base) chenyubin@chenyubindeMacBook-Pro MSofCS % foo=bar (base) chenyubin@chenyubindeMacBook-Pro MSofCS % echo "$foo " bar (base) chenyubin@chenyubindeMacBook-Pro MSofCS % echo '$foo' $foo
函数
bash会支持if,case,for等这些控制流关键字
mcd.sh
:
1 2 3 4 mcd () { mkdir -p "$1 " cd "$1 " }
1 2 3 4 5 (base) chenyubin@chenyubindeMacBook-Pro MSofCS % touch mcd.sh (base) chenyubin@chenyubindeMacBook-Pro MSofCS % vim mcd.sh (base) chenyubin@chenyubindeMacBook-Pro MSofCS % source mcd.sh (base) chenyubin@chenyubindeMacBook-Pro MSofCS % mcd test (base) chenyubin@chenyubindeMacBook-Pro test %
bash使用了很多特殊的变量来表示参数、错误代码和相关变量:
$0
- 脚本名
$1
到 $9
- 脚本的参数。$1
是第一个参数,依此类推。
$@
- 所有参数
$#
- 参数个数
$?
- 前一个命令的返回值
$$
- 当前脚本的进程识别码
!!
- 完整的上一条命令,包括参数。常见应用:当你因为权限不足执行命令失败时,可以使用 sudo !!
再尝试一次。
$_
- 上一条命令的最后一个参数。如果你正在使用的是交互式 shell,你可以通过按下 Esc 之后键入 .
来获取这个值。
下面将用一个例子来展示这些指令的使用:
mcd.sh
:
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 mcd () {echo "脚本名是: $(basename $0) " if [ $# -lt 1 ]; then echo "使用方法: $(basename $0) 目录名称" exit 1 fi mkdir -p "$1 " && cd "$1 " echo "已成功创建并切换到目录: $1 " echo "所有参数: $@ " echo "参数个数: $# " echo "当前脚本的PID: $$" }
指令信息如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 (base) chenyubin@chenyubindeMacBook-Pro MSofCS % source mcd.sh (base) chenyubin@chenyubindeMacBook-Pro test2 % mcd test3 脚本名是: mcd 已成功创建并切换到目录: test3 所有参数: test3 参数个数: 1 当前脚本的PID: 46932 (base) chenyubin@chenyubindeMacBook-Pro test3 % echo $? 0 (base) chenyubin@chenyubindeMacBook-Pro test3 % mcd test4 脚本名是: mcd 已成功创建并切换到目录: test4 所有参数: test4 参数个数: 1 当前脚本的PID: 46932 (base) chenyubin@chenyubindeMacBook-Pro test4 % echo $_ test4 (base) chenyubin@chenyubindeMacBook-Pro test4 % !! echo $_ test4
bool运算
返回值0表示正常执行,其他所有非0的返回值都表示有错误发生 。
返回码可以搭配&&
和||
,用来进行条件判断。这两个操作符都具备short-circuiting 属性:
使用&&
时,如果左侧的命令成功执行(即返回值为0),则会继续执行右侧的命令;如果左侧的命令失败(返回值非0),则不执行右侧的命令。
使用||
时,如果左侧的命令失败(返回值非0),则会执行右侧的命令;如果左侧的命令成功执行(返回值为0),则不执行右侧的命令。
同一行中多个命令可以使用;
分隔开来,允许在单一行内顺序执行多个命令,无论前一个命令执行成功与否。
。
1 2 3 4 5 6 7 8 (base) chenyubin@chenyubindeMacBook-Pro test4 % false || echo "Oops, fail" Oops, fail (base) chenyubin@chenyubindeMacBook-Pro test4 % true || echo "Will not be printed" (base) chenyubin@chenyubindeMacBook-Pro test4 % true && echo "Things went well" Things went well (base) chenyubin@chenyubindeMacBook-Pro test4 % false && echo "Will not be printed" (base) chenyubin@chenyubindeMacBook-Pro test4 % false ; echo "This will always run" This will always run
命令替换(command substitution)与进程替换(process substitution)
命令替换$()
:(做字符串)执行括号内的命令,并将标准输出替换到原位置。
进程替代<()
:(作临时文件)允许将命令的输出暂时当作文件来处理。感觉和上一个命令很像但是能把输出内容链接在一起
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 (base) chenyubin@chenyubindeMacBook-Pro MSofCS % echo "We are in $(pwd) " We are in /Users/chenyubin/Desktop/no_emo/github/MSofCS (base) chenyubin@chenyubindeMacBook-Pro MSofCS % cat <(ls ) <(ls ..) mcd.sh test test1 test2 CS224N MSofCS Spike-Driven-Transformer SpikeBERT bert d2l data spike spikingjelly vit
通配符
使用 ?
和 *
来匹配一个或任意个字符
1 2 3 4 5 6 7 (base) chenyubin@chenyubindeMacBook-Pro MSofCS % ls test ? test1: test2: test3 (base) chenyubin@chenyubindeMacBook-Pro MSofCS % ls *.sh mcd.sh
花括号{}
- 当你有一系列的指令,其中包含一段公共子串时,可以用花括号来自动展开这些命令。这在批量移动或转换文件时非常方便。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 (base) chenyubin@chenyubindeMacBook-Pro test % mkdir foo (base) chenyubin@chenyubindeMacBook-Pro test % touch foo/{a..h} (base) chenyubin@chenyubindeMacBook-Pro test % ls foo (base) chenyubin@chenyubindeMacBook-Pro test % tree -L 2 . └── foo ├── a ├── b ├── c ├── d ├── e ├── f ├── g └── h 2 directories, 8 files
shell函数与脚本
特性
函数
脚本
使用的语言
只能与shell使用相同的语言
可以使用任意语言,包含 shebang (#!
) 很重要
加载时机
仅在定义时被加载,更改定义后需重新加载
每次执行时加载
执行环境
在当前的shell环境中执行,可以直接更改环境变量
在单独的进程中执行,需使用 export
导出环境变量以影响当前环境
对环境的影响
可以对环境变量进行更改,例如改变当前工作目录
通常不直接更改调用它的shell的环境,除非使用特定方法(如 export
)
代码模块性和复用
可以提高代码模块性、代码复用性并创建清晰的结构,与其他编程语言的函数类似
虽然脚本是独立的程序,但往往也会包含它们自己的函数定义以提高代码模块性和复用
Shell工具
查看命令如何使用
为对应的命令行添加-h
或 --help
标记。
使用man
命令。man
命令是手册(manual)的缩写,它提供了命令的用户手册。
tldr
查找文件-find
1 2 3 4 5 6 7 8 find . -name src -type d find . -path '*/test/*.py' -type f find . -mtime -1 find . -size +500k -size -10M -name '*.tar.gz'
不仅仅是进行查找还能进行操作:
1 2 3 4 find . -name '*.tmp' -exec rm {} \; find . -name '*.png' -exec convert {} {}.jpg \;
更高效的工具:
fd
:find的替代品
locate
: 通过编译索引或建立数据库的方式来实现更加快速地搜索(不会用先不管了T.T)
查找代码-grep
查看文件内容时,grep
命令是一个非常强大的工具,用于对输入文本进行模式匹配。下面是grep
命令经常使用的一些选项:
-C [num]
:获取查找结果的上下文(Context),即除了匹配行外,还显示匹配行前后的内容。[num] 指定上下文的行数。
-v
:将对结果进行反选(Invert),也就是输出不匹配的结果。
-R
:递归搜索子目录,对指定目录下的所有文件以及子目录中的文件执行搜索操作。
示例:使用 grep 命令来搜索文件 mcd.sh 中包含字符串 “mcd” 的行
1 2 3 4 (base) chenyubin@chenyubindeMacBook-Pro MSofCS % grep mcd mcd.sh mcd () {(base) chenyubin@chenyubindeMacBook-Pro MSofCS % grep -r mcd . ./mcd.sh:mcd () {
更高效的工具:
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 (base) chenyubin@chenyubindeMacBook-Pro MSofCS % rg foobar mcd.sh (base) chenyubin@chenyubindeMacBook-Pro MSofCS % rg mcd mcd.sh 1:mcd () { (base) chenyubin@chenyubindeMacBook-Pro MSofCS % rg $ mcd.sh 1:mcd () { 2: 3: 4: 5:echo "脚本名是: $(basename $0) " 6: 7: 8:if [ $# -lt 1 ]; then 9: echo "使用方法: $(basename $0) 目录名称" 10: exit 1 11:fi 12: 13: 14:mkdir -p "$1 " && cd "$1 " 15:echo "已成功创建并切换到目录: $1 " 16: 17: 18:echo "所有参数: $@ " 19: 20: 21:echo "参数个数: $# " 22: 23: 24:echo "当前脚本的PID: $$" 25: 26: 27: 28:}
查找Shell命令
在使用Shell时,有多种方式可以帮助您快速找到并重新使用之前输入过的命令:
使用方向键 :按向上的方向键会显示你使用过的上一条命令。如果继续按上键,则会遍历整个历史记录,这允许您快速找到并重新执行之前的命令。
history
命令 :history
命令允许您以程序员的方式来访问Shell中输入的历史命令。
使用 Ctrl+R
进行回溯搜索 :在Shell中,您可以通过按下 Ctrl+R
开始对命令历史记录进行回溯搜索。输入您记得的命令的一部分,Shell会自动查找匹配的命令。反复按下 Ctrl+R
就会在所有搜索结果中循环,直到找到您需要的命令。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Last login: Thu Mar 7 13:36:19 on ttys000 (base) chenyubin@chenyubindeMacBook-Pro ~ % history 985 find . -path '*/test/*.sh' -type f 986 cd test 987 find . -path '*/test/*.sh' -type f 988 cd .. 989 locate MSofCS 990 sudo launchctl load -w /System/Library/LaunchDaemons/com.apple.locate.plist 991 locate MSofCS 992 updatedb 993 locate /etc/sh 994 sudo launchctl load -w /System/Library/LaunchDaemons/com.apple.locate.plist 995 rg -t py 'import requests' 996 brew install tldr 997 brew install ripgrep 998 grep foobar mcd.sh 999 grep mcd mcd.sh 1000 grep -r mcd .
文件夹导航
作业
1. 阅读 man ls,并使用 ls 命令进行如下操作:
显示所有文件(包括隐藏文件)
文件打印以人类可以理解的格式输出 (例如,使用454M 而不是 454279954)
文件以最近访问顺序排序
以彩色文本显示输出结果
典型输出示例:
1 2 3 4 5 -rw-r--r-- 1 user group 1.1M Jan 14 09:53 baz drwxr-xr-x 5 user group 160 Jan 14 09:53 . -rw-r--r-- 1 user group 514 Jan 14 06:42 bar -rw-r--r-- 1 user group 106M Jan 13 12:12 foo drwx------+ 47 user group 1.5K Jan 12 18:08 ..
解:
-a
显示所有文件及目录 (. 开头的隐藏文件也会列出)
-d
只列出目录(不递归列出目录内的文件)
-l
以长格式显示文件和目录信息,包括权限、所有者、大小、创建时间等
-r
倒序显示文件和目录
-t
将按照修改时间排序,最新的文件在最前面
-A
同 -a
,但不列出 “.” (当前目录) 及 “…” (父目录)
-F
在列出的文件名称后加一符号;例如可执行档则加 “*”, 目录则加 “/”
-R
递归显示目录中的所有文件和子目录
具体实例如下:
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 (base) chenyubin@chenyubindeMacBook-Pro MSofCS % ls -a . .DS_Store test test2 .. mcd.sh test1 (base) chenyubin@chenyubindeMacBook-Pro MSofCS % ls -d . (base) chenyubin@chenyubindeMacBook-Pro MSofCS % ls -l total 8 -rw-r--r--@ 1 chenyubin staff 553 3 7 11:56 mcd.sh drwxr-xr-x 3 chenyubin staff 96 3 7 11:44 test drwxr-xr-x 2 chenyubin staff 64 3 7 11:59 test1 drwxr-xr-x 4 chenyubin staff 128 3 7 12:00 test2 (base) chenyubin@chenyubindeMacBook-Pro MSofCS % ls -r test2 test1 test mcd.sh (base) chenyubin@chenyubindeMacBook-Pro MSofCS % ls -t test2 test1 mcd.sh test (base) chenyubin@chenyubindeMacBook-Pro MSofCS % ls -A .DS_Store mcd.sh test test1 test2 (base) chenyubin@chenyubindeMacBook-Pro MSofCS % ls -F mcd.sh test / test1/ test2/ (base) chenyubin@chenyubindeMacBook-Pro MSofCS % ls -R mcd.sh test test1 test2 ./test: foo ./test/foo: a b c d e f g h ./test1: ./test2: test3 ./test2/test3: test4 ./test2/test3/test4:
1.1 显示所有文件(包括隐藏文件): ls- a
1.2 文件打印以人类可以理解的格式输出: ls -h
1.3 文件以最近访问顺序排序: ls-t
1.4 以彩色文本显示输出结果
2. 编写两个bash函数 marco
和 polo
执行下面的操作:
每当你执行 marco
时,当前的工作目录应当以某种形式保存
当执行 polo
时,无论现在处在什么目录下,都应当 cd
回到当时执行 marco
的目录
为了方便debug,你可以把代码写在单独的文件 marco.sh
中,并通过 source marco.sh
命令,(重新)加载函数
1 2 3 4 5 6 7 8 9 10 11 #!/usr/bin/env/ bash macro (){ export MACRO=$(pwd ) } polo (){ cd "$MACRO " }
3.假设您有一个命令,它很少出错。为了在出错时能够对其进行调试,编写一段bash脚本:
运行如下的脚本直到它出错,将它的标准输出和标准错误流记录到文件
加分项:报告脚本在失败前共运行了多少次
脚本示例:
1 2 3 4 5 6 7 8 9 10 11 #!/usr/bin/env bash #使用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"
解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #!/usr/bin/env bash count=0 echo > out.log while true do ./buggy.sh &>> out.log if [[ $? -ne 0 ]]; then cat out.log echo "failed after $count times" break fi ((count++)) done
4.本节课我们讲解的 find
命令中的 -exec
参数非常强大,它可以对我们查找的文件进行操作。您的任务是:
编写一个命令,它可以递归地查找文件夹中所有的HTML文件,并将它们压缩成zip文件
注意,即使文件名中包含空格,您的命令也应该能够正确执行
提示:对于 MacOS 用户,使用 find
的 -print0
选项,并为 xargs
添加 -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 58 59 60 61 62 63 64 65 66 67 68 69 (base) chenyubin@chenyubindeMacBook-Pro MSofCS % mkdir html_root (base) chenyubin@chenyubindeMacBook-Pro MSofCS % cd $_ (base) chenyubin@chenyubindeMacBook-Pro html_root % touch {1..10}.html (base) chenyubin@chenyubindeMacBook-Pro html_root % ls -a . 1.html 2.html 4.html 6.html 8.html .. 10.html 3.html 5.html 7.html 9.html (base) chenyubin@chenyubindeMacBook-Pro html_root % mkdir html (base) chenyubin@chenyubindeMacBook-Pro html_root % cd $_ (base) chenyubin@chenyubindeMacBook-Pro html % touch xxx.html (base) chenyubin@chenyubindeMacBook-Pro html % cd .. (base) chenyubin@chenyubindeMacBook-Pro html_root % ls -a . 1.html 2.html 4.html 6.html 8.html html .. 10.html 3.html 5.html 7.html 9.html (base) chenyubin@chenyubindeMacBook-Pro html_root % tree -L 2 . ├── 1.html ├── 10.html ├── 2.html ├── 3.html ├── 4.html ├── 5.html ├── 6.html ├── 7.html ├── 8.html ├── 9.html └── html └── xxx.html 2 directories, 11 files (base) chenyubin@chenyubindeMacBook-Pro html_root % find html_root -name "*.html" -print0 | xargs -0 tar vcf html.zip find: html_root: No such file or directory (base) chenyubin@chenyubindeMacBook-Pro html_root % cd .. (base) chenyubin@chenyubindeMacBook-Pro MSofCS % find html_root -name "*.html" -print0 | xargs -0 tar vcf html.zip a html_root/9.html a html_root/5.html a html_root/4.html a html_root/8.html a html_root/3.html a html_root/html/xxx.html a html_root/2.html a html_root/1.html a html_root/10.html a html_root/7.html a html_root/6.html (base) chenyubin@chenyubindeMacBook-Pro MSofCS % tree -L 2 . ├── html.zip ├── html_root │ ├── 1.html │ ├── 10.html │ ├── 2.html │ ├── 3.html │ ├── 4.html │ ├── 5.html │ ├── 6.html │ ├── 7.html │ ├── 8.html │ ├── 9.html │ └── html ├── macro.sh ├── mcd.sh ├── test │ └── foo ├── test1 └── test2 └── test3 8 directories, 13 files
5.(进阶)编写一个命令或脚本递归的查找文件夹中最近使用的文件:
更通用的做法,你可以按照最近的使用时间列出文件吗?
1 2 3 4 5 6 7 8 9 10 11 (base) chenyubin@chenyubindeMacBook-Pro MSofCS % find . -type f -mmin -60 -print0 | xargs -0 ls -lt | head -10 -rw-r--r-- 1 chenyubin staff 6656 3 9 16:10 ./html.zip -rw-r--r-- 1 chenyubin staff 0 3 9 16:07 ./html_root/html/xxx.html -rw-r--r-- 1 chenyubin staff 0 3 9 16:06 ./html_root/10.html -rw-r--r-- 1 chenyubin staff 0 3 9 16:06 ./html_root/9.html -rw-r--r-- 1 chenyubin staff 0 3 9 16:06 ./html_root/8.html -rw-r--r-- 1 chenyubin staff 0 3 9 16:06 ./html_root/7.html -rw-r--r-- 1 chenyubin staff 0 3 9 16:06 ./html_root/6.html -rw-r--r-- 1 chenyubin staff 0 3 9 16:06 ./html_root/5.html -rw-r--r-- 1 chenyubin staff 0 3 9 16:06 ./html_root/4.html -rw-r--r-- 1 chenyubin staff 0 3 9 16:06 ./html_root/3.html
参考链接