Shell 基础知识

Shell 基础知识

一、Shell 介绍

​ Shell 是一个应用程序,是一个命令解释器,它连接了用户和 Linux 内核,让用户能够更加高效、安全、低成本地使用 Linux 内核,这就是 Shell 的本质。然而Shell本身并不是内核的一部分,它只是站在内核的基础上编写的一个应用程序,但是 Shell 也有着它的特殊性,就是开机立马启动,并呈现在用户面前;用户通过 Shell 来使用 Linux,不启动 Shell 的话,用户就没办法使用 Linux

​ 在 CentOS 7 中,我们常用 bash 作为一种 Shell,除了 bash 一些常用的 Shell 编译器还有 zsh (zShell) 和 ksh (kShell)

​ Shell 也是一种脚本,是系统命令的集合,可以使用逻辑判断、循环等语法,可以自定义函数,我们编写完源码后不用编译,直接运行源码即可。

​ Shell 脚本是在 Linux 的 shell 中运行的,所以称为 shell 脚本。本质上,shell 脚本 就是一些命令的集合。shell 脚本 可以实现自动化运维,所以能帮助我们很方便的管理服务器;比如我们可以指定一个任务计划,定时的去执行某个 shell 脚本 已满足需求。

二、命令历史(使用过的命令)history

1. ${HISTSIZE}

​ 这个变量设定了当前 shell 会保留多少条命令历史

​ 当用户在命令行输入命令后,这些命令会被记录在 shell 的历史中,以便通过 history 命令查看并重复使用

​ 历史记录数量超过 HISTSIZE 限制后,旧的命令会被删除,只保留最新的命令

2. ${HISTTIMEFORMAT}

​ 这个变量设定显示每条命令执行的时间戳

​ 当设置 HISTTIMEFORMAT 时,Bash 的历史记录会附加上每条命令执行的日期和时间,格式化后的时间戳会显示在 history 命令的输出中

3. chattr

​ 这个命令用于修改 Linux 文件系统的文件属性

​ 符号模式的格式是 +-=[aAcCdDeijsStTu]

+ 操作符表示将选中的属性添加到文件的现有属性中;- 表示从文件中移除这些属性;= 表示文件只会拥有选定的属性。

  • 设置了 ‘a’ 属性的文件只能以追加模式打开进行写入。只有超级用户或拥有 CAP_LINUX_IMMUTABLE 能力的进程可以设置或清除此属性。
  • 设置了 ’d’ 属性的文件在执行 dump(8) 程序时不会被备份
  • 设置了 ‘i’ 属性的文件不能被修改:无法删除或重命名,也无法创建链接,无法写入数据。只有超级用户或拥有 CAP_LINUX_IMMUTABLE 能力的进程可以设置或清除此属性
  • lsattr 显示的是文件的扩展属性(也就是通过 chattr 编辑的属性)
属性标志 含义
a 只能追加(append only):文件只能以追加模式打开,不能删除或修改现有内容。
A 禁止访问时间更新(no atime updates):访问文件时不更新访问时间,减少磁盘 I/O 操作。
c 压缩(compressed):文件在磁盘上自动压缩,读取时解压缩,写入时压缩。
C 禁止写时复制(no copy on write, COW):文件不会进行写时复制操作(适用于支持 COW 的文件系统)。
d 禁止转储(no dump):设置该属性后,dump 命令不会备份该文件。
D 目录同步更新(synchronous directory updates):目录修改时同步写入磁盘。
e 扩展格式(extents):文件使用扩展格式来映射磁盘块,适用于某些文件系统。
i 不可变(immutable):文件不能被修改、删除、重命名或链接。只有超级用户或具有 CAP_LINUX_IMMUTABLE 能力的进程可以设置或清除此属性。
j 数据日志记录(data journaling):文件的数据会首先被写入日志,适用于 ext3ext4 文件系统。
s 安全删除(secure deletion):文件被删除时,其数据块会被清零,确保数据无法恢复。
S 同步更新(synchronous updates):文件修改时数据同步写入磁盘,不使用缓存。
t 禁止尾部合并(no tail-merging):文件系统不会将文件末尾的部分块与其他文件合并。
T 目录层级顶部(top of directory hierarchy):表示该目录是一个目录层次结构的顶部,子目录应分散分配。
u 可恢复删除(undeletable):文件被删除后,其数据仍可恢复。
E 压缩错误(compression error):表示文件有压缩错误(只读,无法使用 chattr 设置或修改)。
h 超大文件(huge file):文件大小大于 2TB(只读,无法使用 chattr 设置或修改)。
I 索引目录(indexed directory):目录使用哈希树进行索引(只读,无法使用 chattr 设置或修改)。
N 内联数据(inline data):文件的数据存储在 inode 中(只读,无法使用 chattr 设置或修改)。
X 压缩原始访问(compression raw access):允许直接访问压缩文件的原始内容(只读,无法使用 chattr 设置或修改)。
Z 压缩脏文件(compressed dirty file):表示文件已被压缩但数据尚未完全写入磁盘(只读,无法使用 chattr 设置或修改)。

