shell脚本编程

默认情况下,echo在每次调用后会添加一个换行符
双引号"" 双引号允许shell解释字符串中出现的特殊字符
单引号'' 单引号不会对字符串中出现的特殊字符做任何解释
echo "hello world! \$PATH"
echo 'hello world! $PATH'
如果需要打印像$这样的特殊字符,那就不要将其放入双引号中,而是使用单引号,或是在特殊字符之前加上一个反斜线\,用来转义
1.在echo中转义换行符
默认情况下,echo会在输出文本的尾部追加一个换行符,可以使用-n选项来禁止。
echo使用转义序列时,需要使用echo -e "包含转义序列的字符串"这种形式
echo -e "1\t2\t3"
2.打印彩色输出
脚本可以使用转义序列在终端中生成彩色文本,文本颜色是由对应的色彩码来描述的。其中包括:
重置=0,黑色=30,红色=31,绿色=32,黄色=33,蓝色=34,洋红=35,青色=36,白色=37
echo -e "\e[1;31m This is red text \e[0m"
其中\e[1;31m就是一个转义字符串,可以将颜色设置为红色,\e[0m将颜色重新置回。只需要将31替换成想要的色彩码就行了
对于彩色背景,经常使用的颜色码是:重置=0,黑色=40,红色=41,绿色=42,黄色=43,蓝色=44,洋红=45,青色=46,白色=47
echo -e "\e[1;42m Green Background \e[0m"

假设有一个叫做gedit的应用程序正在运行,我们可以使用pgrep来获得gedit的进程ID
可以使用等号操作符为变量赋值 varName=value,varName是变量名,value是赋给变量的值。如果value不包含任何空白字符(例如空格),那么就不需要将其放入引号,否则必须使用单引号或双引号。
注意:var = value不同于var=value。把var=value写成var = value是一个常见的错误。两边没有空格的等号是赋值操作符,加上空格的等号表示的是等量关系测试。
fruit=apple
count=5
echo "we have $count ${fruit}(s)"
因为shell使用空白字符来分隔单词,所以我们需要加上一对花括号来告诉shell这里的变量名是fruit

获得字符串的长度:length=${#var} length就是字符串所包含的字符数

修改bash的提示字符串

  当我们打开终端或者运行shell时,会看到类似于user@hostname:/home/$的提示字符串。不同的GNU/Linux发布版中的提示字符串及颜色各不相同。我们可以利用PS1环境变量来定义主提示字符串。默认的提示字符串是在文件~/.bashrc中的某一行定义的,我们可以修改PS1的值来修改提示字符串

shell脚本编程基础

shell编程:过程式、解释执行

编程语言的基本结构:

  各种系统命令的组合

  数据存储:变量、数组

  表达式:a+b

  语句:if

shell脚本:

  包含一些命令或声明,并符合一定格式的文本文件

格式要求:首行shebang机制

  #!/bin/bash

  #!/usr/bin/python

  #!/usr/bin/perl

shell脚本的用途有:

  自动化常用命令

  执行系统管理和故障排除

  创建简单的应用程序

  处理文本文件

创建shell脚本

第一步:使用文本编辑器来创建文件

  第一行必须包括shell声明序列:#!

  #!/bin/bash

  添加注释

  注释以#开头

第二步:运行脚本

  给与执行权限,在命令行上指定脚本的绝对或相对路径

  直接运行解释器,将脚本作为解释器程序的参数运行

脚本调试

  1.检测脚本中的语法错误

  bash -n /path/to/script

  2.调试执行

  bash -x /path/to/script

 


 

变量:命令的内存空间

  数据存储方式:  

  字符

  数值:整形,浮点型

变量命名法则:

  1.不能使用程序中的保留字,例如if,for

  2.只能使用数字、字母及下划线,且不能以数字开头

  3.见名知义

  4.统一命名规则:驼峰命名法(大小写)

变量类型

  作用

  1:数据存储格式

  2:参与的运算

  3:表示的数据范围

  类型:字符、数值:整型、浮点型

bash中变量的种类

根据变量的生效范围等标准划分为下面变量类型:

  局部变量:生效范围为当前shell进程;对当前shell之外的其它shell进程,包括当前shell的子shell进程均无效(echo $变量名)

  环境(全局变量):生效范围为当前shell进程及其子进程(export|declare -x|env 特性:只能向下传递)

  本地变量:

  位置变量:

  特殊变量:$$(表示当前进程的进程编号  pstree -p查看进程树),$?

局部变量

变量赋值:name='value'

可以使用引用value:

  (1)可以是直接字串,name="root"

  (2)变量引用:name="$USER"

  (3)命令引用:name='COMMAND' name=$(COMMAND)

变量引用:$(name)  $name

  "":弱引用,其中的变量引用会被替换为变量值

  '':强引用,其中的变量引用不会被替换为变量值,而保持原字符串

删除变量:unset

 环境变量

变量声明、赋值:

  export name=VALUE

  declare -x name=VALUE

变量引用:$name

显示所有环境变量

  env

  printenv

  export

  declare -x

删除变量

  unset name

 bash内建的环境变量

  PATH

  SHELL

  USER

  UID

  HOME

  PWD

  LANG

  MAIL

  HOSTNAME

  HISTSIZE

只读和位置变量

 

  只读变量:只能声明,但不能修改和删除

    声明只读变量:

      readonly name

      declare -r name

    查看只读变量:

      readonly -p

    (umask 666;touch /data/f1):小括号用于一次性任务

    x=1;echo "pid=$$";(echo "subpid=$$";echo "subx=$x";x=2;echo "subx2=$x";);echo x=$x

  位置变量:在脚本中调用通过命令行传递给脚本的参数

    $1,$2,...:对应第1,第2等参数,shift[n]换位置

    $0:命令本身

    $*:传递给脚本的所有参数,全部参数合为一个字符串

    $@:传递给脚本的所有参数,每个参数为独立字符串

    $#:传递给脚本的参数的个数

      $@ $*只有在被双引号包起来的时候才会有差异

    set --清空所有位置变量

    #!/bin/bash

    scp $1 192.168.189.135:/root/bin

  shift[n]的用法

  #!/bin/bash

  echo "1st arg is $1"
  echo "2st arg is $2"
  echo "3st arg is $3"
  shift 2
  echo "1st arg is $1"
  echo "2st arg is $2"
  echo "3st arg is $3"

退出状态

  进程使用退出状态来报告成功或失败

  0代表成功,1~255表示失败

  $? 变量保存最近的命令退出状态

退出状态码

  bash自定义退出状态码

  exit [n]:自定义退出状态码

  注意:脚本中一旦遇到exit命令,脚本会立即终止;终止退出状态取决于exit命令后面的数字

  注意:如果未给脚本指定退出状态码,整个脚本的退出状态取决于脚本中执行的最后一条命令的状态码

 基础逻辑运算

算数运算

  bash中的算数运算:

    +,-,*,/,%取模(取余数)

  实现算数运算:

    1)let var=算数表达式

    2)var=$[算数表达式]

    3)var=$((算数表达式))

    bash有内建的随机数生成器:$RANDOM(0-32767)

      echo $[$RANDOM%50]:0-49之间的随机数

逻辑判断

    & 并且 and

      0&0=0

      0&1=0

      1&0=0

      1&1=1

    | 或者 or

      0|0=0

      0|1=1

      1|0=1

         1|1=1

 

    && 短路与     cmd1 && cmd2  如果cmd1为假,则cmd2不需要执行;如果cmd1为真,则cmd2要执行

              cmd1 && cmd2 || cmd3  cmd1为真,执行cmd2;cmd1为假,执行cmd3

 

    ||短路或      cmd1||cmd2  如果cmd1为真,则cmd2不需要执行;如果cmd1为假,则cmd2要执行

              cmd1 || ( cmd2 ;cmd3 )  如果cmd1为true,则执行cmd2和cmd3

              cmd1 || { cmd2 ; cmd3 }  exit时退出

    XOR 异或

      0^0=0

      0^1=1

      1^0=1

      1^1=0

      两个数互换:let a=a^b;let b=a^b;let a=a^b

     非:!

      !1=0

      !0=1

条件测试

  判断某需求是否满足,需要由测试机制来实现

    专用的测试表达式需要由测试命令辅助完成测试过程

  评估布尔声明,以便用在条件性执行中

    若真,则返回0

    若假,则返回1

  测试命令:

    test EXPRESSION  help test

    [ EXPRESSION ]

    [[ EXPRESSION ]]

    []和[[]]的区别:

      是否用正则表达式,用正则表达式时,用[[]]

    注意:EXPRESSION前后必须有空白字符

  ()、(())、[]、[[]]、{}的区别:

    []和test命令一样,用变量时建议加双引号引起来

      -a FILE:True if file exists

      -d FILE:True if file is a directory

      -f FILE:True if file exists and is a regular file

      -z STRING:True if string is empty

      -n STRING/STRING:True if string is not empty

    ():用于多个命令组

      false || ( echo cmd1; echo cmd2)

    {} flase || { echo cmd1 ; exit ;}  会退出当前shell

     false || ( echo cmd1 ; exit )  不会退出当前shell,相当于开了一个子shell

  常用逻辑运算符:

    -eq:等于,用于比较数字,即equal

    -ne:不等于,用于比较数字,not equal

    -lt:小于,letter

    -gt:大于,greater

    -le:小于等于

    -ge:大于等于

    EXPR1 -a EXPR2 True if both expr1 and expr2 are true

      [ -r /etc/issue -a -w /etc/passwd ] && echo "read and write"

    EXPR1 -o EXPR2 True if either expr1 or expr2 is true

      [ -r /etc/issue -o -w /etc/passwd ] && echo "read or write"

  bash的字符串测试

    =  是否等于

    >  ascii码是否大于assci码

    <  是否小于

    != 是否不等于

    =~ 左侧字符串是否能被右侧的PATTERN所匹配

      注意:此表达式一般用于[[]]中;扩展的正则表达式

    -z "STRING"  字符串是否为空,空为真,不空为假

    -n "STRING"  字符串是否不空,不空为真,空为假

    注意:用于字符串比较时的用到的操作数都应该使用引号

  使用read命令来接受输入

    -p:指定要显示的提示

    -s:静默输入,一般用于密码

    -n N:指定输入的字符长度N,到达长度N自动退出

    -d '字符':输入结束符时退出

    -t N:timeout为N秒,到了N秒时自动退出

    read从标准输入中读取值,给每个单词分配一个变量,所有剩余单词都被分配给最后一个变量

    read -p "input your name:" name

  判断yes no

    ans="yes";[[ $ans =~ ^([Yy]|[Yy][Ee][Ss])$ ]] && echo true

    ans="Yes";[[ $ans =~ ^([Yy]([Ee][Ss])?)$ ]] && echo true

    ans="No";[[ $ans =~ ^([Nn][Oo]?)$ ]] && echo true

 防止扩展

  反斜线(\)会使随后的字符按原意解释

    echo your cost:\$5.00

  加引号来防止扩展

    单引号(')防止所有扩展

    双引号(")也防止所有扩展,但是下列情况除外

    $(美元符号)——变量扩展

    `(反引号)——命令扩展

    \(反斜线)——禁止单个字符扩展

    !(叹号)——历史命令扩展

bash的配置文件

  按生效范围划分,存在两类

    全局配置:

      /etc/profile

      /etc/profile.d/*.sh

      /etc/bashrc

    个人(用户)配置:

      ~/.bash_profile

      ~/.bashrc

shell登录的两种方式

  交互式登录:

    1)直接通过终端输入账号密码登录

    2)使用"su - username"切换用户

    执行顺序:/etc/profile-->/etc/profile.d/*.sh-->~/.bash_profile-->~/.bashrc-->/etc/bashrc

  非交互式登录:

    1:su username

    2)图形界面下打开的终端

    执行顺序:~/.bashrc-->/etc/bashrc-->/etc/profile.d/*.sh

.和source与bash的区别

  用bash执行脚本的时候相当于在当前shell的子进程中运行的,不会对当前的环境造成影响

  用source执行脚本的时候相当于在当前shell中运行,会对当前的环境造成影响

profile类和bashrc类

  按功能划分,存在两类:

    profile类和bashrc类

      profile类:为交互式登录的shell提供配置

      全局:/etc/profile,/etc/profile.d/*.sh

      个人:~/.bash_profile

      功用:

        1)用于定义环境变量

        2)运行命令或脚本

      bashrc类:为非交互式和交互式登录的shell提供配置

      全局:/etc/bashrc

      个人:~/.bashrc

      功用:

        1)定义命令别名和函数

        2)定义本地变量 

编辑配置文件生效

  修改profile和bashrc文件后需生效

    两种方法:

      1:重新启动shell进程

      2:.或source

bash退出任务

  保存在~/.bash_logout文件中(用户)

  在退出登录shell时运行

 


 

shell编程进阶

条件选择if语句

  选择执行:

  注意:if语句可嵌套

  单分支

    if 判断条件;then

      条件为真的分支代码

    fi

  双分支

    if 判断条件;then

      条件为真的分支代码

    else

      条件为假的分支代码

    fi

  多分支

    if 判断条件1;then

      条件为真的分支代码

    elif 判断条件2;then

      条件为真的分支代码

    elif 判断条件3;then

      条件为真的分支代码

    else

      以上条件都为假的分支代码

    fi

 条件判断:case语句

case支持glob风格的通配符:

  *:任意长度任意字符

  ?:任意单个字符

  []:执行范围内的任意单个字符

  a|b:a或b

case 变量引用 in

PART1)

  分支1

  ;;

PART2)

  分支2

  ;;

...

*)

  默认分支

  ;;(最后这个可以省略)

esac

 

循环

  循环执行

    将某代码段重复运行多次

    重复运行多少次

      循环次数事先已知

      循环次数事先未知

    有进入条件和退出条件

  for,while,until

for循环(循环次数已知)

  for 变量名 in 列表;do

    循环体

  done

  执行机制:

    依次将列表中的元素赋值给"变量名";每次赋值后即执行一次循环体;直到列表中的元素耗尽,循环结束

列表生成方式:  

  1直接给出列表,用空格隔开

  2整数列表:

    a){start...end}

    b)$(seq[start[step]]end)

  3返回列表的命令

    $(COMMAND)   或反向单引号     ``

  4使用glob,如:*.sh

  5变量引用

    $@(单个变量),$*(整体的)

{1..100..3}  以3为步进,取1到100

seq 1 3 100 以3为步进,取1到100

 {1..100} 1到100

while循环(循环次数未知)

  while CONDITION;do

    循环体

  done

CONDITION:循环控制条件;进入循环之前,先做一次判断;每一次循环之后会再次做判断;条件为"true",则执行下一次循环;直到条件测试状态为"false"终止循环

因此:CONDITION一般应该有循环控制变量;而此变量的值会在循环体不断的被修正

进入条件:CONDITION为true

退出条件:CONDITION为false

循环控制语句continue

用于循环体中

continue[N]:提前结束第N层的本轮循环,而直接进入下一轮判断;最内层为第1层

 #!/bin/bash
for i in {1..3};do
        for j in {1..10};do
                if [ $j -eq 5 ];then
                        continue 1
                fi
                echo "j=$j"
        done
        echo
done

#!/bin/bash
for i in {1..3};do
        for j in {1..10};do
                if [ $j -eq 5 ];then
                        continue 2
                fi
                echo "j=$j"
        done
        echo
done

 

 循环控制语句break

用于循环体中

break[N]:提前结束第N层循环,最内层为第一层

#!/bin/bash
for i in {1..3};do
        for j in {1..10};do
                if [ $j -eq 5 ];then
                        break 1
                fi
                echo "j=$j"
        done
        echo
done

#!/bin/bash
for i in {1..3};do
        for j in {1..10};do
                if [ $j -eq 5 ];then
                        break 2
                fi
                echo "j=$j"
        done
        echo
done

循环控制shift命令

shift [n]

用于将参量列表list左移指定次数,缺省为左移一次

参量列表list一旦被移动,最左端那个参数就从列表中删除。while循环遍历位置参量列表时,常用到shift

 #!/bin/bash
while [ $# -gt 0 ];do
        echo "$*"
        shift
done

创建无限循环

while true;do

  循环体

done

until false;do

  循环体

done

while的特殊用法(遍历文件的每一行)

  while read line;do

    循环体

  done < /PATH/FROM/SOMFILE

依次读取/PATH/FROM/SOMEFILE文件中的每一行,且将值赋给变量line

 对文件逐行操作

#!/bin/bash
while read line;do
        uid=`echo $line|cut -d: -f3`
        user=`echo $line|cut -d: -f1`
        if [ $[uid%2] -eq 0 ];then
                echo "$uid:$user"
        fi
done </etc/passwd

对命令结果逐行操作

#!/bin/bash
df|while read disk;do
        if echo $disk|grep "^/dev/sd" >/dev/null;then
                used=`echo $disk | sed -r 's/.* ([[:digit:]]+)%.*/\1/'`
                device=`echo $disk|cut -d" " -f1`
                [ $used -ge 10 ] && echo "$device will be full,used $used%"
        fi
done

特殊用法

help for

select循环与菜单

select variable in list

  do

    循环体命令

  done

select循环主要用于创建菜单,按数字顺序排列的菜单项将显示在标准错误上,并显示PS3提示符,等待用户输入

用户输入菜单列表中的某个数字,执行相应的命令

用户输入被保存在内置变量REPLY中

#!/bin/bash
PS3="Please choose the menu(1-3):"
select menu in baoyu yanwo renshen;do
        case $menu in
        baoyu)
                echo "$REPLY:$menu price is 1000"
                break
                ;;
        yanwo)
                echo "$REPLY:$menu price is 2000"
                break
                ;;
        renshen)
                echo "$REPLY:$menu price is 3000"
                break
                ;;
        *)
                echo "the menu is empty"
        esac
done

信号捕捉trap

trap'触发指令'信号

  自定义进程收到系统发出的指定信号后,将执行触发指令,而不会执行原操作

trap''信号

  忽略信号的操作

trap'_'信号

  恢复原信号的操作

trap -p

  列出自定义信号操作

 

#!/bin/bash
trap 'echo press ctrl+c' 2
trap -p
for i in {1..10};do
        echo $i
        sleep 0.5
done
trap '' 2
trap -p
for i in {10..20};do
        echo $i
        sleep 0.5
done
trap '-' 2
trap -p
for i in {20..30};do
        echo $i
        sleep 0.5
done

 函数

函数function是由若干条shell命令组成的语句块,实现代码重用和模块化编程

它与shell程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运行,而是shell程序的一部分

函数和shell程序比较相似,区别在于:

  shell程序在子shell中运行

  而shell函数在当前shell中运行,因此在当前shell中,函数可以对shell中变量进行修改

定义函数

函数由两部分组成:函数名和函数体

help function

语法一:

  f_name () {

  ...函数体...  

  }

语法二:

  function f_name {

  ...函数体...

  }

语法三:

  function f_name () {

  ...函数体...

  }

函数的定义和使用(函数的优先级比别名和命令高)

  可在交互式环境下定义函数

    declare -f 查看当前shell的所有函数

    unset func 删除函数

    如果函数中有局部变量,如果其名称同本地变量,在函数中定义本地变量的方法

      local NAME=VALUE

    local定义的变量只在函数中有效

    declare -i在函数中相当于local

  可将函数放在脚本文件中作为它的一部分

  可放在只包含函数的单独文件中

调用:函数只有被调用才会执行

  调用:给定函数名

  函数名出现的地方,会被自动替换为函数代码

函数的生命周期:被调用时创建,返回时终止

 函数有两种返回值:

函数的执行结果返回值:

  1.使用echo等命令进行输出

  2.函数体中调用命令的输出结果

函数的退出状态码:

  1.默认取决于函数中执行的最后一条命令的退出状态码

  2.自定义退出状态码,其格式为:

    return 从函数中返回,用最后状态命令决定返回值

    return 0 无错误返回

    return 1-255 有错误返回

使用函数文件

/etc/init.d/functions 定义着许多函数

可以将经常使用的函数存入函数文件,然后将函数文件载入shell

source/.   /PATH/functions

文件名可以任意选取,但最好与相关任务有某种联系

一旦函数文件载入shell,就可以在命令行或脚本中调用函数。可以使用set/declare -f命令查看所有函数

若要改动函数,首先用unset命令从shell中删除函数。改动完毕后,再重新载入此文件

删除shell函数

现在对函数做一些改动后,需要先删除函数,使其对shell不可用,使用unset命令完成删除函数

命令格式为:unset function_name

全局函数(环境函数)——使子进程也可以使用

声明:export -f function_name

查看:export -f或declare -xf

函数参数

函数可以接受参数:

传递参数给函数:调用函数时,在函数名后面以空白分隔给定参数列表即可;func1 arg1 arg2...

在函数体当中,可食用$1,$2,...调用这些参数;还可以使用$@,$*,$#等特殊变量

数组

变量:存储单个元素的内存空间

数组:存储多个元素的连续的内存空间,相当于多个变量的集合

数组名和索引

  索引:编号从0开始,属于数值索引

  注意:索引可支持使用自定义的格式,而不仅是数值格式,即为关联索引,bash4.0之后的版本支持

  bash的数组支持稀疏格式(索引不连续)

声明数组:

  declare -a ARRAY_NAME:普通数组可以不声明

  delcare -A ARRAY_NAME:关联索引的数组即关联数组,使用前要声明

  注意:两者不可互相转换

数组元素的赋值

  1.一次只赋一个元素

  array_name[index]=value

  weekdays[0]="sunday"

  2.一次赋值全部元素

  array_name=("val1""val2""val3"...)

  3.只赋值特定元素

  array_name=([0]="val1" [3]="val2"...)

显示所有数组:declare -a

 

 

 

 

脚本常用命令

echo

date

练习:

1.编写一个脚本,提示用户输入yes或no,并判断用户输入的是yes还是no,或是其他信息

#!/bin/bash
read -p "Please input yes or no:" ANS
if [ -z "$ANS" ];then
        echo "Do not input empty string"
else
        case $ANS in
    [Yy]|[Yy][Ee][Ss])
        echo "your answer is yes"
        ;;
    [Nn]|[Nn][Oo])
        echo "your answer is no"
        ;;
    *)
        echo "your answer is wrong"
    esac
fi

2.编写一个脚本,提示输入正整数的值,计算1+2+...+n的总和

#!/bin/bash
read -p "Please input a number:" num
declare -i sum=0
for i in `seq $num`;do
        let sum+=i
done
echo "The sum is $sum"

3.编写一个脚本,打印九九乘法表

 #!/bin/bash
for i in {1..9};do
        for j in `seq 1 $i`;do
                result=$[$i*$j]
                echo -e "${j}x${i}=$result\t\c"
        done
        echo
done

4.编写一个脚本,求100以内奇数之和

#!/bin/bash
declare -i sum=0
declare -i a
for a in {1..100};do
        if [ $[a%2] -eq 1 ];then
                let sum+=a
        fi
done   
echo "The sum is $sum"

5.编写一个脚本,打印一个矩形

#!/bin/bash
read -p "Please input the high:" high
read -p "Please input the width:" width
for i in `seq 1 $high`;do
        for j in `seq 1 $width`;do
                color=$[RANDOM%7+31]
                echo -e "\033[1;${color}m*\033[0m\c"
        done
        echo
done

6.编写一个脚本,打印出国际象棋盘

#!/bin/bash
for i in {1..8};do
        for j in {1..4};do
                if [ $[i%2] -eq 0 ];then
                        echo -e "\033[1;43m  \033[0m\033[1;41m  \033[0m\c"
                else
                        echo -e "\033[1;41m  \033[0m\033[1;43m  \033[0m\c"
                fi
        done
        echo
done

7.监控httpd服务是否启动,如果暂停服务后立即重启

#!/bin/bash
while true;do
        if killall -0 httpd  &>/dev/null;then
                :
        else
                systemctl restart httpd &>/dev/null
                echo at `date "+%F +%T"` restart httpd >> /data/httpd.log
        fi
        sleep 10
done

8.编写一个脚本,打印等腰三角形

9.编写一个脚本,随机生成10以内的数字,实现猜字游戏,提示比较大或者比较小,相等则退出

#!/bin/bash
rand=$[RANDOM%11]
while read -p "Please input a number:" num;do
        [[ $num =~ ^[[:digit:]]+$ ]] || { echo "Please input a 0-10 digit!";continue; }
        if [ $num -gt $rand ];then
                echo more
        elif [ $num -lt $rand ];then
                echo litter
        else
                break
        fi
done
echo "You are right"

10.

posted @ 2020-03-30 15:00  明a  阅读(292)  评论(0编辑  收藏  举报