shell学习笔记

目录

Shell脚本简介

1, 什么是Shell?

Shell是一个命令解释器,它在操作系统的最外层,负责直接与用户对话,把用户的输入解释给操作系统,并处理各种各样的卿乍系统的输出结果,输出到屏幕返回给用户。这种对话方式可以是交互的方式(从键盘输入命令,可以立即得到Shen的回应),或非交互〔脚本)的方式。

下图中的黄色部分就是命令解释器Shell处于的操作系统中的位置形象图解:

Shell英文是贝壳的意思,从上图我们可以看出,命令解释器shell就像一个贝壳一样包住了系统核心

2, 什么是Shell脚本?

当linux命令或语句不在命令行下执行(严格说,命令行执行的语句也是shell脚本),是通过一个程序文件执行时,该程序就被称为Shell脚本或Shell程序,Shell程序很类似DOS系统下的批处理程序(扩展名*.bat)。用户可以在Shen脚本中敲入一系列的命令及命令语句组合。这些命令、变量和流程控制语句等有机的结合起来就形成了一个功能强大的Shell脚本。

脚本语言的种类

  • Shell脚本语言的种类

  • Bourne shell(包括sh,ksh,and bash)

  • Cshell(包括csh and tcsh)

shell脚本语言是弱类型语言,较为通用的shell有标准的Bourne
shell(sh)和Cshell(csh)。其中Bourne shell(sh)己经被bash shell取代。

查看系统的SHELL

[root@nfs ~]# cat /etc/shells 
/bin/sh
/bin/bash
/sbin/nologin
/bin/dash
/bin/tcsh
/bin/csh
[root@nfs ~]# echo $SHELL        #查看当前用户使用的Shell
/bin/bash

linux系统中的主流shell是Bash,它是Bourne Again Shell的缩写,Bash是由B
Shell发展而来的,但Bash与Sh稍有不同,它还包含了csh和ksh的特色,但大多数都可以不加修改地在Bash上运行

其他常用的脚本语言种类

PHP:PHP是网页程序,也是脚本语言。是一款更专注于web页面开发(前端展利)的脚本语言,例如:dedecms,discuz。PHP程序也可以处理系统日志,配置文件等,php也可以调用系统命令。

Perl脚本语言:比shell脚本强大很多,2010年以前很火,语法灵活、复杂,实现方式很多,不易读,团队协作困难,但仍不失为很好的脚本语言,存世大量的程序软件,运维人员了解就好了,无需学习这个。

python是近几年很火的语言,不但可以做脚本程序开发,也可以实现web程序以及软件的开发。近两年来越来越多的公司都要求会仍python。

shell脚本与php/perl/python语言的区别和优势

shell脚本的优势在于处理操作系统底层的业务(linux系统内部的应用都是shell脚本完成),因为有大量的linux系统命令为它做支撑,2000多个命令都是Shell脚本编程的有力支撑,特别是grep,awk,sed等。例如:一键软件安装、优化,监控报警脚本,常规的业务应用,shell开发更简单快速,符合linux运维的简单、易用、高效原则。

php,python优势在于开发运维工具以及web界面的管理工具,web业务的开发等处理一键软件安装、优化,报警脚本,常规的业务应用等php/python也是能够做到的,但是开发效率和复杂度比用Shell就差很多了。我们使用软件就是要根据业务需求来选择,扬长避短

PHP是网页程序,也是脚本语言。是一款更专注于web页面开发的脚本语言,python是近几年很火的脚本语言,它不但适合脚本程序开发(特别是复杂业务处理),也可以实现web程序以及各类工具软件的开发。

  • 常用操作系统的默认Shell

  • Linux是Bourne Again shell(bash)。

  • Solaris和FreeBSD缺省的是Bourne shell(sh)。

  • AIX下是Korn Shell(ksh)。

  • HP-UX缺省的是POSIX shell(Sh)。

shell脚本的建立和执行

  • shell脚本的建立

在linux系统中,Shell脚本(bash
Shell程序)通常是在编辑器(如vi/vim)中编写,由Unix/Linux命令、bash
shell命令、程序结构控制语句和注释等内容组成,这里推荐使用vim编辑器编写,可以事先做一个别名alias

echo "alias vi='vim'" >>/etc/profile
tail -1 /etc/profile
source /etc/profile
  • 脚本开头(第一行)

一个规范的shell脚本在脚本第一行会指出由哪个程序(解释器)来执行脚本中的内容这一行内容在linux

bash编程中一般为:

#!/bin/bash #也可以不写,linux下默认用bash来解析

也可以为 #!/bin/sh(软连接)

其中开头的“#!”字符又称为幻数,在执行bash脚本的时候,内核会根据“#!“后的解释器来确定该用哪个程序解释这个脚本中的内容。注意:这一行必须在每个脚本的第一行,如果不是第一行则为脚本注释行。

  • sh和bash的区别

早期的Bash与Sh稍有不同,它还包含了csh和ksh的特色,但大多数脚本都可以不修改地在sh上运行。

bash --version #查看bash的版本

如果脚本的开头第一行不指定解释器,那么,就要用对应的解释器来执行脚本

如果是Shell脚本,就用bash test.sh执行。

如果是python脚本,就用python test.py执行

如果是expect脚本,就用expect test.exp执行

其他的脚本程序几乎都是类似的执行方法。

  • 脚本注释

在shen脚本中,跟在(#)井号后面的内容表示注释,用来对脚本进行注释说明,注释部分不会被当做程序执行,仅仅是给用户看的,系统解释器是看不到的更不会执行,注释可自成一行,也可以跟在脚本命令后面与命令在同一行。开发脚本时,如果没有注释,团队里的其他人就很难理解脚本究竟在做什么,如果时间长了自己也会忘记。因此要尽量养成为所开发的shell脚本书写注释的习惯,书写注意不光是方便别人,也是方便自己。否则,写完一个脚本后也许几天后就记不起脚本的用途了,需要时再重新阅读,浪费很多宝贵的时间。特别是影响团队的协作的效率,以及给后来接手维护的人带来困难,注释尽量不用中文。

  • Shell脚本的执行

当shell脚本以非交互的方式(文件方式)运行时,它会先查找系统环境变量ENV,该变量指定了环境文件(通常是.bashrc,.bash_profile,/etc/bashrc,/etc/profile等),然后从该环境变量文件开始执行脚本,当读取了ENV的文件后,Shell才会开始执行shell脚本中的内容

特殊技巧:设置crond任务时,最好把系统环境变量在定时任务脚本中重新定义,否则一些系统环境变量将不被加载,这个问题要注意!

Shen脚本的执行通常可以采用以下几种方式

bash script-name或sh script-name(推荐使用)
path/script-name或./script-name(当前路径下执行脚本)
source script-name或. script-name#注意”."点号
sh <script-name或cat script-name|sh(同样适合bash)
exec command

shell脚本开发基本规范及习惯

  • 脚本第一行指定脚本解释器

#!/bin/bash或#!/bin/sh

  • 脚本开头加版本版权等的信息
#Date: 2016.04.16 10:34:28 CST

#Allthor:liwenbin

#Mail:1935845114@qq.com

#Function: .....

#Version:1.1

提示:可配置vim编辑文件时自动加上以上信息,方法是修改\~/.vimrc配置文件

  • 脚本中不用中文注释

尽量用英文注释,防止本机或切换系统环境后中文乱码的困扰。

  • 脚本以.sh为扩展名命名(不是必须)

例:script-name.sh

  • 代码书写优秀习惯技巧

1.成对的符号内容尽量一次写出来,防止遗漏。

2.[]中括号两端要有空格

3.流程控制语句一次书写完,在添加内容,

  • 通过缩进让代码更易读

    执行脚本方式内在区别

**1)bash script-name或sh script-name(推荐使用)**

**2)path/script-name或./script-name(当前路径下执行脚本)**

**3)sh <script-name或cat script-name|sh(同样适合bash)**

这三种方式是是新产生一个shell,然后执行相应的shell
scripts,当前shell环境中的变量不会应用在shell脚本中生效

4)source script-name或. script-name#注意”."点号

这种方式是不再产生新的shell进程,而在当前shell下执行一切命令,当前shell环境中的变量在shell脚本中可以使用

5)exec command

exec是以新的进程去代替原来的进程,但进程的PID保持不变。因此,可以这样认为,exec系统调用并没有创建新的进程,只是替换了原来进程上下文的内容。原进程的代码段,数据段,堆栈段被新的进程所代替。exec在对文件描述符进行操作的时候,也只有在这时,exec不会覆盖你当前的shell环境。运行完毕之后不回到原先的程序中去。

  1. shell帮助与资料推荐

http://www.gnu.org/software/bash/manual/bash.html

man bash

unix shell范例精解(第4版)-老男孩推荐大家此书

企业Shell常见面试题及企业实战案例深入浅出讲解视频课程http://edu.51cto.com/course/course_id-1511.html

Shell变量基础及深入

什么是变量

通过上面的例子我们可以得出一个变量的概念小结论:简单的说,变量就是用一个固定的字符串(也可能是字符数字等的组合),代替更多更复杂的内容,这个内容里可能还包含变量和路径,字符串等其他的内容,变量的定义式存在内存中的。使用变量的最大好处就是方便,当然,除了方便以外,很多时候在编程中使用变量也是必须的,否则就无完成相关的开发工作。

变量可分为两类:环境变量(全局变量)和局部变量

环境变量

环境变量用于定义shell的运行环境,保证Shell命令的正确执行,Shell通过环境变量来确定登陆用户名、命令路径、终端类型、登陆目录等,所有的环境变量都是系统全局变量,可用于所有子进程中,这包括编辑器、SheU脚本和各类应用(crond任务要注意)。

环境变量可以在命令行中设置,但用户退出时这些变量值也会丢失,因此最好在用户家目录下的.bash-profile文件中或全局配置/etc/bashrc,/etc/profile文件或者profile.d/下定义。将环境变量放入上述的文件中,每次用户登录时这些变量值都将被初始化一次

传统上,所有环境变量格式均为大写。环境变量应用于用户进程程序前,都应该export命令导出定义,例如:正确的环境变量定义方法为export
OLDGIRL=1。

环境变量可用在创建他们的Shell和从该Shell派生的任意子Shell或进程中。他们都被称为全局变量以区别局部变量。通常,环境变量应该大写。

有一些环境变量,比如HOME、PATH、SHELL、UID、USER等,在用户登陆前就己经被/bin/login程序设置好了。通常环境变量定义并保存在用户家目录下的.bash_profile文件或者全局的配置文件/etc/profile中。

自定义环境变量(全局变量)

如果想设置环境变量,就要在给变量赋值之后或设置变量时使用export命令。带-x选项的declare内置命令也可完成同样的功能。

  • 设置环境变量格式
  1. export 变量名=value # 或者先 定义变量,下面再export virate_name1 virate_name1 也可

  2. 变量名=value;export 变量名

  3. declare -x变量名=value

例子:

export GOODBOY=liwenbin

GOODBOY=liwenbin; export GOODBOY

declare -x GOODBOY=liwenbin

环境变量设置的常用文件

  • 用户的环境变量配置
[root\@nfs \~]\# **ls -al /root/.bash_profile /root/.bashrc #当前用户家目录下

\-rw-r--r--. 1 root root 176 5月 20 2009 /root/.bash_profile

\-rw-r--r--. 1 root root 176 9月 23 2004 /root/.bashrc
  • 全局环境变量的配置
/etc/profile

/etc/profile.d

/etc/bashrc

需要登陆后显示加载内容可以把脚本文件放在/etc/profile.d/下,设置可执行即可。

  • 显示与取消环境变量

  • 通过echo或printf打印环境变量

    echo \$USER

    printf "\$USER\n"

提示:在写Shell脚本时可以直接使用上面的系统默认的环境变量

  • env(printenv)或set显示默认的环镜变量

  • 用unset消除本地变量和环镜变量

局部变量

  • 定义本地变量

本地变量在用户当前的Shell生存期的脚本中使用。例如,本地变量OLDBOY取值为ett098,这个值只在用户当前Shell生存期中有意义。如果在Shell中启动另一个进程或退出,本地变量OLDBOY值将无效。

  • 普通字符串变量定义

    变量名=value

    变量名='value'

    变量名="value"

  • Shell中变量名及变量内容的要求

一般是由字母,数字,下划线组成。以字母开头,例如:

oldboy,oldboyl23,oldboy_training

变量的内容,可以使用单引号或双引号引起来,或不加引号。

  • 有关变量输出引号的说明

单引号:可以说是所见即所得:即将单引号内的所有内容都原样输出,或者描述为单引号里面看到的是什么就会输出什么。

双引号:把双引号内的所有内容都输出出来;如果内容中有命令(要反引下)、变量、特殊转义符等,会先把变量、命令、转义字符解析出结果,然后在输出最终内容来

不加引号:把内容输出出来,会将含有空格的字符串视为一个整体输出,如果内容中有命令(要反引下从、变量等,会先把变量、命令解析出结果,然后在输出最终内容来,如果字符串中带有空格等特殊字符,则不能完整的输出,需要改加双引号,一般连续的字符串,数字,路径等可以不加任何引号,…不过无引号的情况最好用双引号替代之。

反引号:一般用于引命令,执行的时候命令会被执行,相当于$()。

例1:下面的例子会输出什么结果。

a=192.168.1.2
b='192.168.1.2'
c="192.168.1.2"
echo "a=$a"
echo "b=$b"
echo"c=S{c}"

提示:
1)$c和${c}在这里等同。
2)需要在命令行实践以上内容。
思考:想一想a,b,c各是什么结果?

答案:
a=192.168.1.2
b=192.168.1.2
c=192.168.1.2

例2:想一想a,b,c各是什么结果?

a=192.168.1.2-$a 
b='192.168.1.2-$a'
c="192.168.1.2-$a"
echo "a=$a"
echo "b=$b"
echo "c=${c}"

[root@ ~]# a=192.168.1.2-$a 
[root@ ~]# echo echo "a=$a"
echo a.168.1.2-
[root@ ~]# b='192.168.1.2-$a'
[root@ ~]# echo "b=$b"
b=192.1.2-$a
[root@ ~]# c="192.168.1.2-$a"
[root@ ~]# echo "c=$c"
c=192.168.1.2-192.168.1.2-

提示:
第一种定义a变量的方式是直接定义变量内容,内容一般为简单连续的数字、字符串、路径名等。

第二种定义b变量的方式是通过单引号定义变量。这个方式的特点是:输出变量时引号里是什么就输出什么,即使内容中有变量也会把变量名原样输出。此法比较适合于定义显示纯字符串。

第三种定义c变量方式是通过双引号定义变量。这个方式的特点是:输出变量时引号里的变量会经过解析后输出该变量内容,而不是把引号中变量名原样输出,适合于字符串中附带有变量的内容的定义。I

习惯:数字不加引号,其他默认加双引号。

特殊例子:awk调用shell变量引号例子

[root@nfs ~]# ETT=123
[root@nfs ~]# awk 'BEGIN {print "$ETT"}'
$ETT
[root@nfs ~]# awk 'BEGIN {print '$ETT'}'
123
[root@nfs ~]# awk 'BEGIN {print $ETT}'      #输出空

一道实用linux运维问题的9种shell解答方法!http://oldboy.blog.51cto.com/2561410/760192

自定义变量的建议

(1).纯数字(不带空格),定义方式可以不加引号(单或双),例如:。
a.01dboyAge=33
b.NETWORKING=yes
(2).没特殊情况,字符串一般用双引号定义,特别是多个字符串中间有空格时,例如:
a.NFSD_MODULE="no load"
b.MyName="01dboy is a handsome boy."
(3).变量内容需要原样输出时,要用单引号?)。
a.OLDBOY_NAME='OLDBOY'+

  • 命令作为变量定义方法

  • 变量名=\命令\

  • 变量名=\$(命令)

    变量命名规范

  1. 变量命名要统一,最好使用全部大写字母,如”ACHE_ERRNUM;语义要清晰,能够正确表达变量内容的含义,过长的英文单词可采用前几个字符代替。多个单词连接使用“”号连接

引用时,最好以${APACHE_ERR_NUM}加大括号或"${APACHE_ERR_NUM}”外面加双引号方式引用变量

  1. 避免无含义字符或数字变量:例如COUNT,并不知道其确切含义。

  2. 全局变量和局部变量命名

  • 脚本中的全局变量定义,如OLDBOY_HOME或OLDBOYHOME,在变量使用时可用{}将变量括起用${OLDBOY_HOME}或者再加上双引号“\${OLDBOY_HOME}",但这不是必须的。

  • 脚本中局部变量定义:存在于脚本函数(function)中的变量称为脚本的局部变量,这样的变量要以local方式进行声明,使之只在本函数作用域内有效,防止变量在函数中的命名与变量外部程序中变量重名造成程序异常。

shell特殊变量

  • 位置变量

  • \$0
    获取当前执行的shell脚本的文件名,如果执行脚本带路径那么就包括脚本路径

  • \$n
    获取当前执行的shell脚本的第n个参数值,n=1..9,当n为0时表示脚本的文件名,如果大于9,就用大括号括起来\${10},参数以空格隔开。

  • \$*
    获取当前shell脚本所有传参的参数,将所有的参数视为单个字符串,相当于“\$1\$2\$"...,注意与\$#号的区别

  • \$#
    获取当前执行的shell脚本后面接的参数的总个数

  • \$@
    这个程序的所有参数”\$1""\$2""\$3"“…”,这是将参数传递给其他程序的最佳方式,因为他会保留所有嵌在每个参数里的任何空白。"\$\@"和"\$*",都要加双引号。

\$和\$@的区别:
\$
将所有的命令行所有参数视为单个字符串,等同于“\$1\$2\$3”
\$@将命令行每个参数视为单独的字符串,等同于“\$1” “\$2” “\$3” 这是将参数传递给其他程序的最佳方式,因为他会保留所有内嵌在每个参数里的任何空白。

linux下set和eval的使用小案例
https://blog.51cto.com/oldboy/1175971

[root@nfs ~]# runlevel=$(set -- $(runlevel); eval "echo \$$#" )
[root@nfs ~]# echo $runlevel
3
[root@lnmp scripts]# cat test.sh
echo $1 ${2}
echo "\$*: $*"
echo "\$#: $#"
[root@lnmp scripts]# bash test.sh 1 2 3
1 2
$*: 1 2 3
$#: 3

取脚本名:

[root@ ]# cat 0.sh 
dirname $0
basename $0
[root@ ]# sh /root/0.sh
/root
0.sh

进程状态变量

  • echo \$\$获取当前Shell的进程号(PID)

  • echo \$!执行上一个指令的PID

  • \$_在此之前执行的命令或脚本的最后一个参数

  • echo \$?获取执行的上一个指令的返回值.

提示:在脚本调用,一般用exit 0,脚本返回返回值给\$?,函数里retrun
0返回返回值给\$?。**

0表示运行成功;。
2权限拒绝;。
1~125表示运行失败,脚本命令、系统命令错误或参数传递错误;
126找到该命令了,但是无法执行;
127未找到要运行的命令。

[@]$ cat etiantian.sh 
echo '$0获取当前执行的shel脚本的文件名:'   $0
echo  '$n 获取当前执行的shell脚本的第n个参数值,n=1.9:'   ' $1'=$1  '$2'=$2
"\$3=$3"
echo  '$*获取当前shell的所有参数“$1$2$3..注意与#的区别:'   $*
echo  '$#获取当前shell命令行中参数的总个数:'  $#
echo  '$$获取当前shell的进程号(PID):'  $$
sleep 2 &
echo  '$!执行上一个指令的PID:'   $!
echo  '$?获取执行的上一个指令的返回值:'   $?
echo  '$@这个程序的所有参数"$1""$2""$3".":'   $@
echo  '$_在此之前执行的命令或脚本的最后一个参数:'   $_

判断备份是否成功

cd  /etc
tar zcf service.tar.gz  ./services  >&/dev/null
[ $? -eq 0 ]  &&  echo ok

bash内部变量

有些内部命令在目录列表时是看不见的,它们由Shell本身提供,常用的内部命令有:
echo,eval,exec,export,readonly,read,shift,wait,exit 和点(.)echo 变量名表。

将变量名表指定的变量显示到标准输出。

eval args读入参数args,并将它们组合成一个新的命令,然后执行。

exec 命令参数
当Shell执行到exec语句时,不会去创建新的子进程,而是转去执行指定的命令,当指定的命令执行完时,该进程(也就是最初的Shell)就终止了,所以Shell程序中exec后面的语句将不再被执行。

export 变量名=value。
Shell可以用export 把它的变量向下带入子Shell,从而让子进程继承父进程中的环境变量。但子Shell不能用export把它的变量向上带入父Shell。

readonly 变量名
只读变量用readonly显示所有只读变量

read 变量名表
从标准输入读字符串,传给指定变量
可以在函数中用local变量名的方式申明局部变量shift 语句。

shift 语句按如下方式重新命名所有的位置参数变量,即$2成为$1,$3成为$2…在程序中每使用一次shift语句,都使所有的位置参数依次向左移动一个位置,并使位置参数$#减1,直到减到0为止。
/usr/bin/ssh-copy-id 里有用到shift

变量子串的常用操作(了解)

常用操作如下表:man bash找本节资料“Parameter Expansion"

\${#String} 返回$String的长度

[root@nfs ~]# LIWENBIN="I am a goodboy"

[root@nfs ~]# echo ${#LIWENBIN}

14

[root@nfs ~]# expr length "$LIWENBIN"

14

[root@nfs ~]# echo $LIWENBIN|wc -L #使用命令计算

14

[root@nfs ~]# echo ${LIWENBIN}|awk -F "" '{print NF}'

14

[root@nfs ~]# echo $LIWENBIN|wc -c

15

   ${String:position} 在$String中,从位置$position之后开始截取

   ${String:position:length}
    在$String中,从位置$position之后开始截取长度为length的字符串

[root@nfs ~]# echo ${LIWENBIN:2}

am a goodboy

[root@nfs ~]# echo ${LIWENBIN:2:6}

am a g

-   ${String#Substring} 从变量$string开头开始删除最短匹配$substring的字符串

-   ${String##Substring}
    从变量$string开头开始删除最长匹配$substring的字符串

[root@nfs ~]# LIWENBIN=abcgoodboyABC

[root@nfs ~]# echo ${LIWENBIN#a*o}

odboyABC

[root@nfs ~]# echo ${LIWENBIN##a*o}

yABC

-   ${String%Substring} 从变量$string结尾开始删除最短匹配$substring的字符串

-   ${String%%Substring} 从变量$string结尾开始删除最长匹配$substring的字符串

[root@nfs ~]# LIWENBIN=abcgoodboyABC

[root@nfs ~]# echo ${LIWENBIN%%o*C}

abcg

[root@nfs ~]# echo ${LIWENBIN%o*C}

abcgoodb

-   ${string/substring/replace} 使用replace,来代替第一个匹配的substring

-   ${string/#substring/replace}从开头匹配String变量中的Substring,就用replace来替换匹配的substring

-   ${string/%substring/replace}从结尾匹配String变量中的Substring,就用replace来替换匹配的substring

[root@nfs ~]# LIWENBIN=abcboygoodboyABC

[root@nfs ~]# echo ${LIWENBIN/boy/girl}

abcgirlgoodboyABC

[root@nfs ~]# echo ${LIWENBIN/#abc/ABC}

ABCboygoodboyABC

[root@nfs ~]# echo ${LIWENBIN/%ABC/abc}

abcboygoodboyabc

拓展:其他变量的替换

(初学者可跳过)

  • ${value:-word}
    如果变量名存在且非null,则返回变量的值。否则,返回word字符串。用途:如果变量未定义,则返回默认值。
[root@nfs ~]# echo $test            例1
                                 #表示test变量为空
[root@nfs ~]# result=${test:-UNSET}
[root@nfs ~]# echo $result
UNSET
[root@nfs ~]# test=liwen            例2
[root@nfs ~]# result=${test:-UNSET}
[root@nfs ~]# echo $result
liwen
[root@nfs ~]# path=/tmp1            例3
[root@nfs ~]# find ${path:-/tmp} -name "mong*"|xargs rm -f
pidfile=${PIDFILE-/var/run/httpd/httpd.pid}  
  • \${value:=word}
    如果变量名存在且非null,则返回变量值。否则,设置这个变量为word,并返回其值。用途:如果变量未定义,则设置变量为默认值,并返回默认值。范例:\${value:=word},如果value未定义,则设置value值为word,并,返回表达式的值也为word。

变量的数值(整数)计算

(())用法:(此法很常用)

 [root@nfs ~]# ((a=1+2**3-4%3))
[root@nfs ~]# echo $a
8
[root@nfs ~]# echo $((a=1+2**3-4%3))
8
[root@nfs ~]# ((a=a+1))
[root@nfs ~]# echo $a
9
[root@nfs ~]# a=1
[root@nfs ~]# a=$((a+4))
[root@nfs ~]# echo $a
5

(( ))内可直接用< >
((0<1))
echo $?

let命令用法

[root@nfs ~]# a=3
[root@nfs ~]# let a=a+5
[root@nfs ~]# echo $a
8

expr命令用法

[root@nfs ~]# expr 2 - 1
1
[root@nfs ~]# expr 2 \* 4
8
[root@nfs ~]# a=0
[root@nfs ~]# a=`expr $a + 1`
[root@nfs ~]# echo $a
1
[root@nfs ~]# b=3
[root@nfs ~]# expr $[$a + $b]
4

提示:expr用法

  • 注意:运算符及计算的数字左右都有至少一个空格。

  • 使用乘号时,必须用反斜线屏蔽其特定含义。因为shell可能会误解星号的含义。

特殊用法

[root@nfs ~]#less /usr/bin/ssh-copy-id
if expr "$1" : ".*\.pub" > /dev/null ; then
[root@nfs ~]#expr "data.zip" : ".*zip" &>/dev/null && echo 1 || echo 0 
1

expr案例

判断变量是否为整数脚本

read -p "please input:" a
expr $a + 1 &>/dev/null
[ $? -eq 0 ] && echo int||echo chars

变量的处理计算变量长度与其他不同方法的耗时对比

[root@nfs ~]# chars=`seq -s" " 100`
[root@nfs ~]# expr length "$chars"
291
[root@nfs ~]# echo ${#chars}
291
[root@nfs ~]# echo $chars|wc -L
291
[root@nfs ~]# time for i in $(seq 10000);do count=${#chars};done;             #最快

real    0m1.165s
user    0m1.163s
sys     0m0.001s
[root@nfs ~]# time for i in $(seq 10000);do count=`echo $chars|wc -L`;done;   #最慢

real    0m18.045s
user    0m0.764s
sys     0m1.942s
[root@nfs ~]# time for i in $(seq 10000);do count=`expr length "$chars"`;done; #适中

real    0m12.007s
user    0m2.065s
sys     0m4.206s

我们看到速度相差几十到上百倍,一般情况调用外部命令处理相差较大。在shell编程中,我们应尽量用内置操作或函数完成。

bc命令(也可以小数)

[root@nfs ~]# cat /etc/shells 
/bin/sh
/bin/bash
/sbin/nologin
/bin/dash
/bin/tcsh
/bin/csh
[root@nfs ~]# echo $SHELL        #查看当前用户使用的Shell
/bin/bash

通过一条命令计算输出1+2+...+10的表达式

[root@nfs ~]# cat /etc/shells 
/bin/sh
/bin/bash
/sbin/nologin
/bin/dash
/bin/tcsh
/bin/csh
[root@nfs ~]# echo $SHELL        #查看当前用户使用的Shell
/bin/bash

$[]的用法

[root@nfs ~]# cat /etc/shells 
/bin/sh
/bin/bash
/sbin/nologin
/bin/dash
/bin/tcsh
/bin/csh
[root@nfs ~]# echo $SHELL        #查看当前用户使用的Shell
/bin/bash

用shell脚本实现杨辉三角的4个实例!http://oldboy.blog.51cto.com/2561410/756234

计算器脚本:

echo $*|bc

Shell变量的输入(read)

shell变量除了可以直接赋值或脚本传参外,还可以使用read命令输入,read为内置命令,通过help
read查看帮助。最常用的两个参数如下:

  • -p prompt:设置提示信息。

  • -t timeout:设置输入等待的时间,单位默认为秒。

read -t 5 -p "please input a character:" a

条件测试与比较

在bash的各种流程控制结构中通常要进行各种测试,然后根据测试结果进行工作,有时也会通过与if等条件语句相结合,更方便的完成判断。

条件测试通常有如下3种语法形式:

语法格式l:test <测试表达式>
语法格式2:[ <测试表达式> ]
语法格式3:[[ <测试表达式> ]]

说明:

  1. 上述语法格式l和语法格式2的写法是等价的。语法格式3为扩展的test命令,老男孩建议大家使用语法格式2。

2) 在[[]]中可以使用通配符进行模式匹配。&&、||、>、<等操作符可以应用于[[]]中,但不能应用于[]中,在[]中可以使用-a、-o通配符。

3) 对于整数的关系运算,也可以使用shell中的算术运算符(())。

条件测试语法及示例

man test查看帮助,许多参数可应用与其他格式

[root@nfs ~]# test -f /etc/hosts && echo 1 ||echo 0      #测试是否为文件
1
[root@nfs ~]# test ! -f /etc/hosts && echo 1 ||echo 0
0
[root@nfs ~]# [ -f /etc/hosts ]&& echo 1||echo 0
1
 [root@nfs ~]# [ ! -f /etc/hosts ]&& echo 1||echo 0
0
[root@nfs ~]# [[ -f /etc/hosts ]]&& echo 1||echo 0
1
[root@nfs ~]# [[ ! -f /etc/hosts ]]&& echo 1||echo 0
0
[root@nfs ~]# [[ -f /etc/hosts && ! -z /etc/hosts ]]&& echo 1||echo 0
1
[root@nfs ~]# [ -f /etc/hosts -a ! -z /etc/hosts ]&& echo 1||echo 0

文件测试表达式

常用的文件测试操作符

  • -f文件,英文file:文件存在且为普通文件则真,即测试表达式成立。

  • -d文件,英文directory:文件存在且为目录文件则真,即测试表达式成立。

  • -s文件,英文size:文件存在且文件大小不为0则真,即测试表达式成立

  • -e文件,英文exist:文件存在则真,即测试表达式成立。只要有文件就行
    如判断MySQL sock文件是否存在可用-e, -f识别不出来

  • -r文件,英文read:文件存在且可读则真,即测试表达式成立。

  • -w文件,英文write:文件存在且可写则真,即测试表达式成立。

  • -x文件,英文executable:文件存在且可执行则真,即测试表达式成立。

  • -L文件,英文link:文件存在且为链接文件则真,即测试表达式成立。

  • fl-nt f2,英文newer than:文件f1比文件f2新则真,根据修改时间判断

  • fl-ot f2,英文older than:文件f1比文件f2 旧则真,根据修改时间判断

特别说明:这些操作符号对于[[]]、[]、test几乎是通用的

条件表达式判断条件后面执行多条命令语句写法

[root\@nfs \~]# [ -r /etc/hots ] || { echo 1;echo 2; }

1

2

字符串测试表达式

字符串测试操作符:man test查看帮助

  -z "字符串": 若串长度为0则真,-z可以理解为zero。
  -n "字符串": 若串长度不为0则真,-n可以理解为no zero。
  "串1" = "串2":若串1等于串2则真,可使用“==”代替"=".
  "串1" != "串2":若串1不等于串2则真

特别注意,

  • 以上表格中的字符串测试操作符号务必要用""引起来。

  • 字符串或字符串变量比较,比较符号两端最好都有空格,多参考系统脚本。

[root@nfs ~]# sed -n '30,31p' /etc/init.d/network 
# Check that networking is up.
[ "${NETWORKING}" = "no" ] && exit 6
[root@nfs ~]# test1=abc
[root@nfs ~]# test2=ABC
[root@nfs ~]# [ "${#test1}" = "${#test2}" ] && echo 1
1

整数二元比较操作符

在[]以及test中使用的比较符 在[[]]以及(())中使用的比较符 说明
-eq ==或= equal的缩写,相等
-ne != not equal的缩写,相等
-gt > 大于,greater than
-ge >= 大于等于greater equal
-lt \< 小于,less than
-le \<= 小于等于,less equal

提示

1)  "<”符号意思是小于,if [[ "$a" < "$b" ]],if [ "$a" \< "$b" ]。在单[]中需要转义,因为shell也用<和>重定向
2)  ">”符号意思是小于,if [[ "$a" > "$b" ]],if [ "$a" \> "$b" ]。在单[]中需要转义,因为shell也用<和>重定向
3)  "="符号意思是等于,if [[ "$a" = "$b" ]] if [ "$a" = "$b" ]在单[]中不需要转义

实际测试结果结论:

  1. 整数加双引号也是对的。

  2. [[]]用-eq等的写法也是对的。

  3. []用>号写法语法没错,逻辑结果不对。

逻辑操作符

逻辑连接符

在[]和test中使用的逻辑操作符 在[[]]中使用的逻辑操作符 说明
-a && and与,两端都为真,则真
-o || or或,两端有一个为真则真
not非,相反则为真

示例

比较整数大小

[root@MySQL scripts]# cat cmp1.sh   
#!/bin/sh

#no.1 judge arg nums.
[ $# -ne 2 ]&&{
  echo "USAGE:"$0" num1 num2"
  exit 1
}
#no.2 judge if int.
expr $1 + $2 &>/dev/null
[ $? -ne 0 ]&&{
 echo "pls input two nums:"
 exit 2
}

#no.3  compare two int.
[ $1 -lt $2 ]&&{
  echo "$1<$2"
  exit 0
}

[ $1 -eq $2 ]&&{
  echo "$1=$2"
  exit 0
}
[ $1 -gt $2 ]&&{
  echo "$1>$2"
  exit 0
}

输入两个数字进行比较

#!/bin/bash
echo -n  "输入俩数字m n":
read m n
echo $m $n
echo '-----------'

#if [ $m -eq 1 ] && [ ${n} -gt 2 ];then 
#       echo 'good'
#else
#       echo 'bad'
#fi

#[ ${m} -eq 1 -a $n -gt 2 ] && echo 'good' || echo 'bad'

[[ ${m} -eq 1 && $n -gt 2 ]] && echo 'good' || echo 'bad'

特殊条件判断语法 && ||

1)条件表达式,大括号的用法。
格式如下。当条件不成立时就会执行大括号内的命令内容:。

[ 3 -ne 31 ]  ||  {
echo  "I am oldboy"
echo "I am coming"
exit 1
}

如果写在一行里面,里面的每个命令还需要用分号结尾,如下所示:

[3-ne 3]ll{echo"I am oldboy";echo"I am coming";exit 1}

提示:本例的用法很简洁,但是理解起来不如if条件句容易,因此,请读者根据自身情况使用。

分支与循环结构

if条件句 单分支结构

if [条件]
    then
      指令
fi
或
if[条件]; then
    指令
fi

特殊写法:if [ -f "$file" ];then echo 1;fi
相当于:[ -f "$file" ]&& echo 1
if [ -f "$file" ];then
    echo 1
fi

if判断条件正则表达式

if [[ "${line}" =~ ^#[[:space:]]at* ]];then 
    posion=${line:5}

if双分支结构

if 条件
    then
        指令集
    else
        指令集
fi

特殊写法:if [ -f "$file" ];then echo 1;else echo 0;fi相当于
[ -f "$file" ]&& echo 1||echo 0

if多分支结构

if 条件 1
then
    指令1
elif 条件2
then
    指令2
else
    指令3
fi

注意:可以有多个elif

示例

多级菜单

#!/bin/bash

#echo -e "1. install lamp \n2. install lnmp \n3.xxx" 

#echo '
########
#1.banana
#2.apple
########
#'

cat <<END
1.lamp
2.lnmp
3.xxx
END

read -p "please input a number:" n

if [ $n -eq 1 ];then
        echo "install $n..."
elif [ $n -eq 2 ];then
        echo 'lnmp...'
else
        echo 'xxx'
fi
~          

判断输入是否是数字

a=$1
b=$2

if [ $# -ne 2 ];then
        echo "usage: num1 num2"
        exit 1
fi

[ -n "`echo $1 | sed 's/[0-9]//g'`" ] && echo "第一个参数必须为数字" && exit 1
[ -n "`echo $2 | sed 's/[0-9]//g'`" ] && echo "第二个参数必须为数字" && exit 1

if [ $a -gt $b ]
then
    echo "yes,$a > $b"
elif [ $a -eq $b ]
then
    echo "yes,$a=$b"
else echo "yes,$a < $b"
fi
~     

expr计算判断是否为数字

expr $1 + 0 >/dev/null 2>&1
[ $? -eq 0 ] && echo "int"

简单备份脚本示例

#!/bin/sh 
FILEPATH="/server/scripts"
if [ -e "$FILEPATH/if3. sh" ]
then   echo "$FILEPATH/if3. sh is exist."
fi 

if   [ ! -e"SFILEPATH/if3. sh" ]
then
    [ ! -d  $FILEPATH] && mkdir -p $FILEPATH
    [ -d SFILEPATH ] && {
     cd $FILEPATH
     touch if3. sh 
     echo "if3. sh is touched."
}
fi
[ !-d /server/backup ] && mkdir /server/backup
 mysqldump -uroot -p233 -A -B >/server/backup/a. sql
[ !-f /server/backup/a. sql  ] && mail-s "bak faile"  asdfasdf@ qq. com < mail.file

监控web和db服务是否正常方法

https://blog.51cto.com/oldboy/942530

  • 端口

    本地:netstat/ss/lsof

    远程:telnet/nmap/nc

  • 进程ps -ef|grep 进程名|grep -v grep|wc -l

  • wget/curl(http方式,判断根据返回值或者返回内容)。

  • header(http),(http方式,根据状态码判断)

  • 数据库特有,通过mysql客户端连接连接,根据返回值或者返回内容。

echo -e "\n" | telnet baidu.com 80 | grep Connected
此法Telnet无需交互

以DB为例大全讲解

[ "`netstat -lntup|grep 3306|awk -F "[ :]+" '{print $5}'`" = "3306" ] #字符串比较,正确写法
最好用字符串判断,这样取出来端口即使为空也不会报错。
[ `netstat -lntup|grep 3306|awk -F "[ :]+" '{print $5}'` -eq 3306 ]   #数字比较,会出现错误
[ `ps -ef|grep mysql|grep -v grep|wc -l` -gt 0 ]           #进程比较,注意脚本名不能含mysql
mysql -uroot -p123456 -S /data/3306/mysql.sock -e "select version();" &>/dev/null #返回值$?
[ `lsof -i tcp:3306|wc -l` -gt 0 ]
[ `nmap 192.168.10.104 -p 22 2>/dev/null|grep open|wc -l` -gt 0 ]
curl --connect-timeout 5 127.0.0.1 &>/dev/null     #然后根据$?返回值判断
curl -s -I 127.0.0.1|head -1                       #根据header判断
-s 安静 不加会过滤出很多0000------
curl -I -s -w "%{http_code}\n" 127.0.0.1 -o /dev/null  #根据http状态码判断
if [ "`curl -I -s -w "%{http_code}\n" 10.0.0.5 -o /dev/null`" != "200" ]
wget -T 4 -q --spider 10.0.0.5 &>/dev/null
# -T 超时,超过4秒无法访问就退出, -q 安静访问

通过端口判断更简易实现脚本:

#!/bin/bash
port=`netstat-1nt | grep 3306|wc -l`
if [ $port-ne 1];then
/data/3306/mysql start 
fi

进程和端口同时判断

#!/bin/bash
portNum=`netstat -lnt|grep 3306|wc -l`
mysqlProcessNum=`ps -ef | grep mysqld | grep -v grep | wc -l`
#if [ $portNum -eq 1 -a $mysqlProcessNum -eq 2 ];then
if [ $portNum -eq 1 ] && [ $mysqlProcessNum -eq 2 ];then
    echo "db is running"
else
systemctl start mysqld
fi

较专业的写法
https://blog.51cto.com/oldboy/986905

#!/bin/bash  
#created by oldboy QQ 49000448  
#date:20100918  
MYUSER=root  
MYPASS="oldboy" 
MYSOCK=/data/3306/mysql.sock  
MySQL_STARTUP="/data/3306/mysql" 
LOG_PATH=/tmp  
LOG_FILE=${LOG_PATH}/mysqllogs_`date +%F`.log  
MYSQL_PATH=/usr/local/mysql/bin  
MYSQL_CMD="$MYSQL_PATH/mysql -u$MYUSER -p$MYPASS -S $MYSOCK" 
#→全变量定义方式,显得更专业。  
$MYSQL_CMD -e "select version();" >/dev/null 2>&1  
if [ $? -eq 0 ]   
then 
echo "MySQL is running! "   
exit 0  
else 
$MySQL_STARTUP start >$LOG_FILE#→日志也是变量。  
sleep 5;  
$MYSQL_CMD -e "select version();" >/dev/null 2>&1  
if [ $? -ne 0 ]  
then   

#while true
#do
#    killall mysqld >/dev/null  2>&1
#    [ $? -ne 0 ] && break
#done

for num in `seq 10`#→通过for循环来杀死mysqld,真正杀死则退出循环或每隔个两秒杀一次,一共杀10次。 或者用上面注释的while死循环,killall mysqld   如果$? 执行结果为0就继续killall(如果完全杀掉了,会报进程不存在)
do  
killall mysqld>/dev/null 2>&1   
[ $? -ne 0 ] && break;  
sleep 2  
done  
$MySQL_STARTUP start >>$LOG_FILE   
fi  

#[root@ali_ql ~]# mysql -uroot -pxxxxxx -e "select version();"  # -e 非交互式
#+------------+
#| version()  |
#+------------+
#| 5.5.62-log |
#+------------+
#[root@ali_ql ~]# echo $?
#0
#

$MYSQL_CMD -e "select version();" >/dev/null 2>&1 && Status="restarted" || Status="unknown"#→这个逻辑更准确。 
echo "MySQL status is $Status" >>$LOG_FILE  
mail -s "MySQL status is $Status" 31333741@qq.com < $LOG_FILE  
#→把上面的Status作为结果标题传给邮件,当然你可以做短信,语音通话报警。  
fi  
exit 

action ok false显示

#!/bin/bash
[ -f /etc/init.d/functions ] && source /etc/init.d/functions || exit 1  # action需要用到这个函数库,判断是否存在并加载
httpCode=`curl -s -I 127.0.0.1 |head -1| cut -d" " -f2`
if [ "$httpCode" -eq "200" ];then
    action "nginx running" /bin/true
else
    action "nginx not" /bin/false
    sleep 1
    /application/nginx/sbin/nginx && \
    action "nginx is stated" /bin/true
fi

传参ip 端口 检测

#!/bin/sh 
if  [ $# -ne 2 ];then 
    echo "Usage:$0 ip port"
    exit 
fi 
HttpPortNum=`nmap $1 -p $2 | grep open | grep-v grep | wc -l`
if [ $HttpPortNum -eq 1];then 
    echo "$1 $2 is open."
else 
echo "$1 $2 is closed."

精确过滤的四种方法

grep "\b200\b" 
grep -w "200"
grep -x "200"
grep "^200$"

远程监控博文:http://oldboy.blog.51cto.com/2561410/942530

Case结构条件句

case "变量" in
    值1)
        指令1...
    ;;
    值2)
        指令2...
    ;;
    *)
        指令3...
esac

输入数字并打印

#!/bin/bash

read -p "input a number:" ans

case "$ans" in
1)
    echo "you input is 1"
;;
2)
    echo "you input is 2"
;;
[3-9])
    echo "you input is $ans"
;;
*)
    echo "the num you input must be less 9"
    exit
;;
esac

打印不同水果颜色

RED_COLOR='\E[1;31m'
GREEN_COLOR='\E[1;32m'
YELLOW_COLOR='\E[1;33m'
BLUE_COLOR='\E[1;34m'
RES='\E[0m'

echo '
#######
1.banana|BANANA
2.apple
xxxxxxxxxxxx
#######
'

read -p "input the fruit you like:" ans

case $ans in
apple|APPLE)
    echo -e "you like is ${RED_COLOR}"$ans."${RES}"
    ;;
banana|BANANA)
    echo -e "you like is ${YELLOW_COLOR}$ans${RES}"
    ;;
pear|PEAR)
    echo -e "you like is ${GREEN_COLOR}$ans${RES}"
    ;;
*)
    exit 5
;;
esac

case语句小结

1、case语句就相当于多分支的if语句。case语句优势更规范、易读。

2、case语句适合变量的值少,且为固定的数字或字符串集合。(1,2,3)或(start,stop,restart)

3、系统服务启动脚本传参的判断多用case语句。多参考rpcbind/nfs/crond脚本。

  • 要掌握的linux系统标杆脚本

/etc/init.d/functions

/etc/rc.d/rc.sysinit

/etc/init.d/httpd

/etc/init.d/nfs

/etc/init.d/rpcbind

while循环

当型循环语法

while 条件  # 条件满足一直做

    do
    指令
done

直到型

until 条件。
    do       #先做,直到条件满足退出
    指令……
done   # 提示:until应用场合不多见,了解就好。

示例

求1+2+...+100.

cat sum.sh 
#!/bin/bash
sum=0
i=1
while ((i<101))
do 
((sum=sum+i ))
((i++))
done
echo $sum

每隔2秒记录一次系统负载情况

cat >while.sh<<EOF 
#!/bin/bash
while true
do 
   uptime
   sleep 2            #休息2秒,同usleep 2000000
done
EOF

定时访问网站地址

[oldboyeoldboy ~]$ cat while-4.sh
#!/bin/sh 
while true 
do curl -I -s  http://blog.etiantian.org/  | head -1
sleep 100
done

上面方法可用于测试负载均衡是否平均分配到了节点

判断网站是否正常

#!/bin/sh 
. /etc/init.d/functions
while true
do
status=`curl -I -s --connect-timeout 10 $1 | head -1 | cut -d" " -f2`
if [ "$status" = "200" ];then
    action "this url is good" /bin/true
else
    action "this url is bad" /bin/false
fi
sleep 2
done

防止脚本执行中断的方法:

1)sh while.sh &