4. !!

​ 这个命令是打印上一条命令并把它执行出来

5. !(数字)

​ 这个命令是打印 history 里面的 第 (数字) 条命令并把它执行出来

6. !<命令>

​ 这个命令是打印 history 里面 最近的一个 以 <命令> 为开头的命令并把它执行出来

三、命令补全和别名

1. 命令补全

需要用到额外软件包 bash-completion

  • 补全命令: 如果你输入命令的一部分,可以按下 Tab 键让 Bash 自动完成命令
  • 补全命令选项: 输入一个命令后,可以通过 Tab 自动补全该命令的选项
  • 补全路径和文件名: 在输入命令时,使用 Tab 补全路径和文件名
  • 补全环境变量: 在输入 $ 后按 Tab 键可以自动补全环境变量

2. alias(别名)

2.1 创建别名

1
$ alias 别名='命令'

2.2 查看别名

1
$ alias

2.3 查看单个别名

1
$ alias 别名

2.4 取消别名Z

1
$ unalias 别名

四、通配符 输入输出重定向

1. 通配符

?

[ ] 这个也可以做判断

{ }

2. 输入输出重定向

>

>>

2>

&> 正确和错误重定向

五、命令

1. cut

cut 命令用于从文本中提取特定的列或字段

  • -d <分隔符>:指定字段分隔符(默认为制表符)
  • -f <字段号>:选择要显示的字段(用逗号或连字符分隔的列表)

假如 file.txt 的内容如下

1
2
3
4
name,age,city
Alice,25,New York
Bob,30,San Francisco
Charlie,22,Los Angeles
1
2
3
4
5
6
$ cut -d ',' -f 1,3 file.txt   # 提取以逗号分隔的第1和第3个字段

name,city
Alice,New York
Bob,San Francisco
Charlie,Los Angeles
  • -c <字符范围>:基于字符位置提取
1
2
3
4
5
6
$ cut -c 1-5 file.txt          # 提取每行的第1到5个字符

name,
Alice
Bob,3
Charl
  • --complement:补集选择,即不显示指定的字段或字符范围
1
2
3
4
5
6
$ cut --complement -d ',' -f 2 file.txt # 从上面的 file.txt 文件中排除第 2 个字段

name,city
Alice,New York
Bob,San Francisco
Charlie,Los Angeles

就等同于 cut -d ',' -f 1,3 file.txt

2. sort

sort 命令用于对文件按字典顺序(ASCII 码顺序)进行排序,支持数字、字母顺序的升序或降序排列

假设有一个 names.txt 内容如下

1
2
3
4
Charlie
Alice
Bob
David
  • 什么都不加默认是升序排序
1
2
3
4
5
6
$ sort names.txt

Alice
Bob
Charlie
David
  • -r:降序排列
1
2
3
4
5
6
$ sort -r names.txt

David
Charlie
Bob
Alice
  • -n:按数值进行排序

假设有一个 numbers.txt 文件,内容如下

1
2
3
4
100
2
50
30
1
2
3
4
5
6
$ sort -n numbers.txt

2
30
50
100
  • -k <字段>:指定按第几列排序

假设有一个 students.txt 文件,内容如下

1
2
3
4
Alice 90
Bob 85
Charlie 95
David 80
1
2
3
4
5
6
7
# 我们希望按 分数(第二列) 进行排序,而不是按 名字(第一列) 排序
$ sort -k 2 students.txt 

David 80
Bob 85
Alice 90
Charlie 95
  • -t <分隔符>:指定分隔符(默认是空格)

假设有一个 data.csv 文件,内容如下

1
2
3
4
Alice,90
Bob,85
Charlie,95
David,80
1
2
3
4
5
6
7
# 我们希望按 第二列(分数) 进行排序
$ sort -t ',' -k 2 -n data.csv

David,80
Bob,85
Alice,90
Charlie,95
  • -u:排序后删除重复行
  • -o:将结果保存到文件
1
$ sort names.txt -o sorted_names.txt

3. wc

wc (word count) 命令用于统计文件中的行数、单词数、字节数或字符数

  • -l:只显示行数

  • -w:显示单词数

  • -c:显示字节数

  • -m:显示字符数

  • -L:显示最长行的长度(字符数)

4. uniq

假设有一个 names.txt 文件,内容如下

1
2
3
4
5
6
Alice
Bob
Alice
Charlie
Bob
David
1
2
3
4
5
6
7
# uniq 只能过滤相邻的重复行,因此我们需要先对文件进行排序
$ sort names.txt | uniq

Alice
Bob
Charlie
David
  • -c:显示每行出现的次数
1
2
3
4
5
6
$ sort names.txt | uniq -c

      2 Alice
      2 Bob
      1 Charlie
      1 David
  • -d:只显示重复的行
1
2
3
4
$ sort names.txt | uniq -d

Alice
Bob
  • -u:只显示不重复的行
1
2
3
4
$ sort names.txt | uniq -u

Charlie
David
  • -i:忽略大小写

假设有一个 names_case.txt 文件,内容如下

1
2
3
4
5
Alice
alice
Bob
bob
Charlie
1
2
3
4
5
$ sort names_case.txt | uniq -i

Alice
Bob
Charlie
  • -f <n>:忽略前 n 个字段

假设有一个 students.txt 文件,内容如下

1
2
3
4
5
6
Alice 90
Bob 85
Alice 85
Charlie 95
David 80
Bob 90
1
2
3
4
5
6
$ sort students.txt | uniq -f 1

Alice 85
Charlie 95
David 80
Bob 90

5. tee

  • -a:将内容追加到文件,而不是覆盖
1
2
$ ls | tee output.txt           # 将 ls 输出保存到 output.txt 并显示在终端
$ ls | tee -a output.txt        # 追加输出到 output.txt
  • --ignore-interrupts:忽略中断信号(如 Ctrl+C

6. tr

tr 命令用于转换或删除文本中的字符。常用于大小写转换、删除特定字符等

  • tr 'set1' 'set2':将 set1 中的字符替换为 set2 中对应的字符
  • -d:删除指定字符
  • -s:将重复出现的字符压缩为单个

7. split

split 命令用于将大文件分割成多个小文件。可以按行数或文件大小进行分割

  • -l <行数>:按每个文件的行数进行分割

  • -b <字节大小>:按字节数进行分割

  • -d:使用数字后缀(默认为字母后缀)

  • -a <后缀长度>:指定分割文件后缀的长度

1
2
3
$ split -l 100 file.txt part_       # 每100行分割成一个文件,文件前缀为 part_
$ split -b 1M file.txt part_        # 每1MB 分割一个文件
$ split -l 100 -d file.txt part_    # 使用数字后缀

六、Shell 脚本

1. Shell 脚本结构与执行

1.1 脚本结构

​ 第一行一定得是 #!/bin/bash 。该命令说明,该文件使用的是 bash 语法,如果不设置改行,则该脚本不会被执行。以 # 开头的行作为解释说明。Shell 脚本 通常以 sh 为后缀,用于区分这是一个 Shell 脚本。

​ 下面我们来编写一个 shell 脚本,如下所示:

1
2
3
4
5
6
7
8
9
$ mkdir shell
$ cd shell
$ vi 1.sh
# 然后写入以下内容

#!/bin/bash
touch /tmp/1.txt
chmod 600 /tmp/2.txt
mv /tmp/1.txt /tmp/2.txt

1.1

1.2 脚本执行

​ 接下来我们执行刚才编写的脚本

1
$ bash 1.sh

​ 其实 shell 脚本 还有一种执行方法,但前提是脚本本身要有执行权限,所以执行前我们需要给脚本加一个 x 权限

1
2
3
4
$ ./1.sh
-bash: ./1.sh: 权限不够
$ chmod +x 1.sh
$ ./1.sh

1.2

2. 常用命令

2.1 查看脚本执行过程

1
2
3
4
$ bash -x 1.sh
+ touch /tmp/1.txt
+ chmod 600 /tmp/2.txt
+ mv /tmp/1.txt /tmp/2.txt

2.1

2.2 查看脚本是否有语法错误

1
$ bash -n 1.sh

2.3 date 命令

2.3.1 显示 年 月 日
1
2
3
date +%Y-%m-%d #年(四位)月日
date +%y-%m-%d #年(两位)月日
date +%F #年(四位)月日

2.3.1

2.3.2 显示 小时 分钟 秒
1
2
date +%H:%M:%S
date +%T

2.3.2

2.3.3 显示星期
1
2
date +%w #一周中的第X天
date +%W #一年中的第X周

2.3.3

2.3.4 时间戳

​ 时间戳是指特定时间的标识,通常以数字形式表示,表示自某个特定时刻以来经过的时间。时间戳广泛用于计算机系统、数据库和网络应用中,以记录事件的发生时间或用于时间相关的计算

​ 这里使用的是 Unix 时间戳

Unix 时间戳:自 1970 年 1 月 1 日(UTC)以来经过的秒数。例如,Unix 时间戳 1633072800 表示 2021 年 10 月 1 日,要是看现在的就用下面的命令

1
date +%s

​ 使用下面的命令显示输入秒数之前的时间

1
date -d @1633072800

2.3.4

2.3.5 显示一个小时之前/之后
1
2
date -d "+1 hour" #1h之后
date -d "-1 hour" #1h之前

2.3.5

2.3.6 显示一天之前/之后
1
2
date -d "+1 day" #一天后
date -d "-1 day" #一天前

2.3.6

3. shell 脚本中的变量

​ 在 shell 脚本 中使用变量可以节省时间并且使我们的脚本更加专业,所以当我们编写一个脚本时,就可以使用变量来代替某个使用频繁并且长度很长的字符串。变量的格式:“变量名=变量的值”

3.1 引用命令的结果

​ 当我们引用某个命令的结果时,可以使用变量代替

1
2
3
4
5
6
$ a=`date +%w`
$ echo $a
6
$ a=$(date +%w)
$ echo $a
6

3.1

3.2 与用户交互

1
2
3
4
5
6
7
8
$ read -p "请输入一个数字:" n
请输入一个数字:10
$ echo $n
10
$ read -p "请输入一个数字:"
请输入一个数字:2005
$ echo $REPLY
2005

3.2

3.3 内置变量

1
2
3
4
5
6
7
8
9
$ vi bian.sh #创建一个名为 bian.sh 的脚本
# 填入以下内容

#!/bin/bash
echo "\$1=$1"
echo "第二个参数是$2"
echo "第三个参数是$3"
echo "本脚本一共有$#个参数"
echo "\$0是$0"
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# 执行脚本
$ sh bian.sh

$1=
第二个参数是
第三个参数是
本脚本一共有0个参数
$0是bian.sh


# 再次执行脚本
$ sh bian.sh a b c
$1=a
第二个参数是b
第三个参数是c
本脚本一共有3个参数
$0是bian.sh

3.3

3.4 数学运算

1
2
3
4
5
6
7
8
$ vi sum.sh
# 填入以下内容

#!/bin/bash
a=1
b=2
sum=$[$a+$b]
echo "$a+$b=$sum"
1
2
3
# 执行脚本
$ sh sum.sh
1+2=3

3.4

4. Shell 中的逻辑判断

4.1 不带有 else

​ 基础结构

1
2
3
4
if  [判断语句]
	then
    command
fi

​ example

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ vi if1.sh
# 填入以下内容

#!/bin/bash

a=10
if [ $a -gt 4 ]
then
echo ok
fi
1
2
3
4
5
6
7
8
9
# 执行脚本
$ sh -x if1.sh 
+ a=10
+ '[' 10 -gt 4 ']'
+ echo ok
ok

# 检验语法错误
$ sh -n if1.sh

4.1

4.2 带有 else

​ 基础结构

1
2
3
4
5
6
if [判断语句]
	then
		command
else
	command
fi

​ exapmle

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ vi if2.sh
# 填入以下内容

#!/bin/bash

a=10
if [ $a -gt 4 ]
	then
		echo ok
	else
		echo "not ok"
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 执行脚本
$ sh -x if1.sh
sh -x if1.sh
+ a=10
+ '[' 10 -gt 4 ']'
+ echo ok
ok

# 检验脚本语法
$ sh -n if1.sh

4.1.2

4.3 带有 elif

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$ vi if3.sh
# 填入以下内容

#!/bin/bash
a=3
if [ $a -gt 4 ]
	then
		echo ok
	elif [ $a -gt 8 ]
		then
			echo "very ok"
		else
			echo "not ok"
fi
1
2
3
4
5
6
7
# 执行脚本
$ sh -x if3.sh
+ a=3
+ '[' 3 -gt 4 ']'
+ '[' 3 -gt 8 ']'
+ echo 'not ok'
not ok

4.3

4.4 嵌套

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
$ vi if4.sh
# 填入以下内容

#!/bin/bash

a=10
if [ $a -gt 4 ]
then
	if [ $a -lt 20 ]
	then
		echo "ok"
	else
		echo "very ok"
	fi
else
	echo "not ok"
fi

4.4

4.5 多个条件

1
2
if [ $a -gt 5 ] && [ $a -lt 10 ] == if [ $a -gt 5 -a $a -lt 10]     # -a表示 and
if [ $b -gt 5 ] || [ $b -lt 3] == if [ $b -gt 5 -o $b -lt 3 ]        # -o表示 or

4.6 if 逻辑判断

4.6.1 if 判断文件的目录属性

​ shell 脚本 中 if 经常用于判断文档的属性,比如判断是普通文件还是目录,判断文件是否有读、写、执行权限等。if 常用选项如下:

  • -e:判断文件或目录是否存在。
  • -d:判断是不是目录以及是否存在。
  • -f:判断是不是普通文件以及是否存在。
  • -T:判断是否有读权限。
  • -w:判断是否有写权限。
  • -X:判断是否可执行。

*注:root用户对文件的读写比较特殊,即使一个文件没有给root用户读或者写的权限,root也可以读或者写

4.6.2 if 判断的一些特殊用法
  1. 命令 if [ -z "$a" ]; 表示当变量 a 的值为空时会怎么样

    4.6.2.1

  2. 命令 if [ -n "$a" ]; 表示当变量 a 的值不为空时会怎么样

    4.6.2.2

  3. 命令 if grep -q '123' 1.sh; then 表示如果 1.sh 中含有 ‘123’ 会怎么样,其中 -q 表示即使过滤内容也不要打印出来

    4.6.2.3

  4. if (($a<1)); then 等同于 if [ $a -lt 1 ]; then ,二者都可以用来进行判断,需要注意的是,当我们未对变量 a 进行赋值时则会报错

    4.6.2.4

    • 注意[ ] 中不能使用< , > , == , != , >= , <= 这样的符号,需要时要使用固定写法 -gt (>) ; -lt(<) ; -ge(>=) ; -le(<=) ; -eq(==) ; -ne(!=)

4.7 shell 中的 case 判断

​ case 判断的基础格式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
case 变量 in
value1)
    command
    ;;
