Linux之shell编程

这是我学习的一些笔记,环境Centos7系统,一起来看看吧!

一、概述

简单来讲Shell 是一种命令行解释器,为用户与操作系统内核之间的交互提供接口。它允许用户通过输入命令来执行各种操作,如文件管理、程序运行和系统配置等。

常见类型(来自deep seek - v3)

  • Bash (Bourne Again Shell):Linux 和 macOS 的默认 Shell。
  • Sh (Bourne Shell):早期的 Unix Shell。
  • Zsh (Z Shell):功能强大,支持插件和主题。
  • Ksh (Korn Shell):结合了 Bourne Shell 和 C Shell 的特性。
  • Csh/Tcsh:语法类似 C 语言,适合 C 程序员。

我们也可以在/etc/shells里面看到这些常见的解释器

二、shell脚本入门

1.写一个”helloworld“

首先创建一个.sh文件hello.sh,然后用vim打开编辑。这里我在test目录里面创建这个文件

    [root@localhost ~]# mkdir test
    [root@localhost ~]# cd test/
    [root@localhost test]# touch hello.sh
    [root@localhost test]# vim hello.sh

使用vim编辑,第一行为指定解析器,在本系统下的默认的shell就是bash

2.执行.sh文件

执行脚本的方式有很多

第一种:bash或者sh+脚本的相对或绝对路径(不需要给文件+x权限,这里文件相当于参数)

第二种(常用):直接使用脚本的绝对或相对路径执行,此时需要给文件增加x执行权限,否则

那我们增加权限

之后再来执行shell文件发现可以了,可以使用绝对和相对路径,但是不能直接使用文件名执行,如果要使用,需要加上 ./

第三种:使用source或者 . 命令加相对或绝对路径执行

第三种里面的 . 和第二种里面的 ./ 有本质上的区别,下面是我查到的截图

三、变量

变量名由字母,数字和下划线组成,不能以数字开头,建议大写

1.系统预定义变量

常见的系统预定义变量:$HOME,$PWD,$SHELL,$USER等

可以使用env命令,或者printenv命令查看当前的全局变量都有什么。可以看到x还是很多的。

printenv命令还可以直接跟一个变量名称单独查看这个变量是什么。而且不需要加$符号。

[root@localhost ~]# env
XDG_SESSION_ID=1
HOSTNAME=localhost.localdomain
SELINUX_ROLE_REQUESTED=
TERM=xterm
SHELL=/bin/bash
HISTSIZE=1000
SSH_CLIENT=192.168.1.1 2097 22
SELINUX_USE_CURRENT_RANGE=
SSH_TTY=/dev/pts/0
USER=root
还有很多......

[root@localhost ~]# printenv HOME
/root
[root@localhost ~]# 

而set命令则可以查看系统当前的所有变量,可以试一下有很多

2.自定义变量

基本语法:变量名=变量值,要注意的是等号两边不能有空格,如果是有空格的文字,要加上引号

 要注意的是,自己定义的变量,默认是局部变量,可以使用export命令使其变成全局变量

我们来做一个测试,首先定义一个变量,然后在之前的hello.sh里面echo这个变量,会发现第二行是空的,当使用export以后就有了结果。

3.自定义只读变量

使用readonly命令定义只读变量

4.撤销变量

基本语法:unset 变量名 (只读变量不能unset)

我们先用set命令来看一下之前定义的变量在哪里

[root@localhost test]# set | less

 找到以后用unset分别撤销变量a和b

 很明显只读变量b不可以被撤销。再去set一下可以看到a没了

5.特殊变量

5.1 $n

n为数字,$0表示文件名,$1~$9代表第1~第9个参数,两位数以上用 ${10} 的形式表示

首先在hello.sh文件里面加上$n,$1和$2未给参数时是空白,给参数后就是对应的值

5.2 $#

获取所有输入参数的个数,我们在之前的文件里面加上这样一句话,然后可以看到结果(要想把字符串里面的$看成一个普通字符,要用''单引号)

5.3 $*和$@

两个都表示获取命令行中的所有参数,不同的是$*会把这些参数看作一个整体,$@是分开的。我们测试一下,在hello.sh文件增加两条语句。结果如下,虽然结果一样,但是有本质区别。

5.4 $?

是一个返回值,表示最后一次执行命令的返回状态,0表示正确,非0表示错误,下图使用ls命令正确,则查看$? 结果是0表示正确,aa命令错误,就返回127,cd hello.sh错误,就返回1。

四、运算符

1.基本操作

基本语法:$((运算式子))或者$[运算式子]

赋值操作

2.Linux自带运算命令

此外Linux本身还有一个命令叫expr,但是相比于shell的运算而言,要麻烦很多,比如使用expr命令要加空格,执行乘法时要做转义,所以expr命令并不经常使用。

使用expr的方式赋值给a,有两种方式

3.add.sh练习

我们来做一个add的shell,要注意给add文件加x权限

[root@localhost test]# touch add.sh
[root@localhost test]# vim add.sh 
[root@localhost test]# ./add.sh
-bash: ./add.sh: 权限不够
[root@localhost test]# chmod +x add.sh 
[root@localhost test]# ./add.sh 12 34
sum=46
[root@localhost test]# 

 五、条件判断

1.基本操作

基本语法:test 表达式或者使用中括号[ 表达式 ],得到的结果要用$?检查,0表示真非0表示假。注意在表达式中间加空格,如果不加空格,Linux会认为这是一串字符,可以看下面的例子

[root@localhost test]# a=hello
[root@localhost test]# test $a=hello
[root@localhost test]# echo $?
0
[root@localhost test]# test $a=hellx
[root@localhost test]# echo $?
0

 可以看到如果不加空格,那么结果都是0,很明显是不对的,加上空格以后才正确

[root@localhost test]# test $a = hellx
[root@localhost test]# echo $?
1
[root@localhost test]# test $a = hello
[root@localhost test]# echo $?
0
[root@localhost test]# 

 在使用中括号方式时,记得在式子两边也加上空格,否则会报错

[root@localhost test]# [$a = hello]
bash: [hello: 未找到命令...
[root@localhost test]# [ $a = hello ]
[root@localhost test]# echo $?
0
[root@localhost test]# [ $a = hellx ]
[root@localhost test]# echo $?
1

2.常见条件判断

2.1 两个整数之间的比较

在Linux里面大于号>和小于号<由其他用法,因此整数之间的比较有相对的语法

-eq等于
-ne不等于
-lt小于
-le小于等于
-gt大于
-ge大于等于

还有字符串之间的相等还是不相等直接用=号和!=号就可以

2.2文件权限判断

-r 有读权限

-w有写权限

-x 有执行权限

2.3文件类型的判断

-e 文件是否存在

-f 文件存在且是普通文件

-d 文件存在且是目录

3.几个案例

3.1 判断大小

3.2 判断hello.sh文件是否具有执行权限

3.3 判断/root/test里面是否存在hello.sh文件并判断这个文件是否是目录
3.4 使用 && 和 || 多条件判断

 六、流程控制

1.if

1.1 单分支

基本语法:then可以在下一行,也可以在分号后面和if同行,以fi结尾

if [ 条件判断 ];then 
    程序
fi

或者

if [ 条件判断 ]
then 
    程序
fi

一个小例子:创建if.sh文件写上一个判断语句

给文件加可执行权限,执行这个文件,可以看到当参数未root时正确输出yes,但是如果没有参数会有一个提示。

 我们经常做一些处理来避免这个提示的出现。像这样,之后再执行时没有参数也不会有提示了

 1.2 多分支

基本语法:

if [ 条件判断 ]
then 
    程序
elif [ 条件判断 ]
then
    程序
else
    程序
fi

 一个例子:修改if.sh文件

2.case

基本语法:值的引号可以不写,但是右括号是必须的,结束一个分支要用两个分号,case语句结束时esac

case $变量名 in
"值1")
    如果变量名等于值1则执行此语句
;;
"值2")
    如果变量名等于值2则执行此语句
;;
......
*)
    以上都不满足则执行此语句
;;
esac

一个例子vim一个case.sh文件

3.for

3.1 格式1

基本语法:

for (( 初始值;循环控制条件;变量变化 ))
do
    程序代码
done

 一个例子:vim一个for1.sh,这里之所以可以用<=来比较是因为for循环使用了(())的原因

3.2 格式2

基本语法:第二种格式更常用

for 变量 in 值1 值2 值3......
do
    程序
done

 一个例子,vim一个for2.sh,三个循环代码和结果如下,使用{1..10}表示从1到10

4.while

基本语法

while [ 条件判断 ]
do
    程序
done

做一个测试,我们vim一个while.sh文件,结果如下。在shell里面只要在命令最开始加上let,之后就可以按照普通编程的方式书写,如果你不习惯shell这种书写方式,可以试试

七、读取控制台输入

基本语法:read (选项) (参数) 

选项:可以跟 -p:指定读取时的提示符,-t:表示等待时间,不加-t则一直等待

参数:就是变量,也就是指定读取的变量名

一个例子:vim一个read.sh,记得加上可执行权限

 八、函数

1.系统函数

事实上Linux里面的所有命令都可以看成是系统函数,在shell脚本里面可以使用$()的方式调用所有的系统命令,下面介绍两个比较常见的系统函数

1.1 basename

基本语法:basename [string/pathname][suffix],这个命令的功能类似字符串的裁剪,只保留最后一个\后面的那个字符串,可以理解为截取路径的文件名称。