2)nohup /server/scripts/while.sh &

3)screen保持会话,总结此命令

脚本在后台执行知识拓展

& 把脚本wh1le-.sh放到后台执行

ctrl+c 停止执行当前脚本或任务

ctrl+z 暂停执行当前脚本或任务,然后bg放到后台执行

bg 把当前脚本或任务放到后台执行,background

fg 当前脚本或任务拿到前台执行,如果有多个任务,可加编号指定某个到前台执行

jobs 查看当前执行的脚本或任务

kill %编号 杀掉进程

例如:sh
while.sh执行脚本,ctrl+z暂停执行当前脚本或任务,然后bg放到后台执行,jobs查看当前后台执行的脚本,fg调到前台执行,ctrl+c停止执行当前脚本或者kill
%1停止

在后台运行作业时要当心:需要用户交互的命令不要放在后台执行,因为这样你的机器就会在那里傻等。不过,作业在后台运行一样会将结果输出到屏幕上,干扰你的工作。如果放在后台运行的作业会产生大量的输出,最好使用下面的方法把它的输出重定向到某个文件中:command
>out.file 2>&1 &。

例如:

[root\@mysql \~]# sh while.sh >/while.log 2>&1 &

有关进程管理命令

  • jobs:显示后台程序

  • ki11,ki1Iall,pkill:杀掉进程

  • crontab:设置定时

  • ps:查看进程

  • pstree:显示进程状态树

  • top:显示进程

  • nice:改变优先权

  • nohup:用户退出系统,脚本迷续工作

  • pgrep:查找匹配条件的进程

  • strace:跟踪一个进程的系统调用情况

  • itrace:跟踪进程调用库函数的情况

  • vmstat:报告虚拟内存统计信息

网站访问慢的案例:https://blog.linuxeye.com/343.html

拓展:while按行读文件的方式

一行一行处理
方式一:

exec <FILE
sum=0
while read line
do
    cmd
done

方式二:

cat ${FILE_PATH}|while read line
do
    cmd
done

方式三:

while read line
do
    cmd
done<FILE

计算apache日志文件访问字节数之和

(223.91.110.10 - - [24/Apr/2020:18:01:00 +0800] "POST /api/note/updateNote?token=5e835ca09bb67a6c3200001d& HTTP/1.1" 200 793 - "Needle/0.7.10 (Node.js v6.1.0; win32 x64)"
)
状态码后边的数字

# awk '{print $10}' /home/wwwlogs/access.log  | grep -v '"-"' | tr "\n" "+"|sed  -r 's#(.*)#\10#g'

sum=0
while read line
do
    size=`echo $line | awk '{print $10}'`
    [ "$size" == "-" ] && continue
    ((sum=sum+$size))
done < /var/log/httpd/access_log
[ -n "$sum" ] && echo "$sum"

While循环小结

1、while循环的特长是执行守护进程以及我们希望循环不退出持续执行的情况,用于频率小于1分钟循环处理(crond),其他的while循环几乎都可以被我们即将要讲for循环替代。

2、case语句可以if语句替换,一般在系统启动脚本传入少量固定规则字符串,用case语句,其他普通判断多用if。

3、一句话,if,for语句最常用,其次while(守护进程),Case(服务启动脚本)

for循环结构

一般for循环结构语法

for 变量名 in 变量取值列表
do
    指令...
done

提示:在此结构中“in变量取值列表”可省略,省略时相当于in "\$@",当于使用for i in
"\$@"

范例

关闭|开启自启服务服务

# 取出3级别开机自启的服务,全关掉
for server in `chkconfig --list|grep 3:on|awk '{print $1}'
do chkconfig --level 3 $server off
done

## 开启这几个服务
for server in "network|crond|rsyslog|sshd"
do 
    chkconfig --level 3 $server on
done

打印5到1

for num in 5 4 3 2 1   # 或in `seq -s " " 5 -1 1`
do
    echo $num
done

####
for num in 10.0.0.{5..1}
do
    echo $num
done

批量改名(rename命令更方便)

for i in `ls *.jpg`
do
    mv $i `echo $i |sed -r 's#(.*)jpg#\1gif#g'`
done

#for filename in `ls *.gif`
#do
#    mv $filename  `echo $filename | cut -d"." -f1`.jpg
#done

awk改名

准备测试数据
touch stu_102999_1_finished.jpg
touch stu_102999_2_finished.jpg
touch stu_102999_3_finished.jpg
touch stu_102999_4_finished.jpg
touch stu_102999_5_finished.jpg
把_finished去掉,并改为gif

[root@centos7 ]# ls *.jpg | awk -F"_finished" '{print "mv " $0 " " $1".gif"}'
mv stu_102999_1_finished.jpg stu_102999_1.gif
mv stu_102999_2_finished.jpg stu_102999_2.gif
mv stu_102999_3_finished.jpg stu_102999_3.gif
mv stu_102999_4_finished.jpg stu_102999_4.gif
mv stu_102999_5_finished.jpg stu_102999_5.gif

[root@centos7 ]# ls *.jpg | awk -F"_finished" '{print "mv " $0 " " $1".gif"}' | bash
[root@centos7 ]# ls
stu_102999_1.gif  stu_102999_2.gif  stu_102999_3.gif  stu_102999_4.gif  stu_102999_5.gif

openvpn脚本部分。

#Location of openvpn binary
openvpn=""
openvpn_1ocations="/usr/sbin/openvpn  /usr/1ocal/sbin/openvpn"
for location in $openvpn_locations  # 变量方式
do
    if[-f"$location"]
    then
      openvpn=$location
    fi 
done

随机数的获取

  • 通过系统环境变量(\$RANDOM)
[root@nfs ~]# echo $RANDOM
31055
[root@nfs ~]# echo $RANDOM|md5sum|cut -c 2-9
813ac176
[root@nfs ~]# echo "$RANDOM liwenbin"|md5sum|cut -c 2-9
1b4ffd5f

\$RANDOM取值范围为0-32767,一般此值范围太小,加密区字符串容易被破解,可以在其后再加字符串加密

  • 通过openssl产生随机数
[root@nfs ~]# openssl rand -base64 8
g5PF6+Y/E2E=
[root@nfs ~]# openssl rand -base64 10
tv6c0ecbGff/VA==
  • 通过时间获得随机数(date)
[root@nfs ~]# date +%s
1461566215
[root@nfs ~]# date +%s%N
1461566220185060377
  • /dev/random设备

/dev/random和/dev/urandom是unix系统提供的产生随机数的设备,很多应用都需要使用random设备提供的随机数,比如ssh
keys, SSL keys, TCP/IP sequence numbers等等。

/dev/random设备会封锁,直到系统产生的随机字符流已经充分够用,所以耗用时间较长;/dev/urandom设备不会封锁,数据的随机程度不高,但是一般情况已经够用

[root@nfs ~]# head /dev/urandom|cksum
3529867934 2610
[root@nfs ~]# head /dev/urandom|cksum
4037679275 2232
  • 使用UUID产生

UUID含义是通用唯一识别码 (Universally Unique Identifier)
,UUID是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的

[root@nfs ~]# cat /proc/sys/kernel/random/uuid 
b846d5e0-683d-4aa1-b225-2855444d8be9
[root@nfs ~]# cat /proc/sys/kernel/random/uuid 
0d9ab2b0-b18b-48b9-94a1-a5121efb755e
  • expect
yum install expect -y
[root@nfs ~]# mkpasswd -l 8
kgQ>jJ34

批量创建文件改名账号改密

问题1:使用for循环在/oldboy目录下批量创建10个文件,名称依次为:。
oldboy-1oldboy-2oldboy-3

for i in oldboy-{1..10}
do
    touch $i
done

问题2:将以上文件名中的oldboy全部改成linux。

 for i in `ls oldboy*`;do echo $i|awk -F'-'  '{print "mv " $0 " " "linux-"$2}' | bash ;done

问题3:批量创建10个系统帐号oldboy01-oldboy10并设置密码(密码不能相同)。

for user in oldboy{01..10}
do
    useradd $user
    echo passwd$user | passwd --stdin $user
done

问题4:批量创建10个系统帐号oldboy01-oldboy10并设置密码(密码为随机8位字符串)。

. /etc/init.d/functions
for user in $(seq -w 10)
do
    password=`echo $(date +%N)$RANDOM | md5sum |cut -c 2-9`
    useradd olduser-$user >&/dev/null && user_status=$?
    echo "$password" | passwd --stdin olduser-$user >&/dev/null && pass_status=$?

    if [ $user_status -eq 0 -a $pass_status -eq 0 ]
    then
        action "adduser olduser-$user" /bin/true
        echo -e "user:\tolduser-$user pass:$password" >> /tmp/user.txt
    else
        action "adduser olduser-$user" /bin/false
        echo -e "user:\tolduser-$user pass:\t$password" >> /tmp/fail_user.txt
    fi

done

有bug,用户已存在会报too many arguments

C语言型for循环结构语法

for((exp1;exp2;exp3))     `# 注意没有in`
do
    指令...
done

范例:1+2+...+100

[root@nfs scripts]# cat sum.sh
#!/bin/bash
sum=0
for((i=1;i<=100;i++))
do
((sum+=i))
done
echo $sum

判断同一网段的主机是否在线

https://blog.51cto.com/zengxin/1690079

#!/bin/bash
for ((I=1;I<=5;I++));do   # 注意没有in
    ping -c 2 -w 2 192.168.3.$I &>/dev/null
    if [ $? -eq 0 ];then
       echo -e "\033[32;40m192.168.3.$Iis up.\033[0m"
    else
       echo -e "\033[31;40m192.168.3.$Iis down.\033[0m"
    fi
done

配置别名IP

#!/bin/bash
inter=1

case $1 in
up)
    for ((i=200;i<=215;i++))
    do
        [ $i == 210 ] && continue
        ifconfig ens33:$inter 10.0.0.$i netmask 255.255.255.0 $1
        ((inter++))
    done
;;

down)
    for ((i=200;i<=215;i++))
    do
        [ $i == 210 ] && continue
        ifconfig ens33:$inter 10.0.0.$i netmask 255.255.255.0 $1
        ((inter++))
    done
;;

*)
    echo "usage: $0 up/down"
;;
esac

exit $?

break continue exit return

break continue exit return一般用于循环结构中控制循环(for,while,if)的走向。

  • break n n表示跳出循环的层数,如果省略n表示跳出整个循环。

  • continue n
    n表示退到第n层继续循环,如果省略n表示跳过本次循环,忽略n次循环的剩余代码,进入循环的下一次循环。

  • exit n
    退出当前shell程序,n为返回值。n也可以省略,再下一个Shell中使用\$?接收这个n的值。

  • return n 用于在函数里,作为函数的返回值,用于判断函数执行是否正确执行

测试脚本

[root@nfs ~]# cat test.sh
#!/bin/bash
for((i=0;i<=5;i++))
do
  if [ $i -eq 3 ]; then
#     continue
#     break
      exit
  fi
  echo $i
done
echo "ok"

shell函数

Shell函数说明

函数也是具有和别名类似的功能

简单地说,函数的作用就是把程序里多次调用相同的代码部分定义成一份,然后为这一份代码起个名字,其它所有的重复调用这部分代码就都只调用这个名字就可以了。当需要修改这部分重复代码时,只需要改变函数体内的一份代码即可实现所有调用修改。

使用函数的优势:

1、把相同的程序段定义成函数,可以减少整个程序的代码量。

2、增加程序的可读、易读性。

3、可以实现程序功能模块化,不同的程序使用函数模块化。

强调:对于Shell来说,linux系统的2000个命令都可以说是Shell的函数。

shell函数语法

语法格式:

  • 简单语法格式:

函数名() {

指令…

return n

}

  • 规范语法格式

function 函数名() {

指令…

return n

}

shell函数的执行

调用函数:

  • 直接执行函数名即可

注意:a.执行函数时,函数后的小括号不要带了。b.函数定义及函数体必须在要执行的函数名的前面定义

  • 带参数的函数执行方法

函数名 参数1参数2

函数后接的参数说明

  • shell的位置参数(\$1、\$2、...\$n、\$#、\$*、\$?以及\$\@)都可以是函数的参数

  • 此时父脚本的参数临时地被函数参数所掩盖或隐藏。

  • \$0比较特殊,它仍然是父脚本的名称。

  • 当函数完成时,原来的命令行脚本的参数即恢复。

  • 在Shell函数里面,return命令功能与Shell里的exit类似,作用是跳出函数

  • 在Shell函数体里使用exit会退出整个Shell脚本,而不是Shell函数

  • return语句会返回一个退出值(返回值)给调用函数的程序。

可以单独写个函数文件,在脚本中调用

[root@centos7 ~]# vim /etc/init.d/my_functions 

#!/bin/bash
func1 () {
    echo "i am jen"
}

func2 () {
    echo "i am xi"
}

#func1
#func2

#######

[root@centos7 scripts]# vim diaoyong_func.sh

[ -f /etc/init.d/my_functions ] && . /etc/init.d/my_functions
func1
func2

示例

函数传参转成脚本命令行传参

#!/bin/bash
#function Check_Url () {
#    curl -I -s $1 | head -1 && return 0 || return 1
#}

#url=$1

function Check_Url () {
    curl -I -s $1 | head -1 && return 0 || return 1
}

Check_Url $1

判断并显示颜色

new_chars() {

RED_COLOR='\E[1;31m'
GREEN_COLOR='\E[1;32m'
YELLOW_COLOR='\E[1;33m'
BLUE_COLOR='\E[1;34m'
RES='\E[0m'

if [ $# -ne 2 ];then
    echo "usage: $0 xxx color"
    exit 1
fi

case $2 in
blue|BLUE)
    echo -e "${BLUE_COLOR}$1${RES}"
    ;;

red|RED)
    echo -e "${RED_COLOR}$1${RES}"
    ;;
*)
    echo  "please input corret format"
    exit 100
    ;;
esac

}

new_chars bl blue # 函数传参
new_chars $1 $2 # 脚本传参

nginx控制脚本

#!/bin/bash
. /etc/init.d/functions

#case $1 in
#start)
#    /usr/local/nginx/sbin/nginx
#    [ $? -eq 0 ] && action "nginx is stated" /bin/true  || \
#    action "nginx start" /bin/false 
#    ;;
#stop)
#    /usr/local/nginx/sbin/nginx -s stop # quit 优雅停止(处理完请求再停止),stop立即停止
#    action "nginx is stopped" /bin/false
#    ;;
#
#status)
#    [ `ss -tnlup|awk -F"[ :]+" '{print $6}'|grep 80|wc -l` -eq 1 ] && echo "nginx is running..." || action "nginx not running" /bin/false
#    ;;
#*)
#    echo "usage: $0 status|start|stop"
#    ;;
#esac
##########

start() {
/usr/local/nginx/sbin/nginx
[ $? -eq 0 ] && action "nginx is started" /bin/true  || \
action "nginx start fail" /bin/false
}

stop() {
/usr/local/nginx/sbin/nginx -s stop # quit 优雅停止(处理完请求再停止),stop立即停止
action "nginx is stopped" /bin/false
}

status() {
[ `ss -tnlup|awk -F"[ :]+" '{print $6}'|grep 80|wc -l` -eq 1 ] && echo "nginx is running..." || action "nginx not running" /bin/false
}

restart() {
/usr/local/nginx/sbin/nginx -s reload # 也可以先quit再start
}

case $1 in
start)
    start
#    exit $?
    ;;
stop)
    stop
    ;;
status)
    status
    ;;
restart)
    restart
    ;;
*)
    usage
    echo "usage: $0 status|start|stop|restart"
    ;;
esac

网上抄的nginx控制脚本:

#!/bin/bash
# Description: Only support RedHat system
. /etc/init.d/functions
WORD_DIR=/data/project/nginx1.10
DAEMON=$WORD_DIR/sbin/nginx
CONF=$WORD_DIR/conf/nginx.conf
NAME=nginx
PID=$(awk -F'[; ]+' '/^[^#]/{if($0~/pid;/)print $2}' $CONF)
if [ -z "$PID" ]; then
PID=$WORD_DIR/logs/nginx.pid
else
PID=$WORD_DIR/$PID
fi
stop() {
$DAEMON -s stop
sleep 1
[ ! -f $PID ] && action "* Stopping $NAME" /bin/true || action "* Stopping
$NAME" /bin/false
}
start() {
$DAEMON
sleep 1
[ -f $PID ] && action "* Starting $NAME" /bin/true || action "* Starting $NAME"
/bin/false
}
reload() {
$DAEMON -s reload
}
test_config() {
$DAEMON -t
}
case "$1" in
start)
if [ ! -f $PID ]; then
start
else
echo "$NAME is running..."
exit 0
fi
;;
stop)
if [ -f $PID ]; then
stop
else
echo "$NAME not running!"
exit 0
fi
;;
restart)
if [ ! -f $PID ]; then
echo "$NAME not running!"
start
else
stop
start
fi
;;
reload)
reload
;;
testconfig)
test_config
;;
status)
[ -f $PID ] && echo "$NAME is running..." || echo "$NAME not running!"
;;
*)
echo "Usage: $0 {start|stop|restart|reload|testconfig|status}"
exit 3
;;
esac

健康网站状态并邮件报警

#!/bin/bash
. /etc/init.d/functions
RETVAL=0
SCRIPTS_PATH="/tmp/test"
MAIL_GROUP="shengwei.tang@joy4you.com"
LOG_FILE=/tmp/test/web.log
# web detection function
function GetUrlStatus(){
        FAILCOUNT=0
        for i in `seq 3`;do
                wget -T 2 --tries=1 --spider http://${1} >/dev/null 2>&1
                [ $? -ne 0 ] && let FAILCOUNT+=1;
        done
        if [ $FAILCOUNT -gt 1 ];then
                RETVAL=1
                NOWTIME=$(date "+%Y-%m-%d %H:%M:%S")
                echo "http://${1} service is error,${NOWTIME}" > $LOG_FILE
                for MAIL_USER in $MAIL_GROUP;do
                        /usr/local/bin/sendEmail -f shengwei.tang@joy4you.com -t 1011464647@qq.com -s smtp.exmail.qq.com -u "${MAIL_USER}"  -xu shengwei.tang@joy4you.com -xp 123456 -m $(cat ${LOG_FILE}) >& /dev/null
                done
        else
                RETVAL=0
        fi
        return $RETVAL
}

# function end

[ ! -d "$SCRIPTS_PATH" ] && mkdir $SCRIPTS_PATH
[ ! -f "$SCRIPTS_PATH/domain.list" ] &&{
cat > $SCRIPTS_PATH/domain.list <<EOF
www.baidu.com
EOF
}

# service check
for URL in `cat $SCRIPTS_PATH/domain.list`;do
        echo -n "checking $URL"
        GetUrlStatus $URL && action " successful" /bin/true || action  "failure" /bin/false
done

# 此脚本可用于服务重启后的快速检查,可和启动脚本放一起

Linux简单优化脚本

01.安装系统时精简安装包(最小化安装)。
02.配置国内高速yum源。
03.禁用开机不需要启动的服务。
04.优化系统内核参数/etc/sysctl.conf。
05.增加系统文件描述符、堆栈等配置。
06.禁止root远程登录,修改SSH端口为特殊端口,禁止DNS,空密码。
07.有外网IP的机器要开启配置防火墙,仅对外开启需要提供服务的端口,配置或关闭SELINUX。
08.清除无用的默认系统帐户或组(非必须)(添加运维成员的用户)。
09.锁定敏感文件,如/etc/passwd(非必须)。
10.配置服务器和互联网时间同步。|
11.配置sudo对普通用户权限精细控制。
12.把以上11点写成一键优化脚本。
https://www.cnblogs.com/hackerer/p/10131698.html

#!/bin/bash
##############################################################################
# File Name    :    Linux system config
# description   :   This script is used to set linux system
# Author         :   simon
# Mail             :   24731701@qq.com
##############################################################################
. /etc/init.d/functions
IP=`/sbin/ifconfig|awk -F '[ :]+' 'NR==2{print $4}'`

# Defined result function

function Msg(){
        if [ $? -eq 0 ];then
             action "$1" /bin/true
        else
             action "$1" /bin/false
        fi

}

# Defined Close selinux Functions
function selinux(){
        [ if "/etc/selinux/config"  ] && {
            sed -i 's#SELINUX=enforcing#SELINUX=disabled#g' /etc/selinux/config
              setenforce 0
              Msg "Close selinux"           
        }
}

# Defined add Ordinary users Functions

function AddUser(){
        id simon &>/dev/null
        if [ $? -ne 0 ];then
        useradd simon &>/dev/null
        echo "123456"|passwd --stdin simon &>/dev/null &&\
       sed -ir '98a simon    ALL=(ALL)    NOPASSWD:ALL' /etc/sudoers &&\
        visudo -c &>/dev/null
        Msg "AddUser simon"
        else
             echo "simon user is exist."
        fi
}

# Defined Hide the system version number Functions

function HideVersion(){
        [ -f "/etc/issue" ] && >/etc/issue
        [ -f "/etc/issue.net"] && > /etc/issue.net
        Msg "Hide sys info."
}

# Defined sshd config Functions

function sshd(){
    sshd_file=/etc/ssh/sshd_config
    if [ `grep "52113" $sshd_file|wc -l` -eq 0 ];then
    sed -ir "13 iPort 52113\nPermitRootLogin no\nPermitEmptyPasswords no\nUseDNS no\nGSSAPIAuthentication no" $sshd_file
    sed -i 's@#ListenAddress 0.0.0.0@ListenAddress '${IP}':52113@g' $sshd_file
    /etc/init.d/sshd restart > /dev/null 2>&1
    Msg "sshd config"
    fi
}

# Defined OPEN FILES Functions
function openfiles(){
        if [ `grep "nofile 65535" /etc/security/limits.conf|wc -l` -eq 0 ];then
             echo '*  -  nofile  65535' >> /etc/security/limits.conf
             ulimit -SHn 65535
             Msg "open files"
        fi
}

function hosts(){
        if [ ! -f /server/scripts/hosts ];then
           echo "/server/scripts/hosts is not exist,please solve this question"
            sleep 300
            exit 1

        fi
        /bin/cp /server/scripts/hosts  /etc/hosts
}

# Defined System Startup Services Functions

function boot(){
        export LANG=en
        for simon in `chkconfig --list|grep "3:on"|awk '{print $1}'|egrep -v "crond|network|rsyslog|sshd|sysstat"`
            do
               chkconfig $simon off
          done
          Msg "BOOT config"
}

# Deined Time Synchronization Functions
function Time(){
        grep "time.nist.gov" /var/spool/cron/root > /dev/null 2>&1
        if [ $? -ne 0 ];then
        echo "#time sync by simon at $(date +%F)" >>/var/spool/cron/root
        echo "*/5 * * * * /usr/sbin/ntpdate time.nist.gov &>/dev/null" >>/var/spool/cron/root
        fi
        Msg "Time Synchronization"

}
# Defined Kernel parameters Functions
function Kernel(){
    /bin/cp /etc/sysctl.conf  /etc/sysctl.conf.$RANDOM
    /bin/cp /server/scripts/sysctl.conf /etc/
    Msg "kernel"

}