value2)
    command
    ;;
*)
    command
    ;;
esac

​ 为了让我们能够更加清晰的理解 case 逻辑判断,接下来我们编写一个脚本来进行实验

 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
$ vi case.sh
# 填入以下内容

#!/bin/bash
read -p "Please input a number:" n 
if [ -z "$n" ]
then
   echo "Please input a number." 
   exit 1 
fi

n1=`echo $n|sed 's/[0-9]//g'`
if [ -n "$n1" ]
then
 echo "Please input a number."
 exit 1 
fi

if [ $n -lt 60 ] && [ $n -ge 0 ]	# 条件是 0 <= n < 60
then
    tag=1
elif [ $n -ge 60 ] && [ $n -lt 80 ] # 条件是 60 <= n < 80
then
    tag=2
elif [ $n -ge 80 ] && [ $n -lt 90 ] # 条件是 80 <= n < 90
then
    tag=3
elif [ $n -ge 90 ] && [ $n -le 100 ] # 条件是 90 <= n <= 100
then
    tag=4 
else
    tag=0 
fi
case $tag in
   1)
     echo "not ok"
     ;;
   2)
     echo "ok"
     ;;
   3)
     echo "very ok"
     ;;
   4)
     echo "oook"
     ;;
   *)
     echo "The number range is 0-100."
     ;;
esac
  • 这段的意思是:接收用户输入的一个数字,并根据该数字的范围(0-100),输出相应的评价信息,如 “not ok”、“ok”、“very ok” 或 “oook”,如果输入超出范围或不是数字,则提示错误

4.7

5. shell 中的循环

5.1 for 循环

​ 基础结构如下

1
2
3
4
for 变量名 in 循环条件;
do
	command
done

​ 下面进行一个简单的演示

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ vi for1.sh
# 填入以下内容

#!/bin/bash
sum=0
for i in `seq 1 10`
do
   sum=$[$sum+$i]
   echo $i
done
echo $sum

5.1

5.2 while 循环

​ 基础结构

1
2
3
4
while 条件
do
	command
done

​ example 1

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
$ vi while2.sh
# 填入以下内容

#!/bin/bash
while :
do
  read -p "Please input a number: " n
  if [ -z "$n" ] 
  then
      echo "you need input sth." 
      continue 
  fi
  n1=`echo $n|sed 's/[0-9]//g '`
  if [ -n "$n1" ] 
  then
      echo "you just only input numbers." 
      continue 
  fi
  break 
done 
echo $n

5.2

6. Shell 中的中断与继续

6.1 跳出循环

break 在脚本中表设计跳出该层循环

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$ vi break1.sh
# 填入以下内容

#!/bin/bash
for i in `seq 1 5`
do
	echo $i
	if [ $i -eq 3 ]
	then
		break
	fi
	echo $i
done
echo aaaaa

6.1

6.2 结束本次循环

​ 当在 shell 脚本中使用 continue 时,结束的不是整个循环,而是本次循环。忽略 continue 之下的代码,直接进行下一次循环

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$ vi continue1.sh
# 填入以下内容

#!/bin/bash
for i in `seq 1 5`
do
	echo $i
	if [ $i == 3 ]
	then
		continue	##此处continue表示若 $i == 3 则结束本次循环
	fi
	echo $i
done
echo $i

6.2

6.3 退出整个脚本

​ 当我们在 shell 脚本中遇到 exit 是,其表示直接退出整个 shell 脚本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$ vi exit1.sh
# 填入以下内容

#!/bin/bash
for i in `seq 1 5`
do
	echo $i
	if [ $i == 3 ]
	then
		exit
	fi
	echo $i
done
echo aaaa

6.3

7. Shell 中的函数

​ shell 脚本 中的函数就是先把一段代码整理到了一个小单元中,并给这个小单元命名,当我们用到这段代码时直接调用这个小单元的名字就可以了,这样很方便,省时省力。但我们需要注意,在 shell 脚本 中,函数一定要写在前面,因为函数要被调用的,如果还未出现就被调用就会出错。

​ 基础格式

1
2
3
4
function f_name()
{
	command
}
  • 解释:
    • function 是定义一个函数的关键字,但在大多数现代 Shell(如 Bash)中,function 关键字是可选的
    • f_name 是函数的名称(你可以自定义这个名字),它可以是任何合法的函数名
    • {} 大括号包围的是函数体,里面的 command 表示函数中要执行的命令

