Linux Shell脚本编程笔记

linux简介

Linux系统可划分为四个部分,Linux内核、GNU工具、图形化界面和应用软件,重点掌握的是Linux内核和GNU工具,分别是由Linus Torvalds和GNU组织根据Unix系统独立开发的。Linux系统的整体框架如下图所示:
在这里插入图片描述

linux内核的四种功能

1. 内存管理

linux中的内存可分为RAM+交换内存,交换内存是磁盘上一块被指定的物理内存,当RAM不够使用时,linux的内存管理程序会将那些相对不经常使用的内存页“交换”到交换空间上,用来释放RAM上的空间。

虚拟内存管理

Linux中的交换内存,又叫虚拟内存,linux的内存管理就是大名鼎鼎的 虚拟内存管理,会给每个进程分配一个大小为4G的虚拟内存(x86的32位Linux系统),空间分配如下图所示:

在这里插入图片描述

每个进程的用户空间被分配了0x000000000xBFFFFFFF的3GB空间,按照【访问属性一致的地址空间被存放在一起】的原则,地址从低到高被分为五个区:

  • 代码区:存放只读不写的可执行目标文件
  • 数据区:已初始化的全局变量
  • BSS区:未初始化的全局变量
  • 堆区:由用户动态分配的内存空间,例如C语言中的malloc和free,以及C++中的new和delete
  • 栈区:实现函数调用

值得注意的是,按照csapp的说法,堆区和栈区之间还有一个动态库的内存映射区域;代码段和栈分别存放,只有数据段、BSS和堆一般是物理地址连续的。

内核空间被分配到0xBFFFFFFF到0xFFFFFFFF的1GB空间,该区域用户不可见。

注:上图有个小错误,按照地址空间从下往上递增的规则,内核空间应该放在用户空间的上方。

内存碎片的问题和解决方案

Linux分配回收内存的单位是page frame,但是会造成严重的内存碎片的问题,为了缓解这个问题,Linux使用Buddy算法slab分配器分别解决了 页面内内存碎片 和 页面间内存碎片 的问题,前者是将相同大小的page frame块用链表连接起来,块的大小设置为2^n的大小,因为任何整数都能表示为二进制整数,所以避免了页面间内存碎片的问题;后者是针对小型内核数据对象建立slab缓存内存池,重复利用一些相同的对象,化零为整,然后再交给Buddy管理器,避免了页面内的内存碎片。

2. 软件程序管理

执行中的程序叫做进程(process),内核会先创建第一个进程(init进程)来启动系统上的其他进程,Ubuntu的/etc/init.d目录中管理着开机自启动的进程,init进程也分为5个启动等级,标准启动等级是3级。

Ubuntu中使用ps -l查看正在运行的进程,使用ps-forest查看父进程和子进程:

在这里插入图片描述

3. 硬件设备管理

Linux中一切皆文件,硬件设备也是一种特殊文件——设备文件,可分为三类

  • 字符型设备文件,一次只能处理一个字符,例如终端
  • 块设备文件,一次能够处理大块字符,例如硬盘
  • 网络设备文件

4.文件系统管理

Linux使用虚拟文件系统,作为和每个文件系统交互的接口。

GNU工具

GNU工具是由GNU组织(GNU’s Not Unix )开发的unix工具,其中供Linux系统使用的核心工具被称为coreutils软件包,由三部分组成:

  • 处理文件的工具
  • 操作文本的工具
  • 管理进程的工具

shell

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脚本案例

通过两个例子,初步了解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

shell中的变量

系统变量

常用系统变量,$HOME,$PWD$SHELL,$USER, 查看所有的系统变量的命令set

自定义变量

定义变量变量标识符=值,注意等号左右不能有空格,默认变量类型为 字符串类型;

撤销变量unset 变量标识符

声明静态变量readonly 变量标识符, 静态变量不能unset

将局部变量升级为全局变量,供其他shell使用,export 变量标识符

还可以使用for语句进行赋值,例如输出/etc下的所有文件名:
#!/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}

$#:获取所有输入参数的个数, 不计入文件名

$*:命令行中的所有输入参数,看作一个整体

$@:命令行中的所有输入参数,区分对待

shell的语法

运算符

推荐$((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时,为真。

多条件判断,&&||

流程控制

1. if 判断

基本语法

if [ condition ]
then
	程序
fi
# 或者
if [ condition ]; then
	程序
fi

注意,if后面要加空格。

2.case判断

case $变量名 in
"值1")
	程序1
	;;
"值2")
	程序2
	;;
