1 Shell 基础介绍
1.1 Shell 简介
Shell 概述
- Shell 是系统的用户界面,提供了用户与内核进行交互操作的一种接口
- Shell 将用户输入的命令并且把它们送到内核去执行,然后返回执行结果
- Shell 是可编程的,它允许用户编写由 Shell 命令组成的程序
Shell 发展史
- 1971 : Bell Labs 的 Ken Thompson 为 UNIX 开发了第一个 Shell: V6 Shell 是一个在内核之外执行的独立的用户程序
- 1977 :Steven Bourne 在 AT&T Bell Labs 为 V7 UNIX 创建 Bourne shell
- 1978 :Bill Joy 在伯克利分校攻读研究生期间为 BSD UNIX 系统开发的 csh
- 1983 : Ken Greer 在卡内基-梅隆大学开发了 tcsh,将 C Shell 引入了 Tenex 系统中的一些功能,如命令行编辑功能和文件名和命令自动补全功能
- 1983 :David Korn 在 AT&T Bell Labs 创建 korn shell,它功能更强大,提供关联数组表达式运算
- 1989 :Bourne-Again Shell(或 Bash)是一个开源 GNU 项目,旨在取代 Bourne shell;Bash 由 Brian Fox 开发,已成为世上最流行的 Shell,它兼容 sh、csh、ksh,是 Linux 系统的默认 Shell
在 Linux 中,有多种 Shell 程序可供选择,比如 dash、csh、zsh 等,默认的 Shell 可以在 /bin/sh
查看,在etc/passwd
中修改。
查看Shell
查看系统默认安装的 Shell
cat /etc/shells
查看当前登录用户默认 Shell
echo $SHELL
查看当前的 Shell
echo $0
1.2 Shell 脚本(定义、作用、格式、权限及执行)
Shell 脚本基础知识
在 Unix/Linux 里,一个程序/命令只做好一件事;复杂的问题可以通过多个命令的组合来解决;形式最简单的 Shell 脚本就是一系列命令构成的可执行文件,并可以被其他脚本复用。编写风格良好易读的 Shell 脚本可以提高日常任务的自动化程度和准确性。
Shell 脚本的约束Shell 脚本可以完成很多任务,但不适用于所有情况
- Shell 脚本可以完成很多任务,但不适用于所有情况
- 对于可以通过调用其他命令行实用工具来完成的任务,Shell 脚本是一种不错的选择
- Shell 脚本可以作为一种“胶水”语言,整合其他编程语言 当解决某个问题时,Shell 脚本实现起来复杂度高,效率低,此时就可以考虑使用其他编程语言。
Shell 脚本开发环境
高级编辑器如 Vim 和 Emacs,在识别文件的后缀为 .sh 后,可以提供语法高亮、检查、补全等功能。
[root@localhost~]# vim demo.sh #新建一个脚本文件,并写入如下内容:
#!/bin/bash
echo "Hello World“
[root@localhost~]# sh demo.sh
Hello World
Shell 脚本指定解释器
Shell 脚本只是静态的代码,若要输出结果,还需要解释器的参与。一般在脚本的第一行,指定执行此脚本的解释器。如果不指定解释器,脚本也能在默认的解释器中正常运行,但出于规范和安全的考虑,建议指定如下:
#!/bin/bash
#!/bin/csh
执行 Shell 脚本
sh script_name.sh
./script_name.sh
Linux 中一切皆文件,脚本/命令/程序都是一个文件,文件作为一个对象,具有权限的属性。在执行别人发送或从网上下载的脚本时可能会遇到权限问题,赋予执行权限可解决:
chmod +x script_name.sh
如果某个 Shell 脚本可执行,则可以通过在命令行中输入其名称调用.被成功调用的前提是,脚本所在路径包含在 $PATH 变量中
[root@localhost~]# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
[root@localhost~]# PATH=$PATH:/New/path
[root@localhost ~]# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/New/path
#永久修改 $PATH 需要在 .bashrc 或 .bash_profile 中添加 export PATH=“$$PATH:/new/path”
后台执行 Shell 脚本
有时候一些脚本执行时间较长,命令行界面会被占用,因此可以采取后台运行脚本:
./my_script.sh &
这种方法在退出 Shell 后,脚本进程会随之终止,为了保证脚本一直运行,可以采用:
nohup ./my_script.sh &
脚本的的标准输出和标准错误会重定向到 nohup.out
文件里
2 Shell 编程基础
2.1 输入输出、管道
Linux 中的文本流
文本流存在于 Linux 的每一个进程中。Linux 的每个进程启动时,会打开三个文本流的端口:标准输入、标准输出、标准错误。这三个端口对应着一个程序的输入、输出和异常的抛出。
例如:
在 bash 中输入一串字符后,bash 进程中的标准输入端口捕获命令行中的输入,进行处理后从标准输出端口中传出,回显在屏幕上,如果处理过程中发生异常,则会通过标准错误端口,将异常回显在屏幕上。
输出重定向
某些情况下,我们需要保存程序的输出,此时就可以通过重定向,将程序的输出保存到文件中
ls > dir_log
ls >> dir_log
输入重定向
与输出重定向类似,输入重定向是把程序的标准输入进行重新定向。
输入重定向:
- 格式:
command << maker
- 输出重定向需要文件,而内联输入重定向可以使用即时输入的文本作为标准输入,传入左边的命令
- 右边的字符“maker”作为标志,表示标准输入的开始和结束,自身不包含在标准输入里。
- 下图例中以“maker”作为标志
管道
有时需要将一个命令的输出连到另一个命令的输入,如果用重定向实现会较复杂。
管道( | )就像现实中的水管一样,可以连接两个命令的输入和输出,甚至是串联多个命令
格式:command1 | command2 | command3
[root@localhost ~]# ls /bin/ | grep python | less
2.2 字符、变量、运算
2.2.1 Shell 中的字符
和其他编程语言一样,Shell 也有一些保留字(特殊字符),在编写脚本时需要注意。
- 注释符号(Hashmark[Comments])
在shell文件的行首,作为shebang标记,#!/bin/bash
;
其他地方作为注释使用,在一行中,#后面的内容并不会被执行,除非用单/双引号包围时,#作为#号字符本身,不具有注释作用。 - 单引号(full quoting [single quote])
单引号括住的内容,被视为单一字符串,引号内的禁止变量扩展,所有字符均作为字符本身处理(除单引号本身之外),单引号必须成对出现。 - 反斜线,反斜杆(escape [backslash])
放在特殊符号之前,转义特殊符号的作用,仅表示特殊符号本身,这在字符串中常用;放在一行指令的最末端,表示紧接着的回车无效(其实也就是转义了Enter),后继新行的输入仍然作为当前指令的一部分。 - 斜线,斜杆(Filename path separator [forward slash])
作为路径的分隔符,路径中仅有一个斜杆表示根目录,以斜杆开头的路径表示从根目录开始的路径;
在作为运算符的时候,表示除法符号。如:a=4/2 - 感叹号(reverse (or negate) [bang],[exclamation mark])
取反一个测试结果或退出状态。表示反逻辑,比如后面的!=,这个是表示不等于;表示取反,如:ls a[!0-9]
表示a后面不是紧接一个数字的文件;
2.2.2 Shell 中的变量
任何语言都有变量这个要素。Shell 与其他强类型的编程语言如:C,Java 和 C++ 等有很大不同,Shell 中的变量是无类型的。通过一个变量,我们可以引用一块内存区域的值,变量名就是这块内存区域上贴的一个标签。
变量的类型
在 Linux Shell 中,变量主要有两大类:环境变量和用户定义变量。每种类型的变量依据作用域不同,又分为全局变量和局部变量
查看变量
对比 printenv 与 set 的区别:dif -yw <(printenv) <(set)
命令set是Shell中的一个内建命令,它能够显示当前Shell中的变量,已经用户自定义的变量,不管该变量有没有export。set命令允许你更改Shell选项的值并设置位置参数,或者显示Shell变量的名称和值。如果未提供任何选项或参数,则会设置显示所有Shell变量和函数的名称和值(按照当前语言环境排序),并且输出的格式可以重新用作设置或重置当前设置变量的输入
printenv和env在环境变量打印方面是类似的。但是在功能上,env主要用于设置环境变量并运行指定的命令命令,而printenv是为了打印环境变量。
set则是一个Shell的内建命令,与Shell有关,用于设置Shell的属性。
修改变量
使用变量
variable=value
variable='value'
variable="value"
- 赋值号的周围不能有空格
- 如果有必要,也可以使用 declare 关键字显式定义变量的类型
- 查询关键字:
help | grep <keywords>
- 如果 value 包含了空白符,则需要用引号包裹起来
单引号包裹的内容,shell 不展开命令、变量,即原样输出
双引号反之,会展开其中包裹的命令和变量
扩展变量
在以下示例中,如果不使用花括号,Bash 会将$FIRST_$LAST
解释为变量 $FIRST_ 后跟变量 $LAST,而不是由_ 字符分隔的变量 $FIRST 和 $LAST。
因此,在此情况下,必须使用花括号引用的形式才能使变量扩展正确运行。
变量的赋值和输出
2.2.3 Shell 的算术扩展
算术扩展可用于执行简单的整数算术运算
语法:$[表达式]
如:
[root@localhost ~]# echo $[1+9]
10
[root@localhost ~]# echo $[8*8]
64
[root@localhost ~]# count=1;echo $[$[$count+3]*2]
8
在 Shell 中计算时间
定义24小时,60分钟,60秒变量,定义每天秒数变量并赋值,输出变量
[root@localhost ~]# SEC_PER_MIN=60
[root@localhost ~]# MIN_PER_HR=60
[root@localhost ~]# HR_PER_DAY=24
[root@localhost ~]# SEC_PER_DAY=$[$SEC_PER_MIN*$MIN_PER_HR*$HR_PER_DAY]
[root@localhost ~]# echo "There are $SEC_PER_DAY seconds in a day"
There are 86400 seconds in a day
算术运算常用表达式
算术运算优先级
优先级由高到低如下:
变量递增的前置后置区别
i++ 要开辟一个变量来保存 i 的值 并返回,然后让 i 这个变量 的值 +1 。而 ++i 直接把 1 加到 i 这个 变量的空间中去,并返回这个空间 中的值, 没有开辟任何临时空间,性能更高。
2.3 语句(条件、循环)
Shell 中的结构化命令
在 Shell 脚本里除了顺序执行,还需要一些额外的逻辑控制流程。
和其他编程语言类似, Shell 中的结构化命令主要包括条件、循环两类
条件语句
Shell 中的 if 和其他 if 判断的条件不太一样,需注意。
If-then 语句
语法:
if command
then
commands
fi
Bash Shell 会先执行 if 后面的语句,如果其退出状态码为 0,则会继续执行 then 部分的命令,否则会执行脚本中的下一个命令。
多分支判断语句
在涉及多条件判断时,可能会使用较为繁琐的 if-then-else 语句,通过 elif 语句频繁检测同一个变量的值,此时更适合使用 case 语句
语法:
case variable in
pattern1 | pattern2) commands1;;
pattern3) commands2;;
*) default commands;;
esac
循环语句
Shell 脚本中常会遇到一些重复任务,相当于循环执行一组命令直到满足了某个特定条件
循环控制符有两种:break、continue 用于控制循环流程的转向
for 循环
#Shell风格语法
for var in list
do
commands
done
#例:
for i in {1..10}
do
printf "$in"
done
#c语言风格
for ((var assignment ; condition ; iteration process))
do
commands
done
#例:
for (( i = 1; i < 10 ; i++))
do
echo “Hello”
done
do和done之间的命令称为循环体,执行次数和list列表中常数或字符串的个数相同。for循环,首先将in后list列表的第一个常数或字符串赋值给循环变量,然后执行循环体,以此执行list,最后执行done命令后的命令序列。
for 循环中的列表
[root@localhost~]# for HOST in host1 host2 host3; do echo $HOST; done
host1
host2
host3
[root@localhost ~]# for HOST in host{1..3}; do echo $HOST; done
host1
host2
host3
[root@localhost~]# for EVEN in $(seq 2 2 8); do echo "$EVEN"; done;
2
4
6
8
for 循环奇数累加器
定义奇数累加器文件,并执行
参考脚本:
#!/bin/bash
sum=0
for i in {1..100..2}
do
let "sum+=i"
done
echo "sum=$sum"
for 循环文件展示
#!/bin/bash
for file in $(ls)
do
echo "file: $file“
done
while 循环
也称为前测试循环语句,重复次数是利用一个条件来控制是否继续重复执行这个语句。为避免死循环,必须保证循环体中包含循环出口条件
#!/bin/bash
sum=0;i=1
while(( i <= 100 ))
do
let "sum+=i"
let "i += 2"
done
echo "sum=$sum"
until 循环
#!/bin/bash
#奇数累加器
sum=0;i=1
until(( i > 100 ))
do
let "sum+=i"
let "i += 2"
done
echo "sum=$sum"
使用循环打印乘法表
参考脚本:
#!/bin/bash
for (( i = 1; i <=9; i++ ))do
for (( j=1; j <= i; j++ ))do
let "temp = i * j"
echo -n "$i*$j=$temp "
done
printf “n”
done
3 Shell 编程最佳实践
3.1 调试
Shell 脚本错误故障排除
编写、使用或维护 Shell 脚本的管理员不可避免地会遇到脚本的错误
- 错误通常是由于输入错误、语法错误或脚本逻辑不佳导致
- 在编写脚本时,将文本编辑器与 bash 语法高亮显示结合使用可以帮助使错误更明显
- 找到并排除脚本中错误的最直接方法是调试
- 避免在脚本中引入错误的另一种简单方法是在创建脚本期间遵循良好风格
调试模式
脚本上激活调试模式,请向脚本第一行中的命令解释器中添加 -x 选项
如此前乘法表,进行如下修改:
#!/bin/bash -x
bash 的调试模式将打印出脚本执行前由脚本执行的命令,已执行的所有 shell 扩展的结果都将显示在打印输出中,所有变量数据状态会实时打印,以供跟踪:
bash -x adder.sh
评估退出代码
[root@localhost tmp]# ls /etc/hosts
/etc/hosts
[root@localhost tmp]# echo $?
0
----------------------------------------------------------------
[root@localhosttmp]# ls /etc/nofile
ls: cannot access /etc/nofile: No such file or directory
[root@localhost tmp]# echo $?
2
- 在脚本中使用退出代码
- 一旦执行,脚本将在其处理所有内容之后退出,但是有时候可能需要中途退出脚本,比如在遇到错误条件时
- 可以通过在脚本中使用 exit 命令来实现这一目的,当脚本遇到 exit 命令时,脚本将立即退出并跳过对脚本其余内容的处理
[root@localhosttmp]# cat hello.sh
#!/bin/bash
echo "Hello World"
exit 1
[root@localhosttmp]# ./hello.sh
Hello World
[root@localhost tmp]# echo $?
1
3.2 语法风格
以下是要遵循的一些具体做法:
- 将长命令分解为多行更小的代码块,代码段越短,就越便于读者领悟和理解
- 将多个语句的开头和结尾排好,以便于查看控制结构的开始和结束位置以及它们是否正确关闭
- 对包含多行语句的行进行缩进,以表示代码逻辑的层次结构和控制结构的流程
- 使用行间距分隔命令块以阐明一个代码段何时结束以及另一个代码段何时开始
- 在整个脚本中通篇使用一致的格式
修改后的脚本:
#!/bin/bash
# 此脚本是读取关于kernel相关的软件包信息,并从RPM数据库中查询软件包的安装时间
PACKAGETYPE=kernel
PACKAGES=$(rpm -qa | grep $PACKAGETYPE)
# 循环处理信息
for PACKAGE in $PACKAGES; do
#查询每个软件包的安装时间截
INSTALLEPOCH=$(rpm -q --qf "%{INSTALLTIME}n" $PACKAGE)
# 把时间截转换为普通的日期时间
INSTALLDATETIME=$(date -d @$INSTALLEPOCH)
# 打印信息
echo "$PACKAGE was installed on $INSTALLDATETIME"
done
3.3 最佳实践示例
检测日志告警信息并邮件通知
场景描述:
某运营商要求对当前环境日志文件“error.log”进行长期监测,如发现关键词“danger”,则发送告警邮件
要求:
#!/bin/bash
# 日志检测脚本 test.sh
tail -f /var/log/error.log | while read danger;
do echo ‘mail’ >> /var/log/error.log;
sleep 1m;
done
#!/bin/bash
# 模拟报错脚本 addlog.sh
echo ‘danger’ >> /var/log/error.log
- 后台运行监测脚本:
./test.sh &
./addlog.sh
cat /var/log/error.log
原文地址:https://blog.csdn.net/qq_51601649/article/details/126001311
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_13773.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!