系统学习------shell脚本编程

BASH基础

Shell为Linux提供了编程环境
程序 = 指令 + 数据

编程风格:

过程式:以指令为中心,数据服务于命令
对象式:以数据为中心,命令服务于数据
shell是一种过程式编程

过程式编程:
顺序执行
循环执行
选择执行


如何判断shell脚本: 文件中头行: #!/bin/bash 标识

运行脚本方法:
给文件给予可执行权限,通过具体的文件路径执行文件执行

chmod +x xxx.sh 
./path/to/xxx.sh

直接运行解释器,将脚本作为解释器程序的参数运行

bash xx.sh 
[root@node1 ~]# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/

系统运行命令寻找的路径,如果你将脚本文件放入到以上路径下,可以将脚本直接作为命令去执行。

变量

变量命名:

	命名只能使用英文字母,数字,下划线;首字母不能以数字开头
	中间不能够有空格,可以使用_下换线
	不能够使用标点符号(特殊字符)
	不能够使用bash中的关键字

有效的命名:
RUNshell
L_inux
var1

无效的命名:
?var1
user*name


赋值

将语句给变量赋值
for file in “ls /etc/”
for file in $(ls /etc/)

[root@node1 ~]# for file in "ls /tmp/";do echo $file;done

将文件路径/tmp/下的所有文件赋给变量file,do…;done之间是循环体。
该条命令是循环显示/tmp/下的文件名。

使用变量:
定义变量: varname=“value”
使用变量: echo $varname | echo ${varname}
建议:使用{}作为边界

只读变量:
readonly关键字可以将变量作为只读变量,变量无法被修改

[root@node1 ~]# vim 1.sh

在里面写入

#!/bin/bash 
url="www.baidu.com"
echo ${url}
url="www.google.com"
echo ${url}

把"www.baidu.com"赋值给变量url,使用变量
再把"www.google.com"赋值给变量url,使用变量
然后给1.sh执行权限

[root@node1 ~]# chmod +x 1.sh

接下来直接执行" ./1.sh ",会输出

"www.baidu.com"
"www.google.com"

如果将1.sh修改为

#!/bin/bash 
url="www.baidu.com"
echo ${url}
readonly url
url="www.google.com"
echo ${url}

就会输出两次"www.baidu.com"并报错,因为url是只读变量了所以不能被修改。

删除变量:unset关键字

#!/bin/bash 
url="www.baidu.com"
echo ${url}
unset url
echo ${url}

此时执行" ./1.sh “,第一次输出"www.baidu.com”,第二次为空。

变量的种类:

本地变量:生效范围仅作为当前shell进程(其他shell或者子shell无法使用)
例如将a=1,echo$a就是1;然后bash进入子shell,再echo $a就是空值。

[root@node1 ~]# a=1
[root@node1 ~]# echo ${a}
1
[root@node1 ~]# bash	# 进入到子shell中
[root@node1 ~]# echo ${a}

环境变量:生效范围为当前shell进程及子进程
变量声明方式1: export varname=“value”
变量声明方式2: declare -x varname=“value”
bash当中有很多内建的环境变量: PATH SHELL

局部变量:生效范围为当前shell进程中某代码片段(通常函数)

位置变量:1,2,3…来表示,让脚本在脚本片段中调用通过命令行传递给他的参数。
用法: ./xx.sh 参数1 参数2 参数3 …

	调用:
		$1,$2,$3: 对应的是参数1,参数2,参数3....
		$0: 对应是命令本身
		$*: 传递给脚本的所有参数(把所有参数当成一个整体)
		$@: 传递给脚本的所有参数 
		$#: 传递给脚本的参数个数

案例1:

[root@node1 ~]# cat 1.sh 
#!/bin/bash 
echo ${1}
echo ${2}
echo $0
echo $*
echo $@
echo $# ·
[root@node1 ~]# ./1.sh Linux Python
Linux
Python
./1.sh
Linux Python
Linux Python
2

案例2:判断所给文件的行数
判断anaconda-ks.cfg文件的行数

[root@node1 ~]# wc -l anaconda-ks.cfg | awk '{print $1}'
48
[root@node1 ~]# cat linecount.sh 
#!/bin/bash
lines=`wc -l $1 | awk '{print $1}'`
echo "This file have ${lines} line"

特殊变量:? 0 * @ $ # 等等

shell数组

语法格式:

定义:array_name=(value1 value2 …)
使用:array_name[0]

示例1:
array_1=(A B C)
array_1[0]=A
array_1[1]=B
array_1[2]=C

读取数组: ${array_name[index]}
修改数组中的元素:
array_1[index]=N_value

读取数组中的所有元素:
${array_name[*]}
${array_name[@]}

读取数组中的所有元素个数:
${#array_name[*]}
${#array_name[@]}

实例:

#!/bin/bash
array_1=(A B C D)
echo ${array_1[0]}
echo ${array_1[3]}
echo ${array_1[*]}
echo ${array_1[@]}
echo ${#array_1[*]}
echo ${#array_1[@]}

shell的环境配置

bash配置文件

  • 生效范围分类:
    全局配置:/etc/profile /etc/profile.d/* /etc/bashrc
    个人配置: ~/.bashrc ~/.bash_profile

功能:

  1. 用于定义环境变量
  2. 运行命令或脚本
  • 功能分类:
  1. profile类:为交互式的shell提供配置
    全局配置:/etc/profile /etc/profile.d/*
    个人配置:~/.bash_profile
  2. bashrc类:为非交互式的shell提供配置
    全局配置:/etc/bashrc
    个人配置:~/.bashrc
    功能: 定义本地变量
  • shell登录:
    交互式: su - Username
    /etc/profile -> /etc/profile.d/* -> ~/.bash_profile
    非交互式: su Username
    ~/.bashrc -> /etc/bashrc

      编辑环境配置文件:
      	定义的新设置立即生效: 
      		1. 重新启动shell
      		2. source命令  -> source /etc/profile 
    

bash算数运算符

算数运算符: + - * / …
增强型: += -= *= /= %=

使用算数符:
(1)let varname=算数表达式
(2)varname=$[算数表达式]
(3)varname=$((算数表达式))
(4)varname=$( expr arg1 arg2 arg3 …)

Note:
乘法符号* 有些情况下需要转义 *
bash中有个内建随机生成器: $RANDOM

练习题

  1. 计算/etc/passwd文件中第10行的用户ID和第20行的用户ID和
#!/bin/bash 
id1=`cat /etc/passwd | head -n 10 | tail -1 | awk -F: '{print $3}'`
id2=`sed -n '20p' /etc/passwd | cut -d: -f3`
let sum_id="id1+id2"
echo $sum_id

在这里插入图片描述
head -n 10是前十行,再加上tail -1才是第十行。然后用awk命令过滤,awk -F指定‘:’作为分隔符打印第三个字段,即11。

sed -n '20p’打印文件的第二十行,cut -d指定以‘:’为分隔符,-f3为提取第个三域。


  1. 传递两个文件文件参数给脚本,计算这两个文件之中所有空白之和
#!/bin/bash 
blank1=`cat $1 | grep "^$" | wc -l`
blank2=`cat $2 | grep "^$" | wc -l`
let sum_blank=blank1+blank2
echo $sum_blank

其中“$1”“$2”是传入的文件名参数,执行脚本文件是就可以写成:
./sum_blank.sh 文件名1 文件名2

grep引号中的第一个字符^和最后一个$:
^: 表示字符串开始。
$: 表示字符串结束。
grep "^$"表示精确匹配文件中的空格

wc指令我们可以计算文件的Byte数、字数、或是列数,wc -l只显示行数。

  1. 统计/etc/,/var/,/usr/目录下所有一级目录和文件之和
#!/bin/bash 
sum_etc=`ls /etc/ | wc -l`
sum_var=`ls /var/ | wc -l`
sum_usr=`ls /usr/ | wc -l`
let sum_all="sum_etc+sum_usr+sum_usr"
echo $sum_all

条件测试

bash条件测试:
判断某些需求是否满足,需要由测试机制来实现
专用的测试表达式需要由命令辅助完成测试过程

测试命令: test Experssion

  • 测试类型:
    数值测试
    字符串测试
    文件测试

数值测试:
-gt 是否大于
-ge 是否大于等于
-eq 是否等于
-ne 是否不等
-lt 是否小于
-le 是否小于等于

字符串测试:
== : 是否等于
> : 是否大于
< : 是否小于
!=: 是否不等于
=~ : 左侧字符串是否能被右侧的Pattern匹配
Note: 此表达式一般用于 [[]]中
-z “String” : 测试字符串是否为空,空则为真;非空则为假
-n “String” : 测试字符串是否不空,不空为真;空则为假

文件测试:
简单存在性测试:
-a file: 文件存在则为正,不存在则为假
存在及类型测试:
-b file: 是否存在且为块设备文件
-c file: 是否存在且为字符设备文件
-d file: 是否存在且为目录文件
-f file: 是否存在且为普通文件
-h file: 是否存在且为符号链接文件(-l 可以)
-p file: 是否存在且为管道文件
-s file: 是否存在且为socket文件

文件权限测试:
-r file: 是否存在可读权限
-w file:是否存在可写权限
-x file:是否存在可执行权限

文件特殊权限测试:
-g file: 是否存在且拥有sgid权限
-u file: 是否存在且拥有suid权限
-k file: 是否存在且拥有sticky权限

文件大小测试:
-s file: 是否存在且非非空
文件是否打开测试:
- fd : fd表示文件爱你描述是否已经打开且与终端相关
-N file: 文件自动上一次读取之后是否被修改过
-O file:当前用户是否为文件属主
-G file:当前用户是否为文件数组

双目测试:
file1 -ef file2: file1与file2是否指向同一个设上的相关inode
file1 -nt file2: file1是否新于file2
file1 -ot file2: file1是否旧于file2

组合测试条件:

  • 第一种方式: && ||
    && 全真则为真
    || 有真则为真
  • 第二种方式:
    -a -> && -> experssion1 && experssion2
    -o -> || -> experssion1 || experssion2
    ! Experssion

bash退出码
脚本中一旦遇到exit命令,脚本会立即终止,终止退出码取决于exit命令后面的数字
如果脚本中未给出退出码,整个脚本的退出码由最后脚本中最后一行命令的执行结果决定

exit N

练习1: 接收一个文本路径作为参数,如果参数个数小于1,则提示用户“至少给1个参数”,并立即退出

[ $# -lt 1 ] && echo "at least one args ...." && exit 1 

$#判断传给脚本的参数个数,小于1就打印“at least one args”并退出,退出码为1。

 [ $# -gt 1 ] && echo "test ok  ...." && exit 0

于是我们可以根据退出码知道执行情况。

Note: 使用条件测试的时候;[ experssion ]; 条件测试表达式experssion与中括号两边是有空格的

选择执行:

语法:

  • 第一种:
    if 判断条件;then
    条件为真的执行代码块
    fi

  • 第二种:
    if 判断条件;then
    条件为真的执行代码块
    else
    条件为假的执行代码块
    fi

  • 第三种:
    if 判断条件1;then
    条件为真的代码块
    elif 判断条件2;then
    条件1为假;条件2为真的执行代码块
    else
    条件12都为假的执行代码块
    fi

练习1: 判断两个数是否相等

#!/bin/sh

set num1 = $1
set num2 = $2

#这个判断如何写?
if test $num1 -eq $num2
then
echo "num1 is equal to num2"
else
echo "num1 is not equal to num2"
fi;

在这里插入图片描述
练习2: 判断用户是否存在,如果不存在添加用户并设置密码和用户名相同;如果存在立即退出;退出状态码为0

#!/bin/bash  

#定义函数  
Find_u(){  
#判断输入值是否为空,如果为空,则函数结束,返回值1  
[ -z $1 ] && return 1  
#判断用户是否存在,存在则显示要求,不存在,函数结束,返回值1  
if id $1 &> /dev/null ;then 
    echo "$1 UID is `id -u $1`"
    echo "$1 Shell is `grep "^$1:" /etc/passwd \
    |cut -d':' -f7 `"  
else
    return 1  
fi  
}  
#循环执行,以符合题目要求  
while :;do
read -p "Please input A username[quit to exit]: " User 
    if [ $User = quit ];then 
        exit 0  
    else
        Find_u $User 
        Res=$?  
        [ $Res -eq 1 ] && echo "No such $User."
    fi  
done

在这里插入图片描述
知识点: 命令的使用 -> 条件测试 + 选择执行结构 + 状态码

用户交互

read命令

-a : 将内容读取写入到数组中

[root@localhost ~]# read -a array_test1
hello linux test
[root@localhost ~]# echo "get ${#array_test1[*]} values in array"
get 3 values in array

-d : 表示定界符;
-e : 只用于互相交互的脚本
-n : 用于限定最多可以有多少个字符有效读入


-p : 用于给出提示符
echo -n "please input value into array_test "
read -a array_test
或直接
read -p “please input value into array_test…” array_test

示例

[root@node1 ~]# cat read.sh 
#!/bin/bash 
read -p "please input one number ......" number1		
echo $number1

其中number1是用于接收用户输入的。


-r : 特殊字符生效
-s :对于一些特殊字符不打印的情况
-t :表示等待输入的时间时长

练习1: 输入姓名之后,进行输出

#!/bin/bash 
read -p "Input your name" name 
echo ${name}

练习2:输入一个文件判断文件类型,判断输入文件是否为目录文件;是则输出yes;否则直接退出

#!/bin/bash 
read -p "please input your test file ...." file 
	if [ -d file ];then 
		echo "this is a directory file ...."
		exit 0 
	else
		echo "this is not a directory file ..."
		exit 1
	fi 

运用条件测试中的具体命令可解决类似题目,可到上文条件测试去看具体命令。

循环结构

循环体: 需要执行的语句,可能执行n遍

for循环

语法:

	for 变量名 in 列表;do 
		循环体
	done 

执行机制:依次将列表中的元素赋值给变量名然后去执行一遍循环体;当列表中的元素耗尽时,退出

示例:

[root@node1 ~]# for i in `ls /tmp/`;do echo $i;done

循环输出/tmp/下的文件名

[root@node1 ~]# for i in {20,30};do ping -c4 192.168.10.$i;done 

分别循环ping 192.168.10.20 和 192.168.10.30 ,输出4行

[root@node1 ~]# for i in `seq 1 10`;do echo $i;done

seq 数字生成器:m到n数字

练习题1: 创建用户user1-user10家目录,并且在user1-user10家目录下创建1.txt - 10.txt文件内容,输出格式为
练习题2: 列出/var/目录下各个子目录所占磁盘大小

while循环

语法:

while 条件测试;do
	循环体
done 

执行机制: 当条件测试为真是就执行一遍循环体;为假时退出循环

经典使用:读取文件中的内容
写法1:

#!/bin/bash 
while read linecontext;do
	echo "+++$linecontext"
done < /root/test.txt

写法2:

cat $1 | while read linecontext
do
	echo $linecount 
done 

until循环

执行机制:条件为假时执行循环体;条件为真时退出
语法:

until 条件测试;do
	循环体
	
done 

函数

函数定义:

function Fun_name(){
	函数体
	返回值
}

调用函数:Fun_name

示例1

#!/bin/bash 
function sum_b(){
  blank1=`cat $1 | grep "^$" | wc -l`
  blank2=`cat $2 | grep "^$" | wc -l`
  let sum_blank=blank1+blank2
  echo $sum_blank
  return 0
}
#调用函数 传入参数
sum_b $1 $2

返回值的获取: 可以通过 echo $?来获取

总结

shell脚本编程要对命令熟悉,通过对命令的组合进行shell编程。
shell脚本应用:通过shell成当前资源(cpu disk vm …)的使用情况 每天都需要统计。

发布了49 篇原创文章 · 获赞 6 · 访问量 3687

猜你喜欢

转载自blog.csdn.net/weixin_46097280/article/details/104856719