[suffix]为后缀,如果写了指定的后最,就表示在截取时去掉后缀

写一个例子:basename.sh获取文件名,注意如果命令写到shell里面,要用$()把命令包裹起来,$0,表示文件名,但是如果只是单纯的$0,会连路径一起显示出来,使用basename可以截取

 1.2 dirname

基本用法:dirname 文件路径,其作用是截取路径中最后一个/前面的内容,与basename正好相反

一个例子:在basename文件中加上两条语句,cd是为了保证路径的完整,pwd显示当前目录

2.自定义函数

基本语法:其中function,()以及最后的return可以省略

注意事项:和其他语言不同shell里面的函数在调用之前必须先声明。返回值通过$?来获取,如果末尾没有写return,则$?得到的是最后一条命令的结果。return后可以跟0~255。

function funname()
{
    程序;
    return int;
}

一个例子:做一个加法函数

改进函数,使用返回值的方式计算,但是由于return只能返回0~255,所以有个技巧是不用return,而是用echo返回

综合案例:归档

vim一个daily_archive.sh

#!/bin/bash
# 首先判断输入参数个数是否唯一
if [ $# -ne 1 ]
then
        echo "参数个数错误!应该输入一个参数,作为归档目录名"
        exit
fi
# 从参数中获取目录名称
if [ -d $1 ]
then
        echo
else
        echo
        echo "目录不存在"
        echo
        exit
fi
DIR_NAME=$(basename $1)
DIR_PATH=$(cd $(dirname $1); pwd)
# 获取当前日期
DATE=$(date +%y%m%d)
# 定义生成的归档文件名
FILE=aechive_${DIR_NAME}_$DATE.tar.gz
DEST=/root/archive/$FILE
# 开始归档目录文件
echo "开始归档..."
echo
tar -czf $DEST $DIR_PATH/$DIR_NAME
# 判断结果
if [ $? -eq 0 ]
then
        echo
        echo "归档成功"
        echo "归档文件为:$DEST"
        echo
else
        echo "归档失败"
        echo
fi
exit

 九、正则表达式入门

用单个字符串来描述、匹配一系列符合某个语法规则的字符串

1.常规匹配

不包含任何特殊字符的匹配

2.常用特殊字符

2.1 特殊字符:^

这个符号是英文状态下的shift+6,表示一行的开头,^后面可以跟多个字符表示以这个开头

2.2 特殊字符:$

表示一行的结尾,$符号要写在最后面,例如cat /etc/passwd | grep bash$,表示以bash结尾,^和$可以一起使用,那么^abc$,就是直接匹配abc这个字符串,^$中间啥都不写表示匹配空行

2.3 特殊字符:.

一个 .表示一个字符,例如:cat /etc/passwd | grep r..t

 2.4 特殊字符:*

不单独使用,和前一个字符连用,表示同一个出现任意次,也可以是0次例如:ro*t。如果.*连用则表示任意字符例如:^a.*in$

 2.5 字符区间:中括号[ ]
[1,2]或[12]表示匹配1或者2
[0-9]表示匹配一个0-9的数字
[0-9]*表示任意长度的数字字符串
[a-z]表示一个a-z之间的字符
[a-z]*表示任意长度的字母字符串

 例如:cat /etc/passwd | grep r[a-z]*t

2.6 特殊字符:\

就是转义字符,用来匹配那些特殊的字符,例如:cat daily_archive.sh | grep '\$' 匹配$符,注意用单引号包裹起来

十、文本处理工具

1.cut

可以用来剪切数据,从文件的每一行剪切字节、字符和字段并输出。

基本语法:cut [选项参数] filename

常用的选项参数说明
-f列号,提取第几列,逗号分隔截取多列,连续的用列1-10这种形式,10-表示第10列之后,-10表示第10列之前
-d分隔符,按照指定分隔符分割,默认是制表符 "\t"
-c按照字符分割,后加n表示几个字符

例子:先创建一个cut.txt文件,写一些数据

截取第一列: cut -d " " -f 1 cut.txt

截取第二三列: cut -d " " -f 2,3 cut.txt

尝试截取/etc/passwd的自定义用户,已知自定义用户登录终端以bash结尾,先看一下有多少条信息。然后我们来截取它的用户名,家目录和登录终端。也就是第1,6,7列。以冒号分隔。

尝试截取ifconfig里面的IP地址:ifconfig | grep netmask | cut -d " " -f 10 (10表示在第10列,因为按照空格分隔前面是有9个空格的)

 2.awk

文本分析工具,把文件逐行的读入,默认以空格为分隔切割,然后再对切割的内容进行处理

2.1基本用法

awk [选项参数] ' /pattern1/{action1} /pattern2/{action2}...' filename

pattern:表示awk在数据中查找的内容,就是匹配模式

action:在找到匹配内容时所执行的一系列命令

选项参数功能
-F指定输入文件分隔符
-v赋值一个用户定义变量

 案例1:搜索passwd文件以root开头的所有行,并输出该行的第7列

[root@hostname test]# cat /etc/passwd | awk -F ":" '/^root/{print $7}'
/bin/bash

案例2:搜索passwd文件以root开头的所有行,并输出该行的第1列和第7列,以逗号分隔

[root@hostname test]# cat /etc/passwd | awk -F ":" '/^root/{print $1","$7}'
root,/bin/bash

 案例3:只显示passwd的第1列和第7列,以逗号隔开并在最前面加一句user shell,在最后一行添加end of file

[root@hostname test]# cat /etc/passwd | awk -F ":" 'BEGIN{print "user shell"} {print $1","$7} END{print "end of file"}'
user shell
root,/bin/bash
bin,/sbin/nologin
...
tcpdump,/sbin/nologin
zwq,/bin/bash
zhangwenqi,/bin/bash
end of file
[root@hostname test]#

案例4:搜索passwd文件以a开头的所有行,并给用户id数值+1

[root@hostname test]# cat /etc/passwd | awk -F ":" '/^a/{print $3}'
3
173
70
[root@hostname test]# cat /etc/passwd | awk -v i=1 -F ":" '/^a/{print $3+i}'
4
174
71
2.2awk内置变量
变量说明
FILENAME文件名
NR已读的记录数也就是行号
NF浏览记录的域的个数也就是切割后的列数

案例1:统计passwd文件名,行号,列数

[root@hostname test]# awk -F ":" '{print "文件名:" FILENAME "行号:"NR "列号:" NF}' /etc/passwd
文件名:/etc/passwd行号:1列号:7
文件名:/etc/passwd行号:2列号:7
文件名:/etc/passwd行号:3列号:7
文件名:/etc/passwd行号:4列号:7
......
文件名:/etc/passwd行号:42列号:7
文件名:/etc/passwd行号:43列号:7
文件名:/etc/passwd行号:44列号:7
文件名:/etc/passwd行号:45列号:7

 案例2:找到ifconfig命令内容的空行

[root@hostname test]# ifconfig | awk '/^$/{print NR}'
9
18
26

案例3:找到ifconfig命令内容的IP地址

[root@hostname test]# ifconfig | awk '/netmask/{print $2}'
192.168.1.129
127.0.0.1
192.168.122.1

综合案例:发送消息 

写一个脚本,利用Linux自带的mesg和write工具,向用户发送消息。输入的用户名作为第一个参数,后面跟着要发送的消息。同时脚本检查用户是否登录在系统中,是否打开消息功能,以及当前消息是否为空。

1.mesg命令

首先mesg命令分别查看不同用户该功能是否开启,可以看到两个用户都是开启的

也可以用who -T命令直接在一个里面查看所有用户是否开启mesg功能,+号表示开启

使用mesg n/y 命令可以关闭或者开启该功能

2.write命令

发送消息,write后跟用户和对应的终端,只能发送不能删除,CTRL+C退出发送

接收消息,当对方结束发送后以EOF结束,按回车就可以了

 3.写一个脚本

把上述操作综合起来,用脚本的方式更方便,vim一个send_mesg.sh文件

#!/bin/bash

#查看用户是否登录
#-i表示忽略大小写,-m表示最大匹配行数 前一个$1是参数,后一个$1是打印第一列
login_user=$(who | grep -i -m 1 $1 | awk '{print $1}')
#-z 表示是否为空
if [ -z $login_user ]
then
        echo "$1 不在线!"
        echo "脚本退出..."
        exit
fi
#查看用户是否开启消息功能
is_allowed=$(who -T | grep -i -m 1 $1 | awk '{print $2}')
if [ $is_allowed != "+" ]
then
        echo "$1 没有开启消息功能!"
        echo "脚本退出..."
        exit
fi
#确认是否有消息发送,这里$2是第二个参数,也就是要发送的消息
if [ -z $2 ]
then
        echo "没有消息发送"
        echo "脚本退出..."
        exit
fi
#从参数中获取要发送的消息
#$*表示文件名,cut后面表示以空格为分隔,取第二列及以后
whole_msg=$(echo $* | cut -d " " -f 2-)
#获取用户登录的终端
user_terminal=$(who | grep -i -m 1 $1 | awk '{print $2}')
#写入要发送的消息
echo $whole_msg | write $login_user $user_terminal
#判断发送成功没有
if [ $? != 0 ]
then
        echo "发送失败!"
else
        echo "发送成功!"
fi

exit

全文来自对 Shell编程_哔哩哔哩_bilibili 的总结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值