# 省略其他分支
*)
	程序
	;;
esac

注意事项:

  1. case行尾必须为单词“in”,每一个模式匹配必须以右括号“)”结束。

  2. 双分号“;;”表示命令序列结束,相当于java中的break。

  3. 最后的“*)”表示默认模式,相当于java中的default。

3.for循环

推荐使用C语言风格的for循环,详细使用参照变量部分

4.while循环

while [ condition ]
do
	程序
done

read读取控制台输入

read(选项)(参数)

选项:

-p:指定读取值时的提示符;

-t:指定读取值时等待的时间(秒)。

参数

​ 变量:指定读取值的变量名

例子:提示7秒内读取控制台输入的名称

read -t 7 -p "Enter your name in 7 seconds" NAME

函数

1. 系统函数

basename和dirname

2.自定义函数

例子:输入两个参数,求和并输出

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 $?

shell中的常用工具[重点]

1. cut , 剪切数据

基本用法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

2. sed,流编辑器

sed是一种流编辑器(stream editor),它一次处理STDIN中的一行内容,处理时,把当前处理的行存储在临时缓冲区中,接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的内容输送到STDOUT。接着处理下一行,这样不断重复,直到文件末尾。文件内容并没有改变,除非你使用重定向存储输出。

sed编辑器的命令,可以从命令行输入,或者存储在一个命令文件中。

基本用法sed [options] 'script' filename

options:-e:从命令行script中读取命令;-f:从file指定的文件中读取命令;-n:不产生命令输出,用户自己通过print进行输出

script:s:查找并替换,默认替换每一行中的第一个匹配正确的对象;a:新增;d:删除

1. 在命令行输入单个命令,不需要options参数

将数据通过管道输送到STDIN流上,然后sed编辑器会将指定命令应用到输入流上。

例如使用s命令,将test改为big test

root# echo "This is a test." | sed  's/test/big test/'
This is a big test.

2. 在命令行中输入多个编辑器命令_需要-e指定,命令之间通过分号分隔

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

3. 从文件中读取命令_需要-f指定sed脚本

-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(将替换结果写入文件)。

3. awk或gawk,流编辑器

同sed一样,awk也是一种流编辑器,gawk是GNU根据Unix上的流编辑器awk拓展而来的,需要自己安装。

基本使用awk [options] 'pattern1{action1} pattern2{action2}' filename

options说明:-F:指定输入文件每行的分隔符;-v:赋值一个用户自定义变量

案例实操:

  1. 数据准备

    # sudo cp /etc/passwd ./
    
  2. 输出passwd文件中所有以root开头的行的第七列(注意,只有匹配了pattern的行才会执行action)

    # awk -F: '/^root/{print $7}' passwd
    
  3. 只显示/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 浏览记录的域的个数(该行切割后,列的个数)

切割ifconfig中的IP

在wsl上使用cut方法:ifconfig eth0 | grep "mask" | cut -d" " -f10

在wsl上使用awk方法:ifconfig eth0 | grep "mask" | awk -F " " '{print $2}'

【值得注意的是,awk这里只需要第二位即可,可见前面的空格被自动忽略,通过NF查看,确实如此】

查询sed.txt中空行所在的行号

# awk '/^$/{print NR}' sed.txt

awk中的模式匹配方法:详见参考文献5,Richard Blum的Linux命令行与shell脚本编程第三版的第二十章,正则表达式

sort

基本用法sort[选项][参数]

选项

选项 说明
-n 依照数值的大小排序
-r 以相反的顺序来排序
-t 设置排序时所用的分隔字符
-k 指定需要排序的列

案例实操

  1. 数据准备

    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
    
  2. 按照:分割后的第三列倒序排序

    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}'

reference

[1] linux内存管理(详解) - 知乎 (zhihu.com)

[2] 【尚硅谷】Shell脚本从入门到实战__bilibili

[3] Shell 教程 | 菜鸟教程 (runoob.com)

[4] Tutorial01_shell (sjtu.edu.cn)

[5] Linux命令行与shell脚本编程大全,第三版,Richard Blum 【强烈推荐!】

本文主要参考来源:

猜你喜欢

转载自blog.csdn.net/weixin_43721070/article/details/121708884