在一些复杂Linux维护工作中,会有大量的重复性工作和交互操作,如果一直用人工的方式操作,不但费时费力,还容易出错,而编写一个shell脚本程序就可以很好的解决这个问题。
shell脚本(Shell Script)就是将一个要执行的命令按顺序保存在一个文本文件中,并给该文件一个可执行权限,方便一次性执行的一个程序文件。
shell脚本主要是方便管理员进行设置或者管理用的。但是它比Windows下的批处理更强大,比用其他编程程序编辑的程序效率更高,它使用了Linux/Unix下的命令。
shell介于操作系统内核与用户之间,充当这“翻译官”的角色,负责接收用户输入的操作指令并进行解释,传递给内核执行,执行输出结果。
我们可以通过查看/etc/shells可以了解当前系统所支持的shell脚本种类。
/bin/bash是目前大多数Linux版本默认的shell脚本。
一个合格的shell脚本程序应该遵循标准的脚本结构,而且能够输出友好的提示信息、更容易让人读懂。
shell脚本的第一行是“#!/bin/bash”,这是一行特殊的脚本声明,表明此后的语句都是用/bin/bash程序来执行;以“#”开头的语句表示注释信息;echo命令用于输出字符串,让脚本更容易让人读懂。
没有经过优化的简单shell脚本
[root@localhost ~]# vi first.sh
#!/bin/bash 声明环境变量
cd /etc 到/etc目录下
pwd 打印当前位置
grep "bash" /etc/passwd | wc -l 筛选出 /etc/passwd文件中含bash的行数
[root@localhost ~]# vi first.sh
#!/bin/bash
cd /etc
echo "当前目录位于:"
pwd
echo "/etc/passwd文件中含有bash的行数为:"
grep "bash" /etc/passwd | wc -l
执行的方式有三种,我以first.sh脚本文件为例,这里脚本文件的绝对路径与相对路径都可以使用。
三种方法的不同点在于:
管道与重定向主要是帮助我们对信息进行提取与过滤。
对于已经接触脚本的朋友,管道符号一定不会陌生,管道符号“
|”在我们键盘enter的上方。位于管道符号左侧的命令输出的结果作为右侧命令的输入(处理对象),就拿上面写好的脚本"grep “bash” /etc/passwd | wc -l "的管道符号为例子。
管道符号左边的grep “bash” /etc/passwd就是筛选出/etc/passwd中含有"bash"字符串的文件,这些文件作为"wc -l"命令的作用对象,也就是将前面输出的文件的行数统计出来。
管道符号的基本使用格式
command命令1 | command命令2 | command命令3 ...|command命令n
管道符号不限使用的次数,从左往右按顺序依次执行过去。
我们在排错的过程中,通常只是想看到关键的信息,对于那些很长的内容,我们就可以将管道符号与正则表达式结合在一起使用,可以更方便我们过滤关键信息。
[root@localhost ~]# grep "bash$" /etc/passwd 查找/etc/passwd文件中以"bash"结尾的文件
root:x:0:0:root:/root:/bin/bash
shi:x:1000:1000:shi:/home/shi:/bin/bash
我们只需要看筛选出文件中的第一列和第七列的内容
[root@localhost ~]# grep "bash$" /etc/passwd | awk -F: '{print $1,$7}' awk为正则表达式,作用是以”:“作为分隔,输出第一和第七区域的字符串,-F是指定分隔符号(未指定时表示以空格和制表符分隔)
root /bin/bash
shi /bin/bash
如果我们想要查看挂载的/dev/cdrom已用空间所占的百分比,我们同样可以使用正则表达式与管道符号
[root@localhost ~]# df -Th
文件系统 类型 容量 已用 可用 已用% 挂载点
/dev/sda3 xfs 295G 8.5G 287G 3% /
devtmpfs devtmpfs 1.9G 0 1.9G 0% /dev
tmpfs tmpfs 1.9G 0 1.9G 0% /dev/shm
tmpfs tmpfs 1.9G 13M 1.9G 1% /run
tmpfs tmpfs 1.9G 0 1.9G 0% /sys/fs/cgroup
/dev/sr0 iso9660 4.3G 4.3G 0 100% /mnt
/dev/sda1 xfs 1014M 174M 841M 18% /boot
tmpfs tmpfs 378M 4.0K 378M 1% /run/user/42
tmpfs tmpfs 378M 40K 378M 1% /run/user/0
[root@localhost ~]# df -Th |grep "sr0"| awk '{print $6}'
100%
用户通过操作系统处理信息的过程中,包括以下几类交互设备文件:
重定向输入指的是将命令中接受输入的途径由默认的键盘改为指定的文件,而不是等待从键盘输入。
重定向输入使用”<“操作符。
重定向符号可以用来为用户设置密码。
[root@localhost ~]# vi passwd.txt
Abc123
[root@localhost ~]# passwd --stdin shi < passwd.txt
更改用户 shi 的密码 。
passwd:所有的身份验证令牌已经成功更新。
这种设置密码的方式免去了之前用”passwd“命令的两次输入密码的过程,减少了交互过程,提高了脚本的执行效率。
重定向输出是指将命令输出的结果保存到指定的文件中,不会显示在显示器的屏幕上。重定向输出使用“>”或“>>”符号,分别表示覆盖文件、追加文件。
当重定向输出的目标文件存在时,使用“>”会覆盖原文件,“>>”就会追加到文件的尾部;如果目标文件不存在,就会新建一个文件,将输出的结果写入新文件。
[root@localhost opt]# ls
httpd-2.4.25 httpd-2.4.25.tar.gz rh
[root@localhost opt]# df -Th | grep sr0 | awk '{print $6}'
100%
[root@localhost opt]# df -Th | grep sr0 | awk '{print $6}' > new.txt 创建了一个新文件,并将输出结果写进去
[root@localhost opt]# cat new.txt
100%
[root@localhost opt]# df -Th | grep sda1 | awk '{print $7}'
/boot
[root@localhost opt]# df -Th | grep sda1 | awk '{print $7}' > new.txt 覆盖了原来的文件
[root@localhost opt]# cat new.txt
/boot
[root@localhost opt]# df -Th | grep sr0 | awk '{print $7}' >> new.txt 追加到文件的底部
[root@localhost opt]# cat new.txt
/boot
/mnt
错误重定向是指将执行命令过程中出现的错误信息保存到指定的文件中,不显示在屏幕上。错误重定向符是“2>”,2是指错误文件的编号。也就是标准错误(STDERR)。
错误重定向符“2>”与重定向输出符号“>”用法相似,都会覆盖原来文件,如果要追加到文件中,可以使用“2>>”操作符。
在我们工作中,一般用这个来收集程序执行的错误信息,以便于排错;对于shell脚本,我们也可以将无关紧要的消息放到/dev/null中,保持清洁。/dev/null就像是一个黑洞,任何文件进去都会被消化掉。
[root@localhost opt]# ls
httpd-2.4.25 httpd-2.4.25.tar.gz rh
[root@localhost opt]# tar jcf /httpd-2.4.25 故意输错的命令,得到下面的报错信息
tar: 谨慎地拒绝创建空归档文件
请用“tar --help”或“tar --usage”获得更多信息。
[root@localhost opt]# tar jcf /httpd-2.4.25 2> error.txt 将报错信息保存到error.txt中
[root@localhost opt]# cat error.txt 查看保存的错误信息
tar: 谨慎地拒绝创建空归档文件
请用“tar --help”或“tar --usage”获得更多信息。
[root@localhost opt]# cd /error.txt
-bash: cd: /error.txt: 没有那个文件或目录
[root@localhost opt]# cd /error.txt 2>> error.txt 将报错信息追加到error.txt文件中
[root@localhost opt]# cat error.txt
tar: 谨慎地拒绝创建空归档文件
请用“tar --help”或“tar --usage”获得更多信息。
-bash: cd: /error.txt: 没有那个文件或目录
shell变量用来存放系统和用户需要使用的特定参数,这些参数随着用户的设定或系统环境的变化而变化,shell变量的使用能够提供更强的适应性与功能。
就像字面意思一样,自己定义的变量,只在自己的shell环境有用,又叫做本地变量。相比于其他高级编程语言(C语言、JAVA语言等)来说,Bash中的环境变量相对简单了很多,不需要提前声明,直接指定变量名并赋值就行。
bash环境定义变量的基本格式
变量名=变量值
其中“=”是赋值符号,变量名需要以字母或者下划线开头,不能包含特殊字符。
我们定义后的变量会保存在内存中,直到我们关机,变量才会失效。查看我们已经定义的变量时,要在我们已经定义的变量前加上“$”符号,用“echo”命令。
[root@localhost ~]# variable=study 定义变量variable,并赋值为study
[root@localhost ~]# days=10 定义变量days,并复制为10
[root@localhost ~]# echo $variable 查看变量variable
study
[root@localhost ~]# echo $variabledays 查看变量variabledays,我们并没有定义这个变量,所以结果为空
[root@localhost ~]# echo $variable days 每一个不同的变量,都要在前面加上一个“$”符号
study days
[root@localhost ~]# echo $variable$days 查看变量variable与变量days
study10
双引号主要是起界定字符串的作用,当要赋值的内容含有空格的时候,必须以双引号括起来,否则会出现赋值不成功的情况。
[root@localhost ~]# weixin=nice days 错误赋值
bash: days: 未找到命令...
[root@localhost ~]# weixin="nice days" 正确赋值
[root@localhost ~]# echo weixin
weixin
双引号的另外一种用法就是,在双引号的范围内可以引用其他的变量,也就是调用其他的变量的值赋予给新的变量。
[root@localhost ~]# weixin="nice days" 赋予$weixin值
[root@localhost ~]# echo $weixin
nice days
[root@localhost ~]# QQ="$weixin is today" 将$weixin的值赋予给QQ
[root@localhost ~]# echo $QQ
nice days is today
当我们想要赋予给变量的值含有特殊字符,比如“$”、“\”等特殊字符时,就可以使用单引号。在单引号的范围内,无法引用任何变量的值,所有的特殊符号都将被看做普通字符。
如果我们想要赋值的内容含有单引号,要与转义字符搭配使用“’”。
就将上面的例子用单引号演示。
[root@localhost ~]# echo $weixin
nice days
[root@localhost ~]# qq='$weixin is today' 赋值给qq,但是单引号内不能引用其他变量值
[root@localhost ~]# echo $qq
$weixin is today
反撇号主要是用于命令替换,允许将某个命令执行的结果赋值给变量。反撇号范围内必须是可以执行的命令行。反撇号在键盘“tab”键的上方。
反撇号的作用可以用“$()”代替,“ $()”还可以实现嵌套命令,这个在之后的博客中会由体现。
[root@localhost ~]# num=`ps -aux | wc -l`
[root@localhost ~]# echo $num
225
[root@localhost ~]# num=$(ps -aux | wc -l)
[root@localhost ~]# echo $num
225
read命令同样可以给变量赋值,read命令用来提示用户输入信息,实现简单的交互过程。
[root@localhost ~]# read num1
hello word 输入read命令后,在键盘输入赋予变量num1的内容
[root@localhost ~]# echo $num1
hello word
[root@localhost ~]# read num num1 read命令以空格为分隔符
hello word 赋予的值分别赋予两个变量
[root@localhost ~]# echo $num
hello
[root@localhost ~]# echo $num1
word
read命令可以结合“-p”、“-t”选项来设置提示信息与输入等待时间(默认是秒)。
[root@localhost ~]# read -p "请输入赋予的值:" num3 设置要提示的信息是"请输入赋予的值:"
请输入赋予的值:17 出现提示信息,并输入赋值
[root@localhost ~]# echo $num3
17
[root@localhost ~]# read -t "5" num4 等待5秒后不输入,自动结束
[root@localhost ~]# echo $num4 查看不到任何赋值
默认情况下,设置的变量只在当前shell环境有效,也可以成为局部变量。我们可以使用内部命令“export”来声明变量为全局;同样的,export也可以为变量赋值。
[root@localhost ~]# echo $QQ 查看变量QQ的赋值
nice days is today
[root@localhost ~]# bash 切换一个子bash环境
[root@localhost ~]# echo $QQ 查看变量QQ的赋值,在这个子bash环境中我们没有定义QQ这个变量
[root@localhost ~]# exit 退出当前子bash环境,返回原来的bash环境
exit
[root@localhost ~]# echo $QQ 查看变量QQ的赋值
nice days is today
[root@localhost ~]# export QQ 全局声明变量QQ
[root@localhost ~]# bash 切换到子bash环境
[root@localhost ~]# echo $QQ 查看变量QQ的赋值
nice days is today
+ | 加法 |
---|---|
_ | 减法 |
* | 乘法 |
/ | 除法 |
% | 取余 |
== | 等于 |
!= | 不等于 |
expr表示整数变量的运算,也可以用(())、let等。
[root@localhost ~]# expr 1 + 2 运算之间一定要加空格
3
[root@localhost ~]# num1=`expr 1 \* 7`
[root@localhost ~]# echo $num1
7
[root@localhost ~]# num2=$((2**3*2)) 表示赋予的值是2的3次方乘2
[root@localhost ~]# echo $num2
16
Linux系统里的env命令可以显示当前用户的环境变量,还可以用来在指定环境变量下执行其他命令。
[root@localhost ~]# env 查看当前环境的环境变量
XDG_SESSION_ID=120
HOSTNAME=localhost.localdomain
SELINUX_ROLE_REQUESTED=
SHELL=/bin/bash
TERM=xterm
HISTSIZE=1000
SSH_CLIENT=14.0.0.1 4757 22
SELINUX_USE_CURRENT_RANGE=
SSH_TTY=/dev/pts/2
USER=root
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:
QQ=nice days is today
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
MAIL=/var/spool/mail/root
PWD=/root
LANG=zh_CN.UTF-8
SELINUX_LEVEL_REQUESTED=
HISTCONTROL=ignoredups
HOME=/root
SHLVL=2
LOGNAME=root
SSH_CONNECTION=14.0.0.1 4757 14.0.0.14 22
XDG_DATA_DIRS=/root/.local/share/flatpak/exports/share:/var/lib/flatpak/exports/share:/usr/local/share:/usr/share
LESSOPEN=||/usr/bin/lesspipe.sh %s
DISPLAY=localhost:11.0
XDG_RUNTIME_DIR=/run/user/0
_=/usr/bin/env
我们熟悉的变量有USER(表示用户名称)、HOME(表示用户的宿主目录)、LANG(表示语言个字符集)、PWD(表示当前所在工作目录)、PATH(表示命令搜索路径)等等。
[root@localhost ~]# ls -lh ~/first.sh 查看脚本文件的位置
-rwxrwxrwx. 1 root root 138 7月 15 19:43 /root/first.sh
[root@localhost ~]# first.sh 不能以文件名运行脚本文件
bash: first.sh: 未找到命令...
[root@localhost ~]# PATH="$PATH:/root" 将/rrot添加到PATH的搜索路径
[root@localhost ~]# first.sh 可以直接以文件名运行脚本
当前目录位于:
/etc
/etc/passwd文件中含有bash的行数为:
2
还有一种方法可以永久对环境变量进行修改,当然就是修改配置文件。环境变量的全局配置文件在/etc/profile中,作用于全局;每个用户还有属于自己的环境变量配置文件~/.bash_profile中。通过修改配置文件,也可以达到修改环境变量的目的。
为了我们在执行shell脚本程序时,方便通过命令行为程序提供操作参数,bash引入了位置变量。当我们在执行一条命令时,第一个字段通常是命令名或脚本程序名,这个字段就是我们预定义变量$0,虽然格式与位置变量相同,但是 $0是预定义变量,这一点要区分开。接下来从左到右依次用$1、$2、$3…$9表示。
[root@localhost ~]# vi second.sh
#!/bin/bash
sum=`expr $1 + $2`
echo $sum
[root@localhost ~]# chmod +x second.sh
[root@localhost ~]# ./second.sh 12 34 $1=12、$2=34
46
[root@localhost ~]# ./second.sh 34 56 $1=34、$2=56
90
预定义变量是bash程序预先定义好的一类特殊变量,用户只能使用,不能创建,也不能直接为预定义变量赋值,常见的几个预定义变量如下:
[root@localhost ~]# vi second.sh
#!/bin/bash
sum=`expr $1 + $2`
echo $sum
echo "共执行$0脚本"
echo "共完成$#个对象的计算"
echo "具体内容包括:$*"
[root@localhost ~]# ./second.sh 34 56
90
共执行./second.sh脚本
共完成2个对象的计算
具体内容包括:34 56
[root@localhost ~]# vi second.sh
#!/bin/bash
sum=`expr $1 + $2 + $3`
echo $sum
echo "共执行$0脚本"
echo "共完成$#个对象的计算"
echo "具体内容包括:$*"
[root@localhost ~]# ./second.sh 34 56 10
100
共执行./second.sh脚本
共完成3个对象的计算
具体内容包括:34 56 10
[root@localhost ~]#