function iptables(){
    /etc/init.d/iptables stop
    /etc/init.d/iptables stop
    Msg "iptables"

}

function hostname(){
    ip=`/sbin/ifconfig eth1|awk -F "[: ]+" 'NR==2 {print $4}'`
    name=`grep -w "$ip" /etc/hosts |awk '{print $2}'`
    sed -i 's/HOSTNAME=*/HOSTNAME='"$name"'/g' /etc/sysconfig/network
    /bin/hostname  $name
    Msg "hostname"

}

# Defined main Functions
function main(){
        AddUser
        HideVersion
        sshd
        openfiles
        hosts
        boot
        Time
        Kernel
        iptables
        hostname
}

main

shell数组

shell数组介绍

简单的说,数组就是相同数据类型的元索按一定顺序排列的集合。

数组就是把有限个类型相同的变量用一个名字命名,然后用编号区分他们的变量的集合。这个名字成为数组名,编号成为数组下标。组成数组的各个变量成为数组的分量,

数组定义与增删改查

  • 数组定义
array=(valuel value2 value3)  #一对括号表示是数组,数组元素用“空格”符号分割开
例如:[root@nfs ~]# array=(1 2 3 4 5)
  • 获取数组的长度
格式:${#数组名[@或*]}    #例如:
[root@nfs ~]# echo ${#array[@]}
5
  • 打印数组元素
echo ${数组名[下标]}                 #注意:下标从0开始,下标为@或*,打印所有元素
[root@nfs ~]# echo ${array[0]} 
1
[root@nfs ~]# echo ${array[4]}
5
[root@nfs ~]# echo ${array[@]}
1 2 3 4 5
  • 数组赋值
方法1
[root@nfs ~]# array[5]=6     
[root@nfs ~]# echo ${array[5]} 
6

方法2:array=([1]=one[2]=two[3]=three)   <=key-value 键值对
[root@oldboy server]#array=([1]=one [2]=two [3]=three)
[root@oldboy server]#echo ${#array[*]}
3
[root@oldboy server]#echo ${array[*]}
one two threel

方法3:array[0]=a array[1]=b array[2]=c
[root@oldboy server]#array[0]=a
[root@oldboy server]#array[1]=b
[root@oldboy server]#array[2]=c
[root @oldboy server]# array[3]=d
[root@oldboy server]# echo ${array[0]}

方法4:declare-a array

方法5:array=($(ls))
  • 数组删除
[root@nfs ~]# unset array[3]     #删除单个数组元素
[root@nfs ~]# echo ${array[@]}
1 2 3 5 6
[root@nfs ~]# unset array        #删除整个数组
  • 数组删除
[root@nfs ~]# unset array[3]     #删除单个数组元素
[root@nfs ~]# echo ${array[@]}
1 2 3 5 6
[root@nfs ~]# unset array        #删除整个数组

[root@oldboy ~]#arrayl=(one two three four five)
[root@oldboy ~]#echo ${array1[@]}
one two three four five
[root@oldboy]#echo ${arrayl[@]#o}〈==左边开始最短的匹配。
ne two three four five
[root@oldboy ~]#echo ${arrayl[@]#fo]<==左边开始最短的匹配笔
one two three ur five
[root@oldboy ~]#echo ${array1[@]%t*e}
one two four fivel
[root@oldboy ~]#echo ${array1[@]%%t*e}
one two four five
  • 数组内容的截取和替换
[root@nfs ~]# array=(1 2 3 4 5)
[root@nfs ~]# echo ${array[@]:2:4}         #截取
3 4 5
[root@nfs ~]# echo ${array[@]/3/4}         #替换
1 2 4 4 5
替换后赋值给新数组
[root@centos7 scripts]# echo ${array[@]}
1 2 3

[root@centos7 scripts]# array1=${array[*]/3/5}
[root@centos7 scripts]# echo ${array1[@]}
1 2 5

范例

for循环打印数组元素

array=(
oldboy
zhangyue
zhangyang
)

for ((i=0; i<${#array[*]}; i++))
do
    echo "this is num $i,then content is ${array[$i]}"
done
echo '------------------'
echo "array len: ${#array[*]}"
[root@nfs data]# cat /root/array.sh
#!bin/bash
array=($(ls))
for ((i=0;i<${#array[*]};i++))
do 
   echo ${array[i]}
done
echo ======================
for i in ${array[*]}
do
   echo $i
done

系统命令结果作为数组元素

array=(`ls`)
for ((i=0; i<${#array[*]}; i++))
do
    echo "${array[$i]}"
done

利用数组检查url状态

#!/bin/sh
. /etc/init.d/functions

#check url add
url_list=(
https://mysqldb.org
http://www.baidu.com
http://www.qq.com
http://192.168.146.128
)

function wait()
{
echo -n '3秒后,执行该操作';
for ((i=0; i<3; i++))
do
echo -n ".";sleep 1
done
echo
}

function check_url(){
set -x
wait
set +x
echo 'check url...'
for ((i=0; i<${#url_list[*]}; i++))
do
#HTTP/1.1 200 OK
    judge=($(curl -I -s --connect-timeout 5 ${url_list[$i]}|head -1|tr "\r" "\n"))
    if [[ "${judge[1]}" == '200' && "${judge[2]}"=='OK' ]]
    then
    action "${url_list[$i]}" /bin/true
    else
    action "${url_list[$i]}" /bin/false
    fi
done
}
check_url

shell脚本监控MySQL主从同步

https://www.cnblogs.com/sunziying/p/6398009.html
第四部分12集

expect免交互ssh登录

https://blog.csdn.net/zhuying_linux/article/details/6902135

#!/usr/bin/expect
### 注意一定不能bash执行脚本!!!
spawn ssh -p 22 root@0cm0.com hostname
expect "password"
send "521Linux\n"
interact

shell编程之信号控制及跳板机的实现

https://blog.csdn.net/qq_42303254/article/details/86084142

Linux信号处理及跳板机

场景1:公司新招聘了一个配置管理员,他的工作是负责将公司开发人员写的新代码依次分发到办公室测试环境、roC测试环境和正式线上环境。因此公司需要开发一个程序,当配置管理员登录服务器,只能进入分发的管理界面,无法进行其他操作或直接进入bash界面。

场景2:公司有大量服务器,我们不能让每个人用root用户登录服务器,这样很危险。但我们又不能在每一台服务器为所有人创建登录账号,这样管理起来会很繁琐。于是就有一种叫做跳板机或堡垒机的解决方案。

我们可以用Shell脚本实现上面的功能,但通常的Shell脚本有一个不是bug的bug。平时我们执行脚本时发现什么问题时,就会用Ctrl+C强制终止脚本。但是在上面的类似场景中我们可不希望自己的shell脚本在运行时被用户使用Ctrl+c之类进入到bash界面,做我们不希望做的事情。这里就引出了我们接下来要学习的信号处理

1, 查看系统信号

使用kill -l和trap -l都可以列出llnux系统的信号名称。

man kill
       -l     Print a list of signal names.  These are found in /usr/include/linux/signal.h
help trap
       -l        print a list of signal names and their corresponding numbers
[root@mysql ~]# kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
......          #总共64个信号

2, linux信号注释说明

1) SIGHUP 本信号在用户终端连接(正常或非正常)结束时发出,
通常是在终端的控制进程结束时, 通知同一session内的各个作业,
这时它们与控制终端不再关联。

2) SIGINT 程序终止(interrupt)信号,
在用户键入INTR字符(通常是Ctrl-C)时发出,用于通知前台进程组终止进程。

3) SIGQUIT 和SIGINT类似, 但由QUIT字符(通常是Ctrl-/)来控制.
进程在因收到SIGQUIT退出时会产生core文件, 在这个意义上类似于一个程序错误信号。

4)SIGILL 执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行数据段.
堆栈溢出时也有可能产生这个信号。

5) SIGTRAP 由断点指令或其它trap指令产生. 由debugger使用。

6) SIGABRT 调用abort函数生成的信号。

7) SIGBUS 非法地址, 包括内存地址对齐(alignment)出错。比如访问一个四个字长的整数,
但其地址不是4的倍数。它与SIGSEGV的区别在于后者是由于对合法存储地址的非法访问触发的(如访问不属于自己存储空间或只读存储空间)。

8) SIGFPE 在发生致命的算术运算错误时发出. 不仅包括浮点运算错误,
还包括溢出及除数为0等其它所有的算术的错误。

9) SIGKILL 用来立即结束程序的运行.
本信号不能被阻塞、处理和忽略。如果管理员发现某个进程终止不了,可尝试发送这个信号。

10) SIGUSR1 留给用户使用

11) SIGSEGV 试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据.

12) SIGUSR2 留给用户使用

13) SIGPIPE
管道破裂。这个信号通常在进程间通信产生,比如采用FIFO(管道)通信的两个进程,读管道没打开或者意外终止就往管道写,写进程会收到SIGPIPE信号。此外用Socket通信的两个进程,写进程在写Socket的时候,读进程已经终止。

14) SIGALRM 时钟定时信号, 计算的是实际的时间或时钟时间. alarm函数使用该信号.

15) SIGTERM 程序结束(terminate)信号,
与SIGKILL不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出,shell命令kill缺省产生这个信号。如果进程终止不了,我们才会尝试SIGKILL。

17) SIGCHLD 子进程结束时,
父进程会收到这个信号。如果父进程没有处理这个信号,也没有等待(wait)子进程,子进程虽然终止,但是还会在内核进程表中占有表项,这时的子进程称为僵尸进程。这种情
况我们应该避免(父进程或者忽略SIGCHILD信号,或者捕捉它,或者wait它派生的子进程,或者父进程先终止,这时子进程的终止自动由init进程
来接管)。

18) SIGCONT 让一个停止(stopped)的进程继续执行. 本信号不能被阻塞.
可以用一个handler来让程序在由stopped状态变为继续执行时完成特定的工作. 例如,
重新显示提示符

19) SIGSTOP 停止(stopped)进程的执行.
注意它和terminate以及interrupt的区别:该进程还未结束, 只是暂停执行.
本信号不能被阻塞, 处理或忽略.

20) SIGTSTP** 停止进程的运行, 但该信号可以被处理和忽略.
用户键入SUSP字符时(通常是Ctrl-Z)发出这个信号

21) SIGTTIN 当后台作业要从用户终端读数据时, 该作业中的所有进程会收到SIGTTIN信号.
缺省时这些进程会停止执行.

22) SIGTTOU 类似于SIGTTIN, 但在写终端(或修改终端模式)时收到.

23) SIGURG 有”紧急”数据或out-of-band数据到达socket时产生.

24) SIGXCPU 超过CPU时间资源限制. 这个限制可以由getrlimit/setrlimit来读取/改变。

25) SIGXFSZ 当进程企图扩大文件以至于超过文件大小资源限制。

26) SIGVTALRM虚拟时钟信号. 类似于SIGALRM, 但是计算的是该进程占用的CPU时间.

27) SIGPROF 类似于SIGALRM/SIGVTALRM, 但包括该进程用的CPU时间以及系统调用的时间.

28) SIGWINCH 窗口大小改变时发出.

29) SIGIO 文件描述符准备就绪, 可以开始进行输入/输出操作.

30) SIGPWR Power failure

常用信号

在使用信号名时需要省略SIG前缀,通常我们需要忽略的信号有HUP、INT、QUIT、TSTP、TERM,也就是信号l,2,3,15,20。

信号 说明
HUP(1) 挂起,通常因终端掉线或用户退出而引发
INT(2) 中断,通常因按下Ctrl+C组合键而引发
QUIT(3) 退出,通常因按下Ctrl+/组合键而引发
ABRT(6) 中止,通常因某些严重的执行错误而引发
ALRM(14) 报警,通常用来处理超时
TERM(15) 终止,通常在系统关机时发送
TSTP(20) 停止进程的运行,但该信号可以被处理和忽略.用户键入SUSP字符时(通常是Ctrl+z)发出这个信号