7.1 打印出第一个、第二个参数、参数的个数及脚本名

1
2
3
4
5
6
7
8
9
$ vi fun1.sh
# 填入以下内容

#!/bin/bash
input()
{
	echo $1 $2 $# $0         # 函数的参数:$1 $2 $# ;$0则是脚本的名字
}
input 1 a b

7.1

7.2 加法的函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ vi fun2.sh
# 填入以下内容

#!/bin/bash
sum()
{
	s=$[$1+$2]
	echo $s
}
sum 1 2

7.2

7.3 获得一个网卡的 IP 地址

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ vi fun3.sh
# 填入以下内容

#!/bin/bash
ip()
{
	ifconfig | grep -A1 "$1: " | tail -1 | awk '{print $2}'
}
read -p "Please input the eth name:" e
myip=`ip $e`
echo "$e address is $myip"

7.3

8. shell 中的数组

8.1 数组读取

​ 首先我们需要先定义一个数组 a=(1 2 3 4 5)

  1. 命令 echo ${a[@]} 读取数组中的全部元素

    8.1.1

  2. 命令 echo ${#a[@]} 获取数组的元素个数

    8.1.2

  3. 命令 echo ${a[2]} 读取第三个元素,数组从 0 开始

    8.1.3

  4. 命令 echo ${a[*]} 等同于 echo ${a[@]} 作用为显示整个数组

    8.1.4

8.2 数组赋值

  1. a[1]=100; echo ${a[@]} 替换指定元素值

    8.2.1

  2. a[5]=2; echo ${a[@]} 如果下标不存在则会自动添加一个元素

    8.2.2

  3. a[7]=6; echo ${a[@]} 跳着添加元素时,中间未赋值的元素,不显示且无值

    8.2.3

8.3 数组的删除

  1. 命令 unset a[1] 用于删除单个元素

    8.3.1

  2. 命令 unset a 用于删除整个数组

    8.3.2

8.4 数组分片

​ 在进行实验操作之前,需要对一个数组进行赋值 a=($(seq 1 5))

  1. 命令 echo ${a[@]:0:3} 表示从第一个元素开始,截取 3 个元素,并打印出来

    8.4.1

  2. 命令 echo ${a[@]:1:4} 表示从第二个元素开始,截取 4 个元素,并打印出来

    8.4.2

  3. 命令 echo ${a[@]:0-3:2} 表示从倒数第 3 个元素开始,截取 2 个元素,并打印出来

    8.4.3

8.5 数组替换

  1. 命令 echo ${a[@]/b/100} 表示用 100 替换 b,但不会保存替换,只是打印出来

    8.5.1

  2. 命令 a=(${a[@]/b/100}) 表示用 100 替换 b,这种方法不仅可以打印出来,还可以保存替换

    8.5.2

七、正则表达式

1. grep/egrep 工具的使用

​ 该命令的格式为 grep [-cinvABC] 'word' filename

  • -c:表示打印符合要求的行数

  • -i:表示忽略大小写

  • -n:表示输出符合要求的行及其行号

  • -v:表示打印不符合要求的行

  • -A:后面跟一个数字(有无空格都行),eg: -A2 表示打印符合要求的行以及下面两行

  • -B:后面跟一个数字,eg: -B2 表示打印符合要求的行以及上面两行

  • -C:后面跟一个数字,eg: -C2 表示打印符合要求的行以及上下各两行

eg:

  1. 过滤出带有某个关键词的行,并输出行号

    1
    2
    3
    
    $ grep -n 'root' /etc/passwd
    1:root:x:0:0:root:/root:/bin/bash
    10:operator:x:11:0:operator:/root:/sbin/nologin
    
  2. 过滤出不带有某个关键词的行,并输出行号

    1
    2
    3
    4
    5
    
    $ grep -nv 'nologin' /etc/passwd
    1:root:x:0:0:root:/root:/bin/bash
    6:sync:x:5:0:sync:/sbin:/bin/sync
    7:shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
    8:halt:x:7:0:halt:/sbin:/sbin/halt
    
  3. 过滤出所有包含数字的行

    1
    2
    3
    
    $ grep '[0-9]' /etc/inittab 
    # multi-user.target: analogous to runlevel 3
    # graphical.target: analogous to runlevel 5
    
  4. 过滤出所有不包含数字的行

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    $ grep -v '[0-9]' /etc/inittab 
    # inittab is no longer used when using systemd.
    #
    # ADDING CONFIGURATION HERE WILL HAVE NO EFFECT ON YOUR SYSTEM.
    #
    # Ctrl-Alt-Delete is handled by /usr/lib/systemd/system/ctrl-alt-del.target
    #
    # systemd uses 'targets' instead of runlevels. By default, there are two main targets:
    #
    #
    # To view current default target, run:
    # systemctl get-default
    #
    # To set a default target, run:
    # systemctl set-default TARGET.target
    #
    
  5. 过滤掉所有以 # 开头的行

    ​ 在正则表达式中,^ 表示行的开始

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    $ grep -v '^#' /etc/vsftpd/vsftpd.conf 
    anonymous_enable=YES
    local_enable=YES
    write_enable=YES
    local_umask=022
    dirmessage_enable=YES
    xferlog_enable=YES
    connect_from_port_20=YES
    xferlog_std_format=YES
    listen=NO
    listen_ipv6=YES
    
    pam_service_name=vsftpd
    userlist_enable=YES
    tcp_wrappers=YES
    
    • PS:这里面是有空行的
  6. 过滤掉所有空行和以 # 开头的行

    ​ 在正则表达式中,^ 表示行的开始,$ 表示行的结尾,那么空行则可以用 ^$ 来表示

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    $ grep -v '^#' /etc/vsftpd/vsftpd.conf | grep -v '^$'
    anonymous_enable=YES
    local_enable=YES
    write_enable=YES
    local_umask=022
    dirmessage_enable=YES
    xferlog_enable=YES
    connect_from_port_20=YES
    xferlog_std_format=YES
    listen=NO
    listen_ipv6=YES
    pam_service_name=vsftpd
    userlist_enable=YES
    tcp_wrappers=YES
    
    • 那么,如何打印出不以英文字母开头的行呢? 让我们先来创建一个文件

      1
      2
      3
      4
      5
      6
      7
      8
      
      $ vi test.txt
      123
      abc
      456
      
      abc2323
      #laksdjf
      Alllllllll
      

      ​ 然后来查看

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      
      $ grep '^[^a-zA-Z]' test.txt 
      123
      456
      #laksdjf
      
      $  grep '[^a-zA-Z]' test.txt 
      123
      456
      abc2323
      #laksdjf
      

      ​ 前面也提到过 中括号 [ ] 的应用,如果是数字就用 [0-9] 这样的形式(当遇到类似 [15] 的形式时,表示只含有 1 或者 5) ​ 如果要过滤数字以及大小写字母,则要写成 [0-9a-zA-Z] 的形式 ​ 另外,[^字符] 表示除 [] 内字符之外的字符

      • 注意:把 ^ 写到方括号的里面和外面是有很大区别的(在外面的是表示首位,在里面的表示"相反")
  7. 过滤出任意一个字符和重复字符

    . 表示任意一个字符,r.o 表示把 ro 之间有一个任意字符的行过滤出来

    1
    2
    3
    
    $ grep 'r.o' /etc/passwd
    root:x:0:0:root:/root:/bin/bash
    operator:x:11:0:operator:/root:/sbin/nologin
    

    * 表示零个或者多个 * 前面的字符,ooo* 表示匹配 三个 o 字符,其中前两个 o 是必须的,最后一个 o* 表示可以有零个或多个 o

    1
    2
    3
    4
    5
    6
    
    $ grep 'ooo*' /etc/passwd
    root:x:0:0:root:/root:/bin/bash
    lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
    mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
    operator:x:11:0:operator:/root:/sbin/nologin
    postfix:x:89:89::/var/spool/postfix:/sbin/nologin
    

    .* 表示零个或者多个任意字符,空行也包含在内,它会把文件里面的所有行都匹配到

    1
    2
    3
    4
    
    $ grep '.*' /etc/passwd | wc -l
    21
    $ wc -l /etc//passwd
    21 /etc//passwd
    
  8. 指定要过滤出字符出现次数

    ​ 这里用到了符号 {},其内部为数字,表示前面的字符要重复的次数 ​ 需要强调的是,{} 左右都需要加上转义字符 \

    ​ 另外,使用 { } 还可以表示一个范围,具体格式为 {n1,n2},其中 n1n2,表示重复 n1n2 次前面的字符,n2 还可以为空,这时表示 ≥ n1

    1
    2
    3
    4
    5
    6
    
    $ grep 'o\{2\}' /etc/passwd
    root:x:0:0:root:/root:/bin/bash
    lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
    mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
    operator:x:11:0:operator:/root:/sbin/nologin
    postfix:x:89:89::/var/spool/postfix:/sbin/nologin
    

​ 除了 grep 工具外,我们也会常常用到 egrep 这个工具,后者是前者的扩展版本,可以完成 grep 不能完成的部分工作

假设我们有一个写着以下内容的 test.txt

1
2
3
4
5
6
rot:x:0:0:rot:/rot:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
rooooooooooot:x:0:0:rooooooooooooot:/root:/bin/bash
11111111111111111111111111111111111111111111111111111
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  1. 过滤出一个或者多个指定的字符

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    $ egrep 'o+' test.txt 
    rot:x:0:0:rot:/rot:/bin/bash
    operator:x:11:0:operator:/root:/sbin/nologin
    operator:x:11:0:operator:/root:/sbin/nologin
    rooooooooooot:x:0:0:rooooooooooooot:/root:/bin/bash
    
    $ egrep 'oo+' test.txt 
    rot:x:0:0:rot:/rot:/bin/bash
    operator:x:11:0:operator:/root:/sbin/nologin
    operator:x:11:0:operator:/root:/sbin/nologin
    rooooooooooot:x:0:0:rooooooooooooot:/root:/bin/bash
    
    $ egrep 'ooo+' test.txt
    rooooooooooot:x:0:0:rooooooooooooot:/root:/bin/bash
    

    ​ 和 grep 不同,这里 egrep 使用的是符号 +,他表示匹配 1 个 或 多个 + 前面的字符,这个 + 是不支持被 grep 使用的

    ​ 包括上面的 {},也是可以直接被 egrep 使用,而不用加 \ 转义

    1
    2
    3
    4
    5
    6
    
    $ egrep 'o{2}' /etc/passwd 
    root:x:0:0:root:/root:/bin/bash
    lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
    mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
    operator:x:11:0:operator:/root:/sbin/nologin
    postfix:x:89:89::/var/spool/postfix:/sbin/nologin
    
  2. 过滤出零个或者一个指定的字符

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
$ egrep 'o?' test.txt 
rot:x:0:0:rot:/rot:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
rooooooooooot:x:0:0:rooooooooooooot:/root:/bin/bash
11111111111111111111111111111111111111111111111111111
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

$ egrep 'ooo?' test.txt 
operator:x:11:0:operator:/root:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
rooooooooooot:x:0:0:rooooooooooooot:/root:/bin/bash

$ egrep 'oooo?' test.txt 
rooooooooooot:x:0:0:rooooooooooooot:/root:/bin/bash
  1. 过滤出字符串 1 或者字符串 2

    1
    2
    3
    4
    
    $ egrep 'aaa|111|ooo' test.txt 
    rooooooooooot:x:0:0:rooooooooooooot:/root:/bin/bash
    11111111111111111111111111111111111111111111111111111
    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
    
  2. egrep() 的应用

    ​ 这里用 () 表示一个整体,下面会把包含 rooo 或者 rato 的行过滤出来

    1
    2
    3
    4
    
    $ egrep 'r(oo|at)o' test.txt 
    operator:x:11:0:operator:/root:/sbin/nologin
    operator:x:11:0:operator:/root:/sbin/nologin
    rooooooooooot:x:0:0:rooooooooooooot:/root:/bin/bash
    

    ​ 也可以把 () 和其他符号组合在一起,比如 (oo)+ 就表示 1 个 或者 多个 oo

    1
    2
    3
    4
    
    $ egrep '(oo)+' test.txt 
    operator:x:11:0:operator:/root:/sbin/nologin
    operator:x:11:0:operator:/root:/sbin/nologin
    rooooooooooot:x:0:0:rooooooooooooot:/root:/bin/bash
    

2. sed 工具的使用

​ 其实 grep 的功能还不够强大,它实现的只是查找功能,不能把查找出来的内容进行替换,而 sed 是流式编辑器,是针对文档的行来操作的,也能进行内容的替换

2.1 打印某行

​ 格式为 sed -n 'n'p filename'n' 里面的 n 是一个数字,表示第几行,-n 表示只显示我们要打印的行

1
2
$ sed -n '2'p /etc/passwd
bin:x:1:1:bin:/bin:/sbin/nologin

​ 要是想把所有行都导引出来,可以使用命令 sed -n '1,$'p filename

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ sed -n '1,$'p /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
nobody:x:99:99:Nobody:/:/sbin/nologin
systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
polkitd:x:999:998:User for polkitd:/:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
postfix:x:89:89::/var/spool/postfix:/sbin/nologin
chrony:x:998:996::/var/lib/chrony:/sbin/nologin
mysql:x:27:27:MariaDB Server:/var/lib/mysql:/sbin/nologin
nginx:x:997:995:Nginx web server:/var/lib/nginx:/sbin/nologin

​ 当然,我们也可以指定一个区间打印

1
2
3
4
$ sed -n '1,3'p /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin

2.2 打印包含某个字符串的行

1
2
3
4
$ sed -n '/root/'p test.txt 
operator:x:11:0:operator:/root:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
rooooooooooot:x:0:0:rooooooooooooot:/root:/bin/bash

​ 这种用法就类似于 grep 了,在 grep 中使用的特殊字符(如 ^ $ . * 等)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
$ sed -n '/^1/'p test.txt 
11111111111111111111111111111111111111111111111111111

$ sed -n '/in$/'p test.txt
operator:x:11:0:operator:/root:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin

$ sed -n '/r..o/'p test.txt 
operator:x:11:0:operator:/root:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
rooooooooooot:x:0:0:rooooooooooooot:/root:/bin/bash

$ sed -n '/ooo*/'p test.txt 
rot:x:0:0:rot:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
rooooooooooot:x:0:0:rooooooooooooot:/root:/bin/bash
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计
备案图标 辽公网安备21010602001101 辽ICP备2024027190号-1