华子目录
为什么学习和使用shell编程
- 简单易学
- 解释性语言,不需要编译即可执行
- 对于一个合格的系统管理员来说,学习和掌握shell编程是非常重要的,通过shell程序,可以在很大程度上简化日常的维护工作,使得管理员从简单的重复劳动中解脱出来
shell起源
- 1964年,美国AT&T公司的贝尔实验室、麻省理工学院及美国通用电气公司共同参与开始研发一套可以安装在大型主机上的多用户、多任务的操作系统,该操作系统的名称为Multics。
- 1970年,丹尼斯•里奇和汤普逊启动了另外一个新的多用户、多任务的操作系统的项目,他们把这个项目称之为UNICS。
- 1973年,使用C语言重写编写了Unix。通过这次编写,使得Unix得以移植到其他的小型机上面。
- 1979年,第一个重要的标准UNIX Shell在Unix的第7版中推出,并以作者史蒂夫•伯恩(StephenBourne)的名字命名,叫做Bourne Shell,简称为sh。
- 20世纪70年代末,C Shell作为2BSD UNIX的一部分发布,简称csh。之后又出现了许多其他的Shell程序,主要包括Tenex C Shell(tcsh)、Korn Shell(ksh)以及GNU Bourne-Again shell(bash)。
查看当前系统支持的shell
[root@server ~]# cat /etc/shells
/bin/sh
/bin/bash
/usr/bin/sh #相当于/bin/sh的备份
/usr/bin/bash #相当于/bin/bash的备份
查看当前系统默认的shell
[root@server ~]# echo $SHELL
/bin/bash
shell概念
- shell(外壳):是一种命令解释器程序,它能识别用户输入的各种命令,并传递给操作系统
- 结构图
- 真正能够控制计算机硬件(CPU、内存、显示器等)的只有操作系统内核(Kernel),图形界面和命令行只是架设在用户和内核之间的一座桥梁,由于安全、复杂、繁琐等原因,用户不能直接接触内核(也没有必要),需要另外再开发一个程序,让用户直接使用这个程序;该程序的作用就是接收用户的操作(点击图标、输入命令),并进行简单的处理,然后再传递给内核,这样用户就能间接地使用操作系统内核
- 用户界面和命令行就是这个另外开发的程序,就是这层“代理”。在Linux下,这个命令行程序叫做 Shell,Shell 是一个应用程序,它连接了用户和 Linux 内核,让用户能够更加高效、安全、低成本地使用 Linux 内核,这就是 Shell 的本质。
- Shell 本身并不是内核的一部分,它只是站在内核的基础上编写的一个应用程序,它和 QQ、迅雷、Firefox 等其它软件没有什么区别。然而 Shell 也有着它的特殊性,就是开机立马启动,并呈现在用户面前;用户通过 Shell 来使用 Linux,不启动 Shell 的话,用户就没办法使用 Linux。
shell程序设计语言
shell也是一种脚本语言
- 任何代码最终都要被 “翻译”成二进制的形式才能在计算机中执行。
- 有的编程语言,如 C/C++、Pascal、Go语言、汇编等,必须在程序运行之前将所有代码都翻译成二进制形式,也就是生成可执行文件,用户拿到的是最终生成的可执行文件,看不到源码。这个过程叫做编译(Compile),这样的编程语言叫做编译型语言,完成编译过程的软件叫做编译器(Compiler)。
- 有的编程语言,如 Shell、JavaScript、Python、PHP等,需要一边执行一边翻译,不会生成任何可执行文件,用户必须拿到源码才能运行程序。程序运行后会即时翻译,翻译完一部分执行一部分,不用等到所有代码都翻译完。这个过程叫做解释,这样的编程语言叫做解释型语言或者脚本语言(Script),完成解释过程的软件叫做解释器。
- 编译型语言的优点是执行速度快、对硬件要求低、保密性好,适合开发操作系统、大型应用程序、数据库等。
- 脚本语言的优点是使用灵活、部署容易、跨平台性好,非常适合 Web 开发以及小工具的制作。
- Shell 就是一种脚本语言,我们编写完源码后不用编译,直接运行源码即可。
用途
- shell脚本的优势在于处理操作系统底层的业务 (linux系统内部的应用都是shell脚本完成)因为有大量的linux系统命令为它做支撑。2000多个命令都是shell脚本编程的有力支撑,特别是grep、awk、sed等。例如:一键软件安装、优化、监控报警脚本,常规的业务应用,shell开发更简单快速,符合运维的简单、易用、高效原则。
- PHP、Python优势在于开发运维工具以及web界面的管理工具,web业务的开发等。处理一键软件安装、优化,报警脚本。常规业务的应用等php/python也是能够做到的。但是开发效率和复杂比用shell就差很多了。
如何学号shell
熟练掌握shell编程基础知识
- 熟练使用vi(vim)编辑器
- 熟练掌握Linux基本命令
- 熟练掌握文本三剑客工具(grep、sed、awk)
- 熟悉常用服务器部署、优化、日志及排错
建议
- 掌握shell脚本基本语法
- 形成自己的脚本开发风格
- 从简单做起,简单判断,简单循环
- 多模仿,多参考资料练习,多思考
- 学会分析问题,逐渐形成编程思维
- 编程变量名字要规范,采用驼峰语法表示
- 不要拿来主义,特别是新手
shell脚本的基本元素
基本元素构成
- 第1行的"#!/bin/bash"
- 注释:说明某些代码的功能
- 可执行语句:实现程序的功能
shell脚本中的注释和风格
- 作用:通过在代码中增加注释可以提高程序的可读性
- 传统的shell只支持单行注释,其表示方法是一个井号"#",从该符号开始一直到行尾都属于注释的内容,如
#comment1
#comment2
#comment3
...
- 多行注释:使用 冒号":" 配合,语法如下
:<<'xxxx'
comment1
comment2
comment3
……
xxxx
xxxx可以是字符或数字,单引号可以不加,但以防出现莫名其妙的意外发生,最好加上
shell脚本编写规范
脚本开头
- 开头指定脚本解释器:#!/bin/sh或 #!/bin/bash
- 其他行#表示注释
- 程序段开头需要加版权等信息,如:
# Date:创建日期
# Author:作者
# Mail:联系方式
# Function:功能
# Version:版本
脚本自动增加注释版权信息
[root@server ~]# vim ~/.vimrc # 新建配置文件
autocmd BufNewFile *.py,*.cc,*.sh,*.java exec ":call SetTitle()"
func SetTitle()
if expand("%:e") == 'sh'
call setline(1,"#!/bin/bash")
call setline(2,"##############################################################")
call setline(3, "# File Name: ".expand("%"))
call setline(4, "# Version: V1.0")
call setline(5, "# Author: Andy_Sun")
call setline(6, "# Email: Andy_Sun@163.com")
call setline(7, "# Organization: http://www.cnblogs.com/Andy_Sun/")
call setline(8, "# Created Time : ".strftime("%F %T"))
call setline(9, "# Description:")
call setline(10,"##############################################################")
call setline(11, "")
endif
endfunc
示例(shell
脚本文件必须以.sh
结尾)
创建一个标准的脚本,实现有注释信息,版权信息,内容任意,并执行
[root@server ~]# vim cmatrix.sh #写入以下内容
wget http://archive.ubuntu.com/ubuntu/pool/universe/c/cmatrix/cmatrix_1.2a.orig.tar.gz
tar xvf cmatrix_1.2a.orig.tar.gz
cd cmatrix-1.2a
yum install -y ncurses-devel
yum install -y gcc
./configure && make && make install
echo "Program installation complete !"
echo "Program installation complete !"
echo "Program installation complete !"
[root@server ~]# bash cmatrix.sh #执行脚本
[root@server ~]# cmatrix
脚本中尽量不用中文注释
- 别吝啬添加注释,必要的注释方便自己别人理解脚本逻辑和功能
- 尽量用英文注释,防止本机或切换系统环境后中文乱码的困扰
- 单行注释,可以放在代码行的尾部或代码行的上部
- 多行注释,用于注解复杂的功能说明,可以放在程序体中,也可以放在代码块的开始部分 代码修改时,对修改的内容
多使用内部命令
- 无论碰到哪种情况,请尽量考虑使用内部命令而不是外部命令
- 内部命令执行的效率高,性能好
没有必要使用cat命令
- 这是我们经常在论坛里讨论的话题之一。没有必要使用cat命令指的是在有些时候,我们会发现根本没有必要使用cat命令。使用了多余的cat命令会让你的代码看起来很丑陋,而且还会带来性能上的问题
- 例如:以下两条命令的结果一样
[root@server ~]# cat /etc/passwd | grep root
[root@server ~]# grep root /etc/passwd
仔细阅读出错信息
- 程序员常犯的一个错误是:当我们敲入的命令报错后,我们中的大多数人只是对错误信息一瞥而过,而不会去认真的读一读,很多时候,错误信息里就包含了解决办法
- 有时候我们修改了某个错误并再次运行后,系统依旧会报错。然后我们再次修改,但系统再次报错。这可能会持续很长时间。但实际上,旧的错误可能已经被纠正,只是由于出现了其它一些新错误才导致系统再次报错。而我们依旧在怀疑为什么修改好的代码依然不能正常运行。
- 因此,请你养成仔细阅读错误信息的习惯。
文件名以.sh
结尾
- shell脚本文件名应见名知义,扩展名为.sh,如:backup_mysql.sh
代码缩进
- shell没有强制要求,但建议缩进,这样可以提高阅读性,程序更有层次感
示例:编写九九乘法表
[root@server ~]# vim 99.sh
#!/bin/bash
for((i=1;i<10;i++))
do
echo -ne "$i\t"
done
echo
for((i=1;i<70;i++))
do
echo -n "="
done
echo
for((i=1;i<10;i++))
do
for((j=1;j<=i;j++))
do
echo -en "$i*$j=$[i*j]\t"
done
echo
done
[root@server ~]# bash 99.sh
1 2 3 4 5 6 7 8 9
=====================================================================
1*1=1
2*1=2 2*2=4
3*1=3 3*2=6 3*3=9
4*1=4 4*2=8 4*3=12 4*4=16
5*1=5 5*2=10 5*3=15 5*4=20 5*5=25
6*1=6 6*2=12 6*3=18 6*4=24 6*5=30 6*6=36
7*1=7 7*2=14 7*3=21 7*4=28 7*5=35 7*6=42 7*7=49
8*1=8 8*2=16 8*3=24 8*4=32 8*5=40 8*6=48 8*7=56 8*8=64
9*1=9 9*2=18 9*3=27 9*4=36 9*5=45 9*6=54 9*7=63 9*8=72 9*9=81
shell脚本执行方法
方法1
- 使用
sh
或bash
命令执行脚本,不需要执行权限(建议使用)(为什么不需要权限,因为:脚本文件名会作为参数,传递给脚本解释器去执行,只要解释器本省有权限就可以了)
[root@server ~]# vim test.sh
#!/bin/bash
echo "china"
[root@server ~]# bash test.sh
china
[root@server ~]# sh test.sh
china
可以使用bash -n 脚本名 ,进行语法检测,且不执行脚本
可以使用bash -x 脚本名 ,进行脚本执行跟踪,逐条语句的跟踪执行
方法2
- 切换到脚本所在目录使用 ./脚本文件,需要该脚本文件执行权限(需要配合cd进行目录切换 ,因为是在当前目录下)
[root@server ~]# ll -d test.sh
-rw-r--r-- 1 root root 334 3月 14 00:00 test.sh
[root@server ~]# ./test.sh
-bash: ./test.sh: 权限不够
[root@server ~]# chmod +x test.sh
[root@server ~]# ll -d test.sh
-rwxr-xr-x 1 root root 334 3月 14 00:00 test.sh
[root@server ~]# ./test.sh
china
方法3
- 绝对路径执行脚本,需要执行权限
[root@server ~]# vim /t1.sh
#!/bin/bash
echo "china"
[root@server ~]# /t1.sh
-bash: /t1.sh: 权限不够
[root@server ~]# ll -d /t1.sh
-rw-r--r-- 1 root root 333 3月 14 00:07 /t1.sh
[root@server ~]# chmod +x /t1.sh
[root@server ~]# ll -d /t1.sh
-rwxr-xr-x 1 root root 333 3月 14 00:07 /t1.sh
[root@server ~]# /t1.sh
china
方法4
- 使用点(.)或者source执行脚本,不需要执行权限
[root@server ~]# source /t1.sh
china
[root@server ~]# . test.sh
china
注意:
- 法1,2,3都是启动一个
子shell
,在子shell
中执行此脚本,脚本中设置的变量在脚本执行完毕后不会保存 - 法4都是在当前
shell进程
中执行此脚本,而不是重新启动一个shell 在子shell进程中 执行此脚本,并且脚本中设置的变量在脚本执行完毕后会保存下来
bash shell基本功能
echo打印命令
-
格式:
echo -参数 内容
-
参数:
-n
:取消输出后行未得换行符号-e
:启用转义字符
-
可以输出带颜色得字体:
-
格式:
echo -e "\e[字体控制;背景色;字体颜色 字符串内容 \e[0m"
-
\e[
表示控制开始
,\e[0m
表示控制结束
-
字体控制
选项:1
表示高亮
,4
表示下划线
,5
颜色闪烁
-
颜色如下:
背景色:40-47
字颜色:30m-37m
[root@server ~]# vim color.sh
echo -e "\e[30m 黑色字 \e[0m"
echo -e "\e[1;31m 紅色字 \e[0m"
echo -e "\e[32m 綠色字 \e[0m"
echo -e "\e[33m 黃色字 \e[0m"
echo -e "\e[34m 藍色字 \e[0m"
echo -e "\e[35m 紫色字 \e[0m"
echo -e "\e[36m 天藍字 \e[0m"
echo -e "\e[37m 白色字 \e[0m"
echo -e "\e[40;37m 黑底白字 \e[0m"
echo -e "\e[41;37m 紅底白字 \e[0m"
echo -e "\e[42;37m 綠底白字 \e[0m"
echo -e "\e[43;37m 黃底白字 \e[0m"
echo -e "\e[44;37m 藍底白字 \e[0m"
echo -e "\e[45;37m 紫底白字 \e[0m"
echo -e "\e[46;37m 天藍底白字 \e[0m"
echo -e "\e[47;30m 白底黑字 \e[0m"
[root@server ~]# bash color.sh
#!/bin/bash
echo -e "\e[30m 黑色字 \e[0m"
echo -e "\e[1;31m 紅色字 \e[0m"
echo -e "\e[32m 綠色字 \e[0m"
echo -e "\e[33m 黃色字 \e[0m"
echo -e "\e[34m 藍色字 \e[0m"
echo -e "\e[35m 紫色字 \e[0m"
echo -e "\e[36m 天藍字 \e[0m"
echo -e "\e[37m 白色字 \e[0m"
echo -e "\e[40;37m 黑底白字 \e[0m"
echo -e "\e[41;37m 紅底白字 \e[0m"
echo -e "\e[42;37m 綠底白字 \e[0m"
echo -e "\e[43;37m 黃底白字 \e[0m"
echo -e "\e[44;37m 藍底白字 \e[0m"
echo -e "\e[45;37m 紫底白字 \e[0m"
echo -e "\e[46;37m 天藍底白字 \e[0m"
echo -e "\e[47;30m 白底黑字 \e[0m"
[root@server ~]# bash color.sh
printf命令
- printf 命令模仿 C 程序库(library)里的 printf() 程序, 由 POSIX 标准所定义,因此使用 printf 的脚本比使用 echo 移植性好,printf 使用引用文本或空格分隔的参数,外面可以在 printf 中使用格式化字符串,还可以制定字符串的宽度、左右对齐方式等。默认 printf 不会像 echo 自动添加换行符,我们可以手动添加 \n
- 格式
printf 格式控制字符串 参数列表
示例
[root@server ~]# echo "hello world"
hello world
[root@server ~]# printf "hello, shell\n"
hello, shell
[root@server ~]# printf "%d %s\n" 1 'abc'
1 abc
[root@server ~]# printf "%-10s %-8s %-4s\n" '华子' '男' '130'
华子 男 130
# %s %c %d %f 都是格式替代符,%s 输出一个字符串,%d 整型输出,%c 输出一个字符,%f 输出实数,以小数形式输出。
# %-10s 指一个宽度为 10 个字符(- 表示左对齐,没有则表示右对齐),任何字符都会被显示在 10 个字符宽的字符内,如果不足则自动以空格填充,超过也会将内容全部显示出来。
[root@server ~]# printf "%-10s %-8s %-4.2f\n" '郭靖' '男' 66.1234
郭靖 男 66.12
# %-4.2f 指格式化为小数,其中 .2 指保留2位小数
history历史命令
- 格式
history [参数] [历史命令保存文件]
-
参数
- -c:清空历史命令记录
- -w:把缓存中的历史命令写入历史命令保存文件。如果不手工指定历史命令保存文件,则放入默认历史命令保存文件~/.bash_history 中
-
修改默认记录历史命令条数:
[root@server ~]# vim /etc/profile (重启生效)
HISTSIZE=1000 #储存历史命令记录条数
- 面试题:显示history历史命令出现次数最高的top10
[root@server /]# history | tr -s " " | cut -d " " -f3 | sort | uniq -c | sort -nr | head -10
# 浏览历史命令 | 压缩为1个空格 | 截取以空格为间隔的第3列 | 排序 | 统计并去重 | 以次数为依据进行降序排序 | 取前10个
- 面试题:增加history显示的信息,如:历史命令的执行时间
[root@server /]# vim ~/.bashrc # 编辑bash的配置文件,最后一行后添加:
export HISTTIMEFORMAT="%Y-%m-%d %H:%M:%S: "
[root@server /]# source ~/.bashrc # 刷新配置
[root@server /]# history # 查看结果
命令与文件名补全:tab
命令别名
- 格式:
alias 别名=原命令
- 例:
[root@server ~]# alias hi=history #重启无效
[root@server ~]# hi
- 注意:别名的优先级比命令高,命令执行时的顺序如下:
第一顺位:执行用绝对路径或相对路径执行的命令。
第二顺位:执行别名。
第三顺位:执行 Bash 的内部命令。
第四顺位:执行按照 $PATH 环境变量定义的目录查找顺序找到的第一个命令。
- 为了让这个别名永久生效,可以把别名写入环境变量配置文件"~/.bashrc"
[root@server ~]# vim ~/.bashrc # 在最下面增加
alias hi=history
; && ||
命令执行顺序
- 顺序执行(用
;
分隔)(分号前后
的命令
都会执行
,不管
有没有成功执行
)
[root@server ~]# date ; ls -l /etc/passwd
[root@shell shell]# sl;ls
bash: sl: command not found...
Similar command is: 'ls' #没有sl命令,执行失败
shell1.sh shell2.sh shell3.sh #执行ls命令
[root@shell shell]# sl;ls
bash: sl: command not found...
Similar command is: 'ls'
shell1.sh shell2.sh shell3.sh
[root@shell shell]# echo $?
0
[root@shell shell]# ls;sl
shell1.sh shell2.sh shell3.sh
bash: sl: command not found...
Similar command is: 'ls'
[root@shell shell]# echo $?
127
#总结:对于顺序执行的命令组合,$?记录的是最后的命令是否执行成功
- 前面命令执行不成功,后面的命令不执行:
&&
[root@server ~]# mkdir /mnt/iso && mount /dev/sr0 /mnt/iso
[root@shell shell]# date && ls
2024年 10月 24日 星期四 12:25:24 EDT
shell1.sh shell2.sh shell3.sh
[root@shell shell]# echo $?
0
[root@shell shell]# sl && ls
bash: sl: command not found...
Similar command is: 'ls'
[root@shell shell]# echo $?
127
[root@shell shell]# ls && sl
shell1.sh shell2.sh shell3.sh
bash: sl: command not found...
Similar command is: 'ls'
[root@shell shell]# echo $?
127
#总结:对于&&结合执行的命令组合,$?记录的是整体命令是否执行成功
- 前面命令成功,后面就不执行,如果前面不成功后面就执行:
||
[root@server ~]# mkdir tt || ls /
[root@server ~]# mkdir tt || ls / # 可以再次执行
[root@shell shell]# ls || sl
shell1.sh shell2.sh shell3.sh
[root@shell shell]# echo $?
0
[root@shell shell]# sl || ls
bash: sl: command not found...
Similar command is: 'ls'
shell1.sh shell2.sh shell3.sh
[root@shell shell]# echo $?
0
#总结:对于||结合执行的命令组合,$?记录的是整体命令是否执行成功
管道符
- 符号:| ,当在两个命令之间设置管道时,管道符|左边命令的输出就变成了右边命令的输入。只要第一个命令向标准输出写入,而第二个命令是从标准输入读取,那么这两个命令就可以形成一个管道
- 面试题:获取服务器的IP地址
[root@server ~]# ip a | grep ens160 | grep inet | tr -s " " | cut -d " " -f3
192.168.48.130/24
# 查看IP信息 | 过滤包含esn160行 | 过滤包含inet行 | 压缩空格为1个 | 列向截取列
[root@server ~]# ip a | grep ens160 | grep inet | cut -d "/" -f1 | cut -d " " -f6
192.168.48.130
- 面试题:显示内存的剩余容量
[root@server ~]# free -h | grep Mem | tr -s " " | cut -d " " -f4
exit退出程序
- 作用:终止Shell程序的执行
- 格式:exit 状态码
- 状态码:该参数是一个整数值,其取值范围为0~255
- 注意:Shell程序的退出状态码储存在 系统变量$?中,因此,用户可以通过该变量取得Shell程序返回给父进程的退出状态码
常见状态码
0----------------命令运行成功
1----------------通知未知错误
2----------------误用shell命令
126-------------命令不可执行
127-------------没有找到命令
128-------------无效退出参数
128+x-----------linux信号x的严重错误
130--------------命令通过Ctrl+C终止
255--------------退出状态码越界
- 演示在不同的情况下,程序返回不同的状态码
[root@server ~]# echo 'china'
china
[root@server ~]# echo $?
0
[root@server ~]# ehco 'china'
bash: ehco: command not found...
Similar command is: 'echo'
[root@server ~]# echo $?
127