trap命令

trap命令是内置命令,需要使用help
trap查看帮助。trap命令用于在接收到信号后将要采取的行动。trap命令的一种常见用途是在脚本程序被中断时完成清理工作。

语法:

  • trap -l 把所有信号打印出来

  • trap -p把当前的trap设置打印出来

  • trap "" singals ===>为空表示这个信号失效

  • trap "commands" singals ===>
    收到signals指定的信号时,信号功能复位同时执行commands命令。

  • trap singals ===>没有命令部分,信号复原

用stty -a可以列出中断信号与键盘的对应。

范例1:

[root@mysql ~]# trap "" 2     #此时Ctrl+c失效
[root@mysql ~]# trap -p       #查看   
trap -- '' SIGINT
[root@mysql ~]# trap 2        #此时Ctrl+c生效
[root@mysql ~]# ^C
[root@mysql ~]# trap "echo -n 'you can not use ctrl+c'" 2  #收到2信号时执行echo
[root@mysql ~]# ^Cyou can not use ctrl+c          #测试

范例2:同时处理多个信号

[root@mysql ~]# trap "" 1 2 3 15 20
[root@mysql ~]# trap -p
trap -- '' SIGHUP
trap -- '' SIGINT
trap -- '' SIGQUIT
trap -- '' SIGTERM
#TSTP(20)命令没有显示出来,但在脚本中可以实现
[root@mysql ~]# cat trap.sh 
trap "" 1 2 3 15 20
trap -p
[root@mysql ~]# sh trap.sh 
trap -- '' TSTP
[root@mysql ~]# trap 1 2 3 15 20
[root@mysql ~]# sh trap.sh 
trap -- '' HUP
trap -- '' INT
trap -- '' QUIT
trap -- '' TERM
trap -- '' TSTP
[root@mysql ~]# trap 1 2 3 15 20
[root@mysql ~]# sh trap.sh 
trap -- '' HUP
trap -- '' INT
trap -- '' QUIT
trap -- '' TERM
trap -- '' TSTP
#可以批量使用信号名来忽略信号,但信号名不可以批量取消
[root@mysql ~]# trap 1 2 3 15 20
[root@mysql ~]# trap -p
[root@mysql ~]# trap "" HUP INT QUIT TERM TSTP
[root@mysql ~]# trap -p
trap -- '' SIGHUP
trap -- '' SIGINT
trap -- '' SIGQUIT
trap -- '' SIGTERM
[root@mysql ~]# trap HUP INT QUIT TERM TSTP
[root@mysql ~]# trap -p
trap -- '' SIGHUP
trap -- 'HUP' SIGINT
trap -- 'HUP' SIGQUIT
trap -- 'HUP' SIGTERM
[root@mysql ~]# trap ":" HUP INT QUIT TERM TSTP
[root@mysql ~]# ^C
# :也是一个命令,执行后$?为0

Linux信号的生产应用案例

请记住,脚本程序通常是以从上到下的顺序解释执行的,所以必须在你想保护的那部分代码以前指定trap命令。

  • 触发信号后清理文件
[root@mysql ~]# cat trap_rm_file.sh 
#!/bin/bash
trap "find /tmp -type f -name "website_*"|xargs rm -rf && exit" INT
while :
do
  touch /tmp/website_$(date +%F-%T)
  sleep 1
done
[root@mysql ~]# sh trap_rm_file.sh

另起一个窗口查看文件,发现会生成好多文件,回到脚本执行窗口按Ctrl+c结束,再查看文件发现已经删除

Shell跳板机(触发信号后屏蔽信号)

l)首先做好sshkey验证或使用expect脚本。

2)实现传统的远程连接菜单选择脚本

3)利用linux信号防止用户在跳板机上操作。

4)用户登录后即调用脚本。

知识点1:trap信号

知识点2:ssh key免密钥登录(参考以前的文档)

知识点3: /etc/profile.d/ ==>加载系统登陆程序的一个目录

放在/etc/profile.d/目录下的文件即使没有执行权限也可以开机自动执行

# 脚本程序
[root@backup scripts]# cat tiaobanji.sh 
#!/bin/bash

USER=liwen
function trapper(){
   trap "" HUP INT QUIT TERM TSTP
}

function menu() {
cat <<EOF
#################HOST LIST#################
        1)192.168.80.101
        2)192.168.80.102
        3)192.168.80.103
        4)192.168.80.104
        5)192.168.80.105
        6)192.168.80.106
        7)192.168.80.107
###########################################
EOF
}

function select_host() {
   case "$1" in
        1)
           ssh -p52113 $USER@192.168.80.101
        ;;
        2)
           ssh -p52113 $USER@192.168.80.102
        ;;
        3)
           ssh -p52113 $USER@192.168.80.103
        ;;
        4)
           ssh -p52113 $USER@192.168.80.104
        ;;
        5)
           ssh -p52113 $USER@192.168.80.105
        ;;
        6)
           ssh -p52113 $USER@192.168.80.106
        ;;
        7|*)
           ssh -p52113 $USER@192.168.80.107
   esac
}

function main() {
trapper
while true
do
   clear
   menu
   read -p "please select host:" num
  select_host $num
done  
}

main

# 开机自启动

[root@backup ~]# cd /etc/profile.d/
[root@backup ~]# cd /etc/profile.d/
[root@backup profile.d]# vim tiaoban.sh
[ $UID -ne 0 ] && ./server/scripts/tiaobanji.sh
#root用户UID为0,当root用户登录时不执行此脚本,其他用户都执行

跳板机安全应用

1, 首先跳板机禁止外网IP登录,只能内网IP登录。

ListenAddress 192.168.80.107

2, 其他服务器有外网IP的也别忘了禁止外网IP登录,只能内网IP登录。同时禁止root登录,等做完ssh
key认证,连密玛登录也禁了,只能通过证书登录,而且只有跳板机有其他服务器的密钥。

PasswordAuthentication no

3, 先远程拨号登录VPN,然后登录跳板机,然后再从跳板机登录其他服务器。

Shell脚本的调试

https://blog.csdn.net/weixin_42167759/article/details/80700719

脚本开发规范

https://www.cnblogs.com/surpassme/p/10041382.html

  • 重视书写习惯,开发规范和开发制度,尽量减少脚本调试的难度和次数,

  • 提升开发效率。

  • shell基本语法要熟练,才能利用好脚本调试。

  • 写脚本思路要清晰

思考开发的框架,尽量模块化开发(第一关,第二关,第三关),复杂的脚本分段实现。思考实现框架:编程模块化

  1. shell脚本调试技巧

  2. 使用dos2unix命令处理来自windows下开发的脚本

  3. 使用echo加exit命令断点调试

  4. 使用bash命令参数调试

    • -n不会执行该脚本,仅查询脚本语法是否有问题,并给出错误提示。

    • -v在执行脚本时,先将脚本的内容输出到屏幕上然后执行脚本,如果有错误,也会给出错误提示

    • -x将执行的脚本内容及输出显示到屏幕上,这个是对调试很有用的参数。**

  5. 使用set命令调试部分脚本内容

    • set -n读命令但并不执行。

    • set -v显示读取的所有行。

    • set -x显示所有命令及其参数

在脚本中加set -x开启调试,set +x关闭调试,也可以在命令行中使用,缩小调试的作用域

set -x
{
....
    }
set +x

shell脚本范例

svn数据多进程备份

#!/bin/bash                                                                                                                                                              
work_dir="/home/scripts"
put_to_oss="${work_dir}/put_svndata.py"
process_num=6
svn_dir='/home/svndata/svn'
backup_pdir='/home/backup/svndata'
date_now=`date +%Y%m%d`
backup_dir="$backup_pdir/$date_now"
log="/home/backup/log/svn_backup.log"

cd $work_dir
echo "${date_now}=====backup start=====" &>>$log

[ -e ./fd1 ] || mkfifo ./fd1    # 创建有名管道
exec 3<> ./fd1                  #创建文件描述符,以可读(<)可写(>)的方式关联管道文件,这时候文件描述符3就有了有名管道文件的所有特性
rm -rf ./fd1                    #关联后的文件描述符拥有管道文件的所有特性,所以这时候管道文件可以删除,我们留下文件描述符来用就可以了

for i in `seq 1 ${process_num}`;               
do
   echo >&3                                   #创建令牌
done

mkdir $backup_dir &>>$log

for i in $svn_dir/*;do
    basedir=`basename $i`
    if [ ! "$basedir" == 'conf' ];then
        read -u3                          #read 命令每次读取一行,也就是拿到一个令牌
        {
          mkdir $backup_dir/$basedir &>>$log
          /usr/bin/svnadmin hotcopy $i $backup_dir/$basedir &>>$log
          echo >&3                        #执行完一条命令会将令牌放回管道
        }&
    fi
done 
/bin/cp -rf $svn_dir/conf $backup_dir &>>$log
wait                                    #保证前面的命令执行完后再执行后面的
exec 3<&-                               #关闭文件描述符的读
exec 3>&-                               #关闭文件描述符的写
source ./python_env/bin/activate &>>$log
cd $backup_pdir
/usr/bin/python $put_to_oss $date_now &>>$log
#delete_dir=`date +%Y%m%d --date="-2 days"`
#rm -fr $delete_dir &>>$log
for dir in `ls`;do
  if [ $dir != $date_now ];then
     rm -rf $dir &>>$log
  fi
done
echo "${date_now}=====backup stop=====" &>>$log

shell获取命令行参数选项(创建脚本)

#!/bin/bash                                                                                                                                                              
work_dir="/home/scripts"
put_to_oss="${work_dir}/put_svndata.py"
process_num=6
svn_dir='/home/svndata/svn'
backup_pdir='/home/backup/svndata'
date_now=`date +%Y%m%d`
backup_dir="$backup_pdir/$date_now"
log="/home/backup/log/svn_backup.log"

cd $work_dir
echo "${date_now}=====backup start=====" &>>$log

[ -e ./fd1 ] || mkfifo ./fd1    # 创建有名管道
exec 3<> ./fd1                  #创建文件描述符,以可读(<)可写(>)的方式关联管道文件,这时候文件描述符3就有了有名管道文件的所有特性
rm -rf ./fd1                    #关联后的文件描述符拥有管道文件的所有特性,所以这时候管道文件可以删除,我们留下文件描述符来用就可以了

for i in `seq 1 ${process_num}`;               
do
   echo >&3                                   #创建令牌
done

mkdir $backup_dir &>>$log

for i in $svn_dir/*;do
    basedir=`basename $i`
    if [ ! "$basedir" == 'conf' ];then
        read -u3                          #read 命令每次读取一行,也就是拿到一个令牌
        {
          mkdir $backup_dir/$basedir &>>$log
          /usr/bin/svnadmin hotcopy $i $backup_dir/$basedir &>>$log
          echo >&3                        #执行完一条命令会将令牌放回管道
        }&
    fi
done 
/bin/cp -rf $svn_dir/conf $backup_dir &>>$log
wait                                    #保证前面的命令执行完后再执行后面的
exec 3<&-                               #关闭文件描述符的读
exec 3>&-                               #关闭文件描述符的写
source ./python_env/bin/activate &>>$log
cd $backup_pdir
/usr/bin/python $put_to_oss $date_now &>>$log
#delete_dir=`date +%Y%m%d --date="-2 days"`
#rm -fr $delete_dir &>>$log
for dir in `ls`;do
  if [ $dir != $date_now ];then
     rm -rf $dir &>>$log
  fi
done
echo "${date_now}=====backup stop=====" &>>$log
服务器技术交流群请加微信 YJZyjz