Linux系统可划分为四个部分,Linux内核、GNU工具、图形化界面和应用软件,重点掌握的是Linux内核和GNU工具,分别是由Linus Torvalds和GNU组织根据Unix系统独立开发的。Linux系统的整体框架如下图所示:
linux中的内存可分为RAM+交换内存,交换内存是磁盘上一块被指定的物理内存,当RAM不够使用时,linux的内存管理程序会将那些相对不经常使用的内存页“交换”到交换空间上,用来释放RAM上的空间。
Linux中的交换内存,又叫虚拟内存,linux的内存管理就是大名鼎鼎的 虚拟内存管理,会给每个进程分配一个大小为4G的虚拟内存(x86的32位Linux系统),空间分配如下图所示:
每个进程的用户空间被分配了0x00000000至
0xBFFFFFFF的3GB空间,按照【访问属性一致的地址空间被存放在一起】的原则,地址从低到高被分为五个区:
值得注意的是,按照csapp的说法,堆区和栈区之间还有一个动态库的内存映射区域;代码段和栈分别存放,只有数据段、BSS和堆一般是物理地址连续的。
内核空间被分配到0xBFFFFFFF到0xFFFFFFFF的1GB空间,该区域用户不可见。
注:上图有个小错误,按照地址空间从下往上递增的规则,内核空间应该放在用户空间的上方。
Linux分配回收内存的单位是page frame,但是会造成严重的内存碎片的问题,为了缓解这个问题,Linux使用Buddy算法和slab分配器分别解决了 页面内内存碎片 和 页面间内存碎片 的问题,前者是将相同大小的page frame块用链表连接起来,块的大小设置为2^n的大小,因为任何整数都能表示为二进制整数,所以避免了页面间内存碎片的问题;后者是针对小型内核数据对象建立slab缓存内存池,重复利用一些相同的对象,化零为整,然后再交给Buddy管理器,避免了页面内的内存碎片。
执行中的程序叫做进程(process),内核会先创建第一个进程(init进程)来启动系统上的其他进程,Ubuntu的/etc/init.d
目录中管理着开机自启动的进程,init进程也分为5个启动等级,标准启动等级是3级。
Ubuntu中使用ps -l
查看正在运行的进程,使用ps-forest
查看父进程和子进程:
Linux中一切皆文件,硬件设备也是一种特殊文件——设备文件,可分为三类
Linux使用虚拟文件系统,作为和每个文件系统交互的接口。
GNU工具是由GNU组织(GNU’s Not Unix )开发的unix工具,其中供Linux系统使用的核心工具被称为coreutils软件包,由三部分组成:
shell就是其中一种特殊的交互式工具,可以用来处理文件、管理进程,使用cat /etc/shells
查看系统支持的shell类型,使用echo $SHELL
查看系统默认的shell:
root@LAPTOP-GJB0QGV5:/home/xmfeng# cat /etc/shells
# /etc/shells: valid login shells
/bin/sh
/bin/bash
/usr/bin/bash
/bin/rbash
/usr/bin/rbash
/bin/dash
/usr/bin/dash
/usr/bin/tmux
/usr/bin/screen
root@LAPTOP-GJB0QGV5:/home/xmfeng# echo $SHELL
/bin/bash
CentOS和Ubuntu的默认解析器都是bash。
通过两个例子,初步了解shell脚本编程的特点,注意脚本开头的#!/bin/bash
指定/bin/bash作为解析器。
需求:创建脚本,输出hello world
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# touch helloworld.sh
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# vim helloworld.sh
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# cat helloworld.sh
#!/bin/bash
echo "Hello World"
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# chmod 777 helloworld.sh
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# ./helloworld.sh
Hello World
需求:创建一个banzhang.txt,在banzhang.txt文件中增加“I love cls”
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# touch batch.sh
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# vim batch.sh
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# chmod 777 batch.sh
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# ./batch.sh
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# cat banzhang.txt
I love cls
常用系统变量,$HOME
,$PWD
,$SHELL
,$USER
, 查看所有的系统变量的命令set
定义变量变量标识符=值
,注意等号左右不能有空格,默认变量类型为 字符串类型;
撤销变量unset 变量标识符
声明静态变量readonly 变量标识符
, 静态变量不能unset
将局部变量升级为全局变量,供其他shell使用,export 变量标识符
#!/bin/bash
for file in `ls /etc`
do
echo $file
done
或者使用通配符
#!/bin/bash
for file in /home/xmfeng/shell_practice/*
do
if [ -d "$file" ]
then
echo "$file is a directory"
elif [ -f "$file" ]
then
echo "$file is a file"
fi
done
甚至shell提供了C语言风格的for循环,在语法细节上也更加灵活:
#!/bin/bash
for (( i = 1; i <=10 ; i++ ))
do
echo "The next number is $i"
done
其中,变量赋值等号左右可以有空格;条件中的变量不必以美元号开头;迭代过程中的算式未采用expr形式。
$n:$0表示脚本名称,$1到$9表示参数1到参数9,十以上的参数要用大括号括起来,例如${10}
$#:获取所有输入参数的个数, 不计入文件名
$*:命令行中的所有输入参数,看作一个整体
$@:命令行中的所有输入参数,区分对待
推荐$((1+2))
或者$[1+2]
, 不推荐expr,因为后者是bash shell为了和dash shell兼容而不得已的实现,对于*需要加上转义字符,这两种方法只支持整数运算,浮点数运算参考zshell(zsh)或者bc,例如
var1=$(echo "scale=4;3.14/3" | bc)
echo $var1
其中,scale指定小数位数。当然对于多行运算,表达十分繁琐,幸好bc支持文件重定向,可以将表达式写成一个文件,然后重定向到bc,也可以使用 内联输入重定向:
#!/bin/bash
var1=10.46
var2=43.44
var3=9.22
var4=71
var5=$(bc<<EOF
scale = 4
a1 = ( $var1 * $var2 )
b1 = ( $var3 * $var4 )
a1+b1
EOF)
注意到,bash内联计算器中可以变量赋值,但是作用域仅限计算器内部。
基本语法: [ condition ]
, 注意条件语句condition的前后一定要有空格。
常见的判断条件:
(1)两个整数之间比较
= 字符串比较
-lt 小于(less than) -le 小于等于(less equal)
-eq 等于(equal) -gt 大于(greater than)
-ge 大于等于(greater equal) -ne 不等于(Not equal)
(2)按照文件权限进行判断
-r 有读的权限(read) -w 有写的权限(write)
-x 有执行的权限(execute)
(3)按照文件类型进行判断
-f 文件存在并且是一个常规的文件(file)
-e 文件存在(existence) -d 文件存在并是一个目录(directory)
判断结果在$?
中,该标志位等于1时,为假,等于0时,为真。
多条件判断,&&
和||
。
基本语法
if [ condition ]
then
程序
fi
# 或者
if [ condition ]; then
程序
fi
注意,if后面要加空格。
case $变量名 in
"值1")
程序1
;;
"值2")
程序2
;;
# 省略其他分支
*)
程序
;;
esac
注意事项:
case行尾必须为单词“in”,每一个模式匹配必须以右括号“)”结束。
双分号“;;”表示命令序列结束,相当于java中的break。
最后的“*)”表示默认模式,相当于java中的default。
推荐使用C语言风格的for循环,详细使用参照变量部分。
while [ condition ]
do
程序
done
read(选项)(参数)
选项:
-p:指定读取值时的提示符;
-t:指定读取值时等待的时间(秒)。
参数
变量:指定读取值的变量名
例子:提示7秒内读取控制台输入的名称
read -t 7 -p "Enter your name in 7 seconds" NAME
basename和dirname
例子:输入两个参数,求和并输出
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# touch fun.sh
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# vim fun.sh
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# cat fun.sh
#!/bin/bash
function sum()
{
s=0
s=$[ $1 + $2 ]
echo $s
}
read -p "Please input the number1: " n1;
read -p "Please input the number2: " n2;
sum $n1 $n2;
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# chmod 777 fun.sh
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# ./fun.sh
Please input the number1: 2
Please input the number2: 4
6
函数的返回值只能通过$?
获得,可以使用return $变量名
的方式返回一个0-255的整数,如果不加,默认以最后一条命令的返回结果作为函数的返回结果。使用函数返回值重写上面的程序:
#!/bin/bash
function sum()
{
s=0
s=$[ $1 + $2 ]
return $s
}
read -p "Please input the number1: " n1;
read -p "Please input the number2: " n2;
sum $n1 $n2;
echo $?
基本用法:cut[选项参数] filename
,默认分隔符是 制表符
选项参数说明:-f
指定列号(多个列号可以用逗号分隔),-d
指定分隔符
例子:给定cut.txt,切割第二列和第三列;切割出guan
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# touch cut.txt
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# vim cut.txt
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# cat cut.txt
dong shen
guan zhen
wo wo zhang
lai lai wang
le le chen
xiao ming li
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# cut -d " " -f 2,3 cut.txt
shen
zhen
wo zhang
lai wang
le chen
ming li
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# cat cut.txt | grep "guan" | cut -d " " -f 1
guan
例子:获取系统变量$PATH值,输出第二个:之后的所有路径
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# echo $PATH | cut -d: -f 3-
/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
例子:切割ifconfig中的IP地址
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# ifconfig eth0
eth0: flags=64<RUNNING> mtu 1500
inet 169.254.157.111 netmask 255.255.0.0
inet6 fe80::659c:b87b:ec81:9d6f prefixlen 64 scopeid 0xfd<compat,link,site,host>
ether b4:69:21:3d:aa:e9 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# ifconfig eth0 | grep "netmask"
inet 169.254.157.111 netmask 255.255.0.0
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# ifconfig eth0 | grep "netmask" | cut -d " " -f 10
169.254.157.111
sed是一种流编辑器(stream editor),它一次处理STDIN中的一行内容,处理时,把当前处理的行存储在临时缓冲区中,接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的内容输送到STDOUT。接着处理下一行,这样不断重复,直到文件末尾。文件内容并没有改变,除非你使用重定向存储输出。
sed编辑器的命令,可以从命令行输入,或者存储在一个命令文件中。
基本用法:sed [options] 'script' filename
options:-e:从命令行script中读取命令;-f:从file指定的文件中读取命令;-n:不产生命令输出,用户自己通过print进行输出
script:s:查找并替换,默认替换每一行中的第一个匹配正确的对象;a:新增;d:删除
将数据通过管道输送到STDIN流上,然后sed编辑器会将指定命令应用到输入流上。
例如使用s命令,将test改为big test
root# echo "This is a test." | sed 's/test/big test/'
This is a big test.
通过man sed
查看,可以发现-e
的意思是 “add the script to the commands to be executed”
多个命令需要用分号分开,命令的开头和末尾不能有空格,例如
root@LAPTOP:/home/xmfeng/shell_practice# echo "This is a white cat." | sed -e 's/white/black/ ; s/cat/dog/'
This is a black dog.
-f的意思是"add the contents of scripts-file to the commands to be executed"
一般会将.sed
作为sed脚本的拓展名,例如
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# cat data1.txt
this is a white cat.
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# cat script1.sed
s/white/black/
s/cat/dog/
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# sed -f script1.sed data1.txt
this is a black dog.
例子:将"mei nv"插入到sed.txt第二行,并打印
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# touch sed.txt
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# vim sed.txt
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# cat sed.txt
dong shen
guan zhen
wo wo
le le
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# sed '2a mei nv' sed.txt
dong shen
guan zhen
mei nv
wo wo
le le
注意:sed.txt并没有改变
删除特定行,分别删除data中的第三行,第二行和第三行,含有number1字符串的行(也能使用sed的模式匹配特性)
# sed '3d' data.txt
# sed '2,3d' data.txt
# sed '/number1/d' data.txt
替换:s/pattern/replacement/flags
中的替换标记有四种,数字、g(global,全部替换)、p(打印替换所在行,常常和-n配合使用)和w(将替换结果写入文件)。
同sed一样,awk也是一种流编辑器,gawk是GNU根据Unix上的流编辑器awk拓展而来的,需要自己安装。
基本使用: awk [options] 'pattern1{action1} pattern2{action2}' filename
options说明:-F:指定输入文件每行的分隔符;-v:赋值一个用户自定义变量
案例实操:
数据准备
# sudo cp /etc/passwd ./
输出passwd文件中所有以root开头的行的第七列(注意,只有匹配了pattern的行才会执行action)
# awk -F: '/^root/{print $7}' passwd
只显示/etc/passwd的第一列和第七列,以逗号分割,且在所有行前面添加列名user,shell在最后一行添加"dahaige,/bin/zuishuai"(BEGIN 在所有数据读取行之前执行;END 在所有数据执行之后执行。)
# awk -F: 'BEGIN{print "user,shell"} {print $1","$7} END{print "dahaige, /bin/zuishuai"}'
变量 | 说明 |
---|---|
FILENAME | 文件名 |
NR | 已读的记录数(STDIN所在行号) |
NF | 浏览记录的域的个数(该行切割后,列的个数) |
在wsl上使用cut方法:ifconfig eth0 | grep "mask" | cut -d" " -f10
在wsl上使用awk方法:ifconfig eth0 | grep "mask" | awk -F " " '{print $2}'
【值得注意的是,awk这里只需要第二位即可,可见前面的空格被自动忽略,通过NF查看,确实如此】
# awk '/^$/{print NR}' sed.txt
awk中的模式匹配方法:详见参考文献5,Richard Blum的Linux命令行与shell脚本编程第三版的第二十章,正则表达式
基本用法: sort[选项][参数]
选项
选项 | 说明 |
---|---|
-n | 依照数值的大小排序 |
-r | 以相反的顺序来排序 |
-t | 设置排序时所用的分隔字符 |
-k | 指定需要排序的列 |
案例实操
数据准备
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# touch sort.txt
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# vim sort.txt
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# cat sort.txt
bb:40:5.4
bd:20:4.2
xz:50:2.3
cls:10:3.5
ss:30:1.6
按照:分割后的第三列倒序排序
root@LAPTOP-GJB0QGV5:/home/xmfeng/shell_practice# sort -nrk 3 -t: sort.txt
bb:40:5.4
bd:20:4.2
cls:10:3.5
xz:50:2.3
ss:30:1.6
问题1:使用Linux命令查询sed.txt中空行所在的行号【注意,NR前面不要加$】【考察:awk】
awk '/$^/{print NR}' sed.txt
问题2:有文件chengji.txt内容如下:
张三 40
李四 50
王五 60
使用Linux命令计算第二列的和并输出【考察:awk】
awk -v sum=0 '{sum+=$2} END{print $sum}' chengji.txt
问题1:Shell脚本里如何检查一个文件是否存在?如果不存在该如何处理?【考察条件判断语句中的参数,shell脚本中的if[condition];then else fi语句】
#!/bin/bash
if [ -f filename.txt ];then
echo "文件存在"
else
echo "文件不存在"
fi
问题2:用shell写一个脚本,对文本中无序的一列数字排序,并求和输出结果【考察sort和awk】
#!/bin/bash
sort -n test.txt | awk -v sum=0 '{sum+=$1} END{print $sum}'
问题3:使用awk或者sed删除第一列
awk '{$1="";print $0}' file
sed -e 's/[^ ]* //' file
[1] linux内存管理(详解) - 知乎 (zhihu.com)
[2] 【尚硅谷】Shell脚本从入门到实战__bilibili
[3] Shell 教程 | 菜鸟教程 (runoob.com)
[4] Tutorial01_shell (sjtu.edu.cn)
[5] Linux命令行与shell脚本编程大全,第三版,Richard Blum 【强烈推荐!】
本文主要参考来源: