Loading...

目录


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 () {
#!/bin/bash

# 输出脚本名
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
# 查找所有名称为src的文件夹
find . -name src -type d
# 查找所有文件夹路径中包含test的python文件
find . -path '*/test/*.py' -type f
# 查找前一天修改的所有文件
find . -mtime -1
# 查找所有大小在500k至10M的tar.gz文件
find . -size +500k -size -10M -name '*.tar.gz'

不仅仅是进行查找还能进行操作:

1
2
3
4
# 删除全部扩展名为.tmp 的文件
find . -name '*.tmp' -exec rm {} \;
# 查找全部的 PNG 文件并将其转换为 JPG
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 () {

更高效的工具:

  • rg
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:#!/bin/bash
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 .

文件夹导航

  • ls -R

  • tree

  • broot

  • nnn

作业

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 ..

解:

1
ls [-alrtAFR] [name...]
  • -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函数 marcopolo 执行下面的操作:

  • 每当你执行 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 ))#生成0~99的数字

if [[ n -eq 42 ]]; then #检查n是否等于42,如果等于则执行下面的代码
echo "Something went wrong" #输出到标准输出
>&2 echo "The error was using magic numbers" #输出到标准错误
exit 1 #n=42,以状态1推出
fi

echo "Everything went according to plan" #n不等于42就输出这段代码到标准输出

解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env bash
count=0
echo > out.log #清空或创建log文件用来记录

while true
do
./buggy.sh &>> out.log #执行上面的文件,然后将标准输出和标准错误都写道out.log中
if [[ $? -ne 0 ]]; then #执行程序返回的状态=0代表执行成功
cat out.log #打印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

参考链接