shell脚本编程基础
bash中变量的种类
局部变量:生效范围为当前shell进程,对当前shell之外的其它shell进程,包括当前shell的子shell进程均无效
环境变量:生效范围为当前shell进程及其子进程
本地变量:生效范围为当前shell进程中某代码片段,通常指函数
位置变量:$1,$2,...来表示,用于脚本中调用命令行传递的参数
特殊变量:$?, $0, $*, $@, $#,$$
[root@centos7 ~]# echo $$
4766
[root@centos7 ~]# echo $PPID
4760
[root@centos7 ~]# pstree -p |grep sshd
|-sshd(1066)-+-sshd(1749)---bash(1764)---vim(8386)
| `-sshd(4760)---bash(4766)-+-grep(8672)
局部变量
变量赋值:name=value
变量引用:name="root" name="$USER" name='command' name=$(command)
显示已定义的所有变量:set
删除变量:unset name
[root@centos6 ~]# a=root
[root@centos6 ~]# echo "$a" '$a'
root $a
环境变量
变量赋值:export name=value declare -x name=value
变量引用:$name ${name}
显示所有环境变量:env export
删除变量:unset name
只读和位置变量
只读变量
只能声明,但不能修改和删除,只对当前登录用户有效,退出重进就失效了
声明只读变量: readonly name
查看只读变量:readonly -p
位置变量
在脚本代码中调用命令行传递给脚本的参数
$1, $2, ...:对应第1、第2等参数,shift [n]换位置
$0:位置变量中命令本身
$*:位置变量中传递给脚本的所有参数,全部参数合为一个字符串
$@:位置变量中传递给脚本的所有参数,每个参数为独立字符串
$#:位置变量中传递给脚本的参数的个数
set --:清空所有位置变量
[root@centos6 app]# ./a.sh {0..9}
$1 is:0
$2 is:1
$3 is:2
$4 is:3
$0 is:./a.sh
$10 is:9
$* is:0 1 2 3 4 5 6 7 8 9
$# is:10
()和{}
()的作用:在当前shell下开启一个新空间,shell进程号未变,小括号里面的变量不影响外面,外面的变量可以影响小括号里面。 加小括号的意思是执行一次小括号里面的命令就完事了,所以将来编脚本的时候如果需要用到一次性的任务,不想影响周边的环境,就可以用小括号写法
{}的作用:不开启子shell,将影响当前的shell环境,注意{}的写法,前后有空格,且最后有分号
退出状态
进程使用退出状态来报告成功或失败
0代表成功,非0代表失败
$?:上一次命令执行的情况
exit #:自定义退出状态码,如果未给脚本指定退出状态码,整个脚本的退出状态码取决于脚本中执行的最后一条命令的状态码
[root@centos6 app]# ping -c1 -W1 172.18.0.1 &> /dev/null ;echo $?
0
[root@centos6 app]# ping -c1 -W1 172.18.0.300 &> /dev/null ;echo $?
2
[root@centos6 app]# ping -c1 -W1 172.18.0.200 &> /dev/null ;echo $?
1
bash中运算和赋值
算数运算
bash中的算数运算:+,-,,/,%(取余数),*(乘方)
let var=算数表达式
var=$[算数表达式]
var=$((算术表达式))
declare –i var=数值
echo '算术表达式' |bc
$RANDOM(0-32767):echo $[$RANDOM%50]
逻辑运算
与(且):第一个为假,结果必定为假;第一个为真,第二个必须要参与运算
或:第一个为真,结果必定为真;第一个为假,第二个必须参与运算
非:!
异或^:异或的两个值,相同为假,不同为真
赋值
var+=1:自加1
var++:自加1
var-=1:自减1
var--:自减1
read
从stdin中读取值,给变量赋值,stdin多余的内容会都赋值给最后一个变量,常用选项:
- -p:指定要显示的提示
- -s:静默输入,一般用于密码
- -n #:指定输入的字符长度
- -d 'string':指定输入结束符
- -t number:指定超时时间,单位为秒
[root@centos6 ~]# read a b c
nihao helloworld ccc ddd eee
[root@centos6 ~]# echo "$a|$b|$c"
nihao|helloworld|ccc ddd eee
[root@centos6 ~]# read -p '请输出密码:' passwd
请输出密码:centos
[root@centos6 ~]# echo $passwd
centos
bash中常用的测试命令
条件测试
每条命令系统都会返回执行布尔值,以便用在条件性执行中:0表示真,1表示假
测试命令:注意方括号首尾都有空格
test experssion
[ experssion ]
[[ experssion ]]
条件性的执行操作符
&&:代表条件性的且
||:代表条件性的或
[root@centos6 ~]# ping -c1 -W2 172.18.0.100 &> /dev/null && echo "Succcess" || echo "False"
Succcess
[root@centos6 ~]# ping -c1 -W2 172.18.0.200 &> /dev/null && echo "Succcess" || echo "False"
False
[root@centos6 ~]# ping -c1 -W2 172.18.0.300 &> /dev/null && echo "Succcess" || echo "False"
False
数值测试
-gt:是否大于
-ge:是否大于等于
-eq:是否等于
-ne:是否不等于
-lt:是否小于
-le:是否小于等于
字符串测试
==:是否等于
>:ascii是否大于ascii
<:是否小于
!=:是否不等于
=~:左侧是否包含右侧,此表达式一般用于[[ ]]中,支持扩展的正则表达式
-z “string”:字符串是否为空,空为真,不空为假
-n “string”:字符串是否不为空,不空为真,空为假
文件测试
-a file:同-e
-e file:是否存在,存在为真,不存在为假
-b file:是否存在且为块设备文件
-c file:是否存在且为字符设备文件
-d file:是否存在且为目录文件
-f file:是否存在且为普通文件
-h/-L file:是否存在且为符号链接文件
-p file:是否存在且为命名管道文件
-S file:是否存在且为套接字文件
文件权限测试
-r file:是否存在且可读
-w file:是否存在且可写
-x file:是否存在且可执行
-u file:是否存在且拥有suid权限
-g file:是否存在且拥有sgid权限
-k file:是否存在且拥有sticky权限
文件属性测试
单文件测试
-s file:是否存在且非空
-t file:是否在某终端已经打开
-N file:文件自从上一次被读取之后是否被修改过
-O:当前登录用户是否为文件属主
-G:当前登录用户是否为文件属组
双目测试
file1 -ef file2:file1是否是file2的硬链接
file1 -nt file2:file1是否新于file2(mtime)
file1 -ot file2:file1是否旧于file2
组合测试条件
[root@centos6 ~]# [ -f /bin/cat ] && echo "Success" || echo "False"
Success
[root@centos6 ~]# [ -g /bin/cat ] && echo "Success" || echo "False"
False
bash中防止扩展
\:转义符,使随后的字符按照原意解释
':单引号,强引用,防止所有扩展
":双引号,弱引用,防止所有扩展,以下除外($:变量扩展、`:命令替换、 \:禁止单个字符扩展、!:历史命令替换)
bash的配置文件
全局配置
/etc/profile
/etc/profile.d/*.sh
/etc/bashrc
个人配置
~/.bash_profile
~/.bashrc
配置文件读取顺序
交互式登录:/etc/profile --> /etc/profile.d/*.sh --> ~/.bash_profile--> ~/.bashrc--> /etc/bashrc
非交互式登录::~/.bashrc--> /etc/bashrc--> /etc/profile.d/*.sh
编辑配置文件生效
重新启动shell进程
./source filename:直接重新读取配置文件
~/.bash_logout
在退出登录shell时执行,可以通过编辑此文件实现退出时自动备份和清理临时文件
练习
-
编写脚本/root/bin/createscripts.sh,输入createscripts.sh /path/newsh.sh,则会在指定路径生成脚本文件,并涵盖注释信息
#!/bin/bash touch $1 echo "# -----------------------------------" >> $1 echo "# Filename: `basename $1`" >> $1 echo "# Revision: 1.0" >> $1 echo "# Date: `date "+%F"`" >> $1 echo "# Author: fanjie" >> $1 echo "# Email: 361163404@qq.com" >> $1 echo "# Website: www.fanjie.com" >> $1 echo "# Description: " >> $1 echo "# -----------------------------------" >> $1 echo "# Copyright: 2018 fan" >> $1 echo "# License: GPl" >> $1 vim $1 chmod +x $1
-
编写脚本/root/bin/systeminfo.sh,显示当前主机系统信息,包括主机名、IPV4地址、操作系统版本、内核版本、CPU型号、内存大小、硬盘大小
#!/bin/bash echo "当前主机系统信息如下: " echo "主机名: `hostname` " echo "IPV4地址: `ifconfig ens33|grep "\(inet\)[[:blank:]]"|tr -s ' '|cut -d' ' -f3`" echo "操作系统版本: `cat /etc/redhat-release |cut -d' ' -f1-4`" echo "内核版本: `uname -r`" echo "CPU型号: `lscpu|grep "^\(Model \)"|tr -s ' '|cut -d' ' -f3-8`" echo "内存型号: `free -m|grep "\(Mem\)"|tr -s ' '|cut -d' ' -f2` MB" echo "硬盘大小: `fdisk -l /dev/sda |grep "^\(Disk /dev/sda\)"|cut -d' ' -f3` GB"
-
编写脚本/root/bin/backup.sh,可实现将/etc/目录备份到/root/etcYYYY-mm-dd中
#!/bin/bash cp -a /etc/ /root/ mv /root/etc /root/etc`date +"%F"`
-
编写脚本/root/bin/disk.sh,显示当前硬盘分区中空间利用率最大的值
#!/bin/bash df |tr -s ' ' %|sort -t% -k5 -nr|head -1|cut -d% -f5
-
编写脚本/root/bin/links.sh,显示正连接本主机的每个远程主机的IPv4地址和连接数,并按连接数从大到小排序
#!/bin/bash cat /var/log/httpd/access_log|cut -d- -f1|sort|uniq -c|sort -t' ' -k1 -nr
-
编写脚本/root/bin/sumid.sh,计算/etc/passwd文件中的第10个用户和第20用户的UID之和
#!/bin/bash UID10=`cat -n /etc/passwd|grep "^\([[:space:]]*10\)"|cut -d: -f3` UID20=`cat -n /etc/passwd|grep "^\([[:space:]]*20\)"|cut -d: -f3` echo $UID10+$UID20 |bc
-
编写脚本/root/bin/sumid.sh,计算/etc/passwd文件中的第x用户和第y用户的ID之和,xy为参数指定
#!/bin/bash UID1=`cat -n /etc/passwd|grep "^\([[:space:]]*$1\)"|cut -d: -f3` UID2=`cat -n /etc/passwd|grep "^\([[:space:]]*$2\)"|cut -d: -f3` echo $UID1+$UID2 |bc
-
编写脚本/root/bin/sumspace.sh,传递两个文件路径作为参数给脚本,计算这两个文件中所有空白行之和
#!/bin/bash space1=`cat $1 |grep "^[[:space:]]*$"|wc -l` space2=`cat $2 |grep "^[[:space:]]*$"|wc -l` echo $space1+$space2 |bc
-
编写脚本/root/bin/sumfile.sh,统计/etc, /var, /usr目录中共有多少个一级子目录和文件
#!/bin/bash etc=$[$(ls -l /etc/ |wc -l)-1] var=$[$(ls -l /var/ |wc -l)-1] usr=$[$(ls -l /usr/ |wc -l)-1] echo $[$etc+$var+$usr]
-
编写脚本/root/bin/hostping.sh,接受一个主机的IPv4地址做为参数,测试是否可连通。如果能ping通,则提示用户“该IP地址可访问”;如果不可ping通,则提示用户“该IP地址不可访问”
#!/bin/bash ping -c1 -W1 $1 &> /dev/null && echo "该IP可访问" || echo "该IP不>可访问"
-
对以上对该脚本进行升级,可以判断ip地址的合法型,如果不合法,直接提示用户"IP地址不合法"并退出
#!/bin/bash
-
编写脚本/root/bin/checkdisk.sh,检查磁盘分区空间和inode使用率,如果超过80%,就发广播警告空间将满
#!/bin/bash use1=`df |tr -s ' ' %|sort -t% -k5 -nr|head -1|cut -d% -f5` [ $use1 -gt 80 ] && echo "磁盘可用空间超过80%" use2=`df -i|tr -s ' ' %|sort -t% -k5 -nr|head -1|cut -d% -f5` [ $use2 -gt 80 ] && echo "inode可用空间超过80%"
-
编写脚本/bin/per.sh,判断当前用户对指定的参数文件,是否不可读并且不可写
#!/bin/bash [ !-r $1 ] && [ !-w $1 ] && echo "this user can't rw"
-
编写脚本/root/bin/excute.sh ,判断参数文件是否为sh后缀的普通文件,如果是,添加所有人可执行权限,否则提示用户非脚本文件
#!/bin/bash echo $1 | grep "\.sh$" && chmod +x $1 || echo "非脚本文件"
-
让所有用户的PATH环境变量的值多出一个路径,例如:/usr/local/apache/bin
export PATH=$PATH:/usr/local/apache/bin 将上述文件写入/etc/profile,/etc/profile.d/*.sh,/etc/bashrc
-
用户root登录时,将命令指示符变成红色,并自动启用如下别名:rm=‘rm –i’
cdnet=‘cd /etc/sysconfig/network-scripts/’
editnet=‘vim /etc/sysconfig/network-scripts/ifcfg-eth0’
editnet=‘vim /etc/sysconfig/network-scripts/ifcfg-eno16777736 或 ifcfg-ens33 ’ (如果系统是CentOS7)PS1='\[\e[1;31m\][\u@\h \W]\$\[\e[0m\]' 将上述文件写入/root/.bashrc或/root/.bash_profile
-
任意用户登录系统时,显示红色字体的警示提醒信息“Hi,dangerous!”
echo -e "\033[31mHi,dangerous\!\033[0m" 将上述文件写入/etc/profile,/etc/profile.d/*.sh,/etc/bashrc
-
编写用户的环境初始化脚本reset.sh,包括别名,登录提示符,vim的设置,环境变量等
#!/bin/bash cat > /etc/profile.d/env.sh << EOF alias cdnet="cd /etc/sysconfig/network-scripts" alias editnet="vim /etc/sysconfig/network-scripts/ifcfg-ens33" # export PS1="[\[\e[31m\]\u\e[0m\]@\h \W]\\$ " export PATH=/app/bin:$PATH EOF cat > ~/.vimrc << EOF set nu EOF