zoukankan      html  css  js  c++  java
  • 快速掌握Shell编程

    (一)Shell编程概述

    1.1 shell简述

      Shell编程和JavaScript非常相似,Shell和JavaScript都是弱类型语言,同时也都是解释型语言。解释型语言需要解释器,JavaScript的解释器是浏览器,Shell脚本的解释器时bash,是一个shell、一个命令行用户接口。

    1.2 bash简述

      bash在执行或者解释脚本的时候,此bash非彼bash。用户登录进来的时候就用一个bash。通过敲一个命令来解释脚本的时候,是在当前bash中打开一个新的bash,这两个bash是父子关系。其实在空行敲回车也是从当前bash打开新的bash。

      在命令行中输入$ps -aux |grep bash可以看见当前只有一个bash。这个bash就是系统和用户进行交互的一个用户接口。

      在命令行中输入命令bash回车。这就是打开了一个新的bash。再次输入ps -aux |grep bash可以看见当前有两个bash。因为敲一个命令就是一个程序的入口,这个命令就被启动,这个程序就是bash。这两个bash就是父子关系。这种有层次结构的bash可以使用exit退出。

    (二)变量

    2.1 变量的类型  

      shell脚本的变量实际上也是bash的变量,共有四种类型:环境变量、本地变量(或称局部变量,但略有差别)、位置变量、特殊变量。环境变量,作用在当前bash和所有子bash,与本地变量的区别在于作用域不同;本地变量(局部变量,当前代码段),作用在当前bash,所有子bash都不能用;位置变量和特殊变量,是bash内置的用来保存某些特殊数据的变量,也不存在作用域的问题(也叫系统变量)。

    2.2 环境变量

      环境变量,$export 变量名=值,其作用域为当前的shell和其子shell。

      注意:脚本在执行时都会启动一个子shell进程,命令行中启动的脚本会继承当前shell环境变量;系统自动启动脚本(非命令行启动),则需要自我定义环境变量。

    2.3 本地变量(局部变量)

      本地变量是只属于某一个bash的变量。例如,$var_name=值,其作用域是整个bash进程。

      局部变量类似,例如$local var_name =值,其作用域为当前代码段。

    2.4 位置变量

      位置变量是指用于脚本执行的参数,$1表示第一个参数,以此类推$1,$2…。

    2.5 特殊变量

      特殊变量是每一个bash进程中特有的一些变量,无需声明。总共就几个,$?和$#j较为常用。 

    2.5.1 $?

      $? 表示上一个命令的执行状态返回值。事实上,任何一个程序执行的结果只有两类,即程序有两类返回值:

      第一类是命令的执行结果。例如输入$ls显示的目录下的所有目录和文件就是执行结果,再如输入$id -u root看见的0就是执行结果。

      第二类是命令的执行状态。任何命令的执行状态都被$?这个变量存储起来。$?: 0表示正确,1-255表示错误。例如使用$echo “$?”查看上一个命令的执行状态,打印上一个命令的执行状态0表示正确,非0表示错误。注意不能查看上上个命令的执行状态,因为被覆盖了。

    2.5.2 $#

      $# 表示传递到脚本的参数个数。

    2.5.3 $*

      $* 表示传递到脚本的参数,与位置变量不同,此选项参数可超过9个。

    2.5.4 $$

      $$ 表示脚本运行时当前进程的ID号,常用作临时变量的后缀。 

    2.5.5 $!

      $! 表示后台运行的(&)最后一个进程的ID号 。

    2.5.6 $@

      $@ 与$#相同,使用时加引号,并在引号中返回参数个数。

    2.5.7 $-

      $- 表示上一个命令的最后一个参数 。

    2.6  变量的声明、撤销、查看与引用

    2.6.1 声明变量

      环境变量的声明必须加export;本地变量和局部变量的声明不需写export,直接写变量名后面接值即可。

    2.6.2 撤销变量

      使用unset 变量名,撤销变量。

    2.6.3 查看变量

      查看shell中变量:set

      查看shell中的环境变量:printenv或env

    2.6.4 引用变量

    a) 引用变量

      引用变量格式为:${变量名},一般可以省略{}。只有特殊情况下不能省略,具体效果见下图:

    b) 单引号:强引用

      单引号:强引用,不作变量替换,引用字符串常量(单引号的内容都是字符串)。 

    c) 双引号:弱引用

      双引号:弱引用,做变量替换。

    d) 反引号:命令替换

      反引号:``命令替换。当字符串表示一个命令,需要执行时,则要用反引号。 

    (三)输出重定向

    3.1 命令执行结果保存在一个文件中

    3.1.1 >覆盖重定向

      $ls >/path/file。不会重定向错误结果

    3.1.2 >> 追加重定向

      $cat file1 file2 >> file3 //将file1和file2的文件内容追加重定向到file3后面。

    3.1.3 2> 错误覆盖重定向

      程序执行出错的结果放到文件中。

    3.1.4 2>>错误追加重定

      程序执行出错的结果放到文件中。

    3.1.5 &> 全部覆盖重定向

      无论命令执行对错,都会覆盖重定向到文件。

    3.1.6 &>> 全部追加重定向

      无论命令执行对错,都会追加重定向到文件。 

    3.2 命令执行结果直接丢弃

      /dev/null文件,dev是设备,null是一个设备文件,称之为数据黑洞,所有数据放到这里都无法恢复。

      $ls >> /dev/null

    (四) 脚本

      通过组织命令及变量来完成具有某种业务逻辑的功能称之为脚本。

    4.1 简单脚本案例

    4.1.1 案例一(添加用户)

    a) 业务描述

      添加6个用户,每个用户的密码同用户名,不显示添加密码的信息,并给显示添加用户成功信息。

    b) 编写脚本

      首先,创建脚本。

    mkdir /opt/shell
    cd /opt/shell
    vim test1.sh 

      其次,编写脚本主体。

    #!/bin/bash#脚本的第一行一定是这个脚本的声明,这里声明的是脚本的解释器。
    # 用户可以有参数传过来,也可以直接定义一个变量。
    U=’user1’ #声明变量,本地变量的声明直接变量名=值,无需export。这里选择单引号,因为user1是个字
    符串,只需强引就行,双引号也行。
    useradd $U #引用变量
    #设置密码和用户名相同,但是设置完之后不显示passwd的执行结果。但是这个passwd会在控制台出现信息
    ,所以需要使用管道传入数据,并且重定向。
    echo "$U" | passwd --stdin $U &>/dev/null #前一个命令的输出传给后一个命令的输入。
    echo "success." #打印成功信息。 

      以上是添加1个给定名称的用户,可以稍作修改,变为参数传入的方式动态添加用户:  

    cp test1.sh test2.sh
    vim test2.sh
    #!/bin/bash
    #
    useradd $1 #这里是使用传递参数的方式,1代表1个参数,实际上$1是特殊变量$*的特例
    echo "$1" |  passwd --stdin  $1 &>/dev/null
    echo "Add user $1 success."

    c) 执行脚本

      这个两个脚本当前没有执行权限。有两种方法使该脚本有执行权限:一是添加执行权限;二是,使用命令sh /path/脚本名(sh是一个执行脚本的命令)。

      脚本1的执行:

      $sh test1.sh

      脚本2的执行:

      $sh test2.sh username

      查看是否添加成功:

      $cat /etc/passwd

    d) 查看脚本执行程度

      $bash -x /opt/shell/test2.sh

    4.1.2 案例二(删除用户) 

    a) 业务描述

      写一个脚本,完成以下任务:第一,使用一个变量保存一个用户名;第二,删除此变量中的用户,且一并删除其家目录;第三,显示“用户删除成功”信息。

    b) 编写脚本

      首先,创建脚本。

    vim /opt/shell/test3.sh 

      其次,编写脚本主体。

    #!/bin/bash
    U=$* #使用一个变量存储用户名
    userdel $* #删除这个用户
    rm -rf /home/$* #删除该用户的家目录
    echo "delete user and $*home success." #打印成功信息

    c) 执行脚本

    sh test3.sh username #注意一定要有参数(要删除的用户),不然会删除整个/home目录
    cat /etc/passwd #查看用户是否删除成功。
    ls /home #查看用户的家目录是否删除成功

    4.2 条件判断

      条件判断是布尔类型,而shell是弱类型的语言,也即没有类型,所以表达式只有真或假。

    4.2.1 条件表达式

      条件表达式有两种形式:一是[ expression ],注意中括号和表达式之间一定要加空格隔开;二是,test expression,test+空格+表达式。

    4.2.2 整数的比较

      =:-eq  等于

      !=:-ne  不等于

      >:-gt  大于

      <:-lt  小于

      >=:-ge  大于或等于

      <=:-le  小于或等于

    4.2.3 逻辑运算 

    a) 命令的逻辑关系

      在linux中,命令执行状态:0为真,其他为假。

    b) 逻辑与或非

      可以使用离散数学的逻辑与或非来进行逻辑判断。

      逻辑与:&&。当第一个条件为假时,第二条件不用再判断,最终结果已经有;当第一个条件为真时,第二条件必须得判断。

      逻辑或:||。

      逻辑非:!。

    c) 案例(命令逻辑关系的条件判断) 

      业务描述:添加用户前先判断是否存在,如果存在就打印该用户已存在,并且退出。

      编写脚本: 

    vim /opt/shell/test4.sh
    #!/bin/bash
    #
    id $1 &>/dev/null && echo "User $1 exist" && exit 3 #使用命令的执行状态作为逻辑判断。id $1 如果用户$1存在,则为0(真),需要继续判断后面的,所以执行echo,并且退出脚本,不再
    创建该用户。退出,3是非0,当前脚本执行不成功。
    #上一行可以使用逻辑或的代替,见下两行
    #!id $1 &>/dev/null || echo "User $1 exist" #如果用户存在id $1则为0,前面加了!就是非0,就为假。前面为假,对于逻辑或来说需要继续判断后面的,所以执行echo 打印用户存在。
    #id $1 &>/dev/null && exit 3 #用户存在就退出 useradd $
    1 #添加用户 id $1 &>/dev/null && echo "$1" | passwd --stdin $1 &>/dev/null #如果用户创建成功了>,就执行后面的echo passwd添加密码。 echo "Add user $1 success." 

      执行脚本:

    sh test4.sh user1

    4.2.4 if条件判断

    a) 语法结构

      if条件表达式的语法结构为:

    if [ 条件 ]; then
        语句
    elif [ 条件 ]; then
        语句
    else
        语句
    fi

    b) if的逻辑与或

      -a :并且

      -o :或者

      例如:

    if [ $# -gt 1 -a $# -lt 3 -o $# -eq 2 ] ; then

      如果,上一个命令传入的参数个数大于1,并且小于3,或者等于2。&&是离散数学中的逻辑与,-a是一个逻辑运算符,并且的意思,不同于&&。

    c) 案例(if语句)

      业务描述:给定一个用户,如果他的UID为0则显示为管理员,否则显示其他普通用户。

      编写脚本:

    vim /opt/shell/test5.sh 
    #!/bin/bash
    USER_ID=`id -u $1`&> /dev/null || exit 3 #注意这里需要用反引号,这里也不能将变量名取为UID,会和环境变量冲突,这样声明的变量是只读的。
    if [ $USER_ID -eq 0 ] ; then
      echo "admin."
    else
      echo "other."
    fi 

      执行脚本:

    sh test5.sh root

    4.3 算术运算符

    4.3.1 算术运算符的类型

    a) let 算术运算表达式

    let C=$A + $B

    b) $[算术表达式]

    C = $[$A+$B] 

    c) $((算术表达式))

    C=$(($A+$B))

    d) expr 算术表达式

      注意:表达式中各操作数及运算符之间要有空格。而且要使用命令引用。

    C=`expr $A + $B`

    4.3.2 案例(算术运算符的使用)

    a) 业务描述

      给定一个用户,获取其密码警告期限,然后判断用户密码使用期限是否已经小于警告期限,如果小于,则是显示“WARN”,否则显示密码还有多少天到期。

      密码警告期限:距离警告的天数;密码使用期限:密码有效期减去使用的时间。

    b) 编写脚本

      首先,需要知道:

      $date +%s 可以获得今天的秒数

      $cat /etc/shadow 可以获得密码使用时间。

    cat /etc/shadow | grep root

      观察上图,这一行数据是以冒号隔开的。其中的17021是指,创建密码的那天距离1970年1月1日的天数;0表示密码有效期最短0天;99999表示密码最多99999天有效;7表示有效期还剩7天时警告。

      然后,编辑脚本如下: 

    vim /opt/shell/test6.sh
    #!/bin/bash
    #
    UN=$1 #根据传入的数据作为变量UN的值
    C_D=`grep "^$UN" /etc/shadow | awk -F: '{print $3}' ` #grep "^$UN" /etc/shadow是从shadow中找到到以$UN这个用户名开头的,print $3是密码的创建时间,这里使用-F来指定:分隔符。加反引号是一个命令。
    M_D=`grep "^$UN" /etc/shadow | awk -F: '{print $5}' ` #'{print $5}'是找到最长有效期
    W_D=`grep "^$UN" /etc/shadow | awk -F: '{print $6}'` #'{print $6}'是警告时间
    NOW_D=$[`date +%s`/86400] #`date +%s`获得当前系统时间(秒),使用数学运算符[]相当于数>学的括号,除以一天的秒数
    U_D=$[$NOW_D-$C_D] #算出了使用的天数
    L_D=$[$M_D-$U_D] #算出剩余的天数
    if [ $L_D -le $W_D ];then #判断注意空格,剩余时间小于等于警告时间输出warn.
      echo "warn."
    else
      echo "left day is $L_D" #其他情况输出剩余天数
    fi

    c) 执行脚本

    sh test6.sh root

    4.4 文件与字符串测试

    4.4.1 文件与字符串测试语法

    a) 文件测试

      文件测试需要中括号 [ ]

      -e FILE:测试文件是否存在

      -f FILE:测试文件是否为普通文件

      -d FILE:测试文件是否为目录

      -r 当前用户有没有读的权限

      -w 当前用户有没有写的权限

      -x 当前用户有没有执行的权限

    b) 字符串测试

      == 等号两端需要空格

      !=

      -n string : 判断字符串是否为空

      -s string : 判断字符串是否不空

    4.4.2 案例(文件与字符串测试)

    a) 业务描述

      指定一个用户名,判断此用户的用户名和它的基本组,组名是否相同。

    b) 编写脚本 

    vim /opt/shell/test7.sh
    #!/bin/bash
    #
    if !id $1 &>/dev/null ; then
      echo "No such user."
      exit 12
    fi
    if [ $1 == `id -n -g $1` ] ;then #`id -n -g $1`是指输入的用户的组的名称(-g是组id)
      echo "相同"
    else
      echo "不相同"
    fi

    c) 执行脚本

    sh test7.sh root

    4.5 循环语句

    4.5.1 for循环

    a) for循环的语法结构

    for 变量 in 列表; do
      语句
    done

    b) for循环中的列表  

      for循环中的列表尤为重要,列表可以是以空格隔开的一组数,可以理解为数组;也可以动态生成列表。

      静态数组列表

      以下是个静态列表的实例:

    for I in 1 2 3 4 5 ; do 
        语句
    done

      动态生成列表 

      动态生成列表有三种方式,分别为:

      第一,{1..100}。中间两个点,1-10可以成{1..10}

      第二,seq [起始数] [跨度数] 结束数。seq相当于oracle中sequence,递增的序列,[起始数]带有中括号意为可以不给,默认为1;[跨度数]带有中括号也可以不给,默认值为1;结束数必须给,不然会造成死循环。

      第三,ls /PATH/ 文件列表。这个实际上是通过for循环递归目录下的文件。

    c) for循环案例一

      业务描述:

      将那些可以登录的用户查询出来,并且将用户的帐号信息提取出来,后放入/tmp/test.txt文件中,并在行首给定行号。

      编写脚本:

    vim /opt/shell/test8.sh
    #!/bin/bash
    #
    J=1
    count=`wc -l /etc/passwd | cut -d' ' -f1` #wc -l /etc/passwd 可以获得passwd文件的行数;cut -d' ' -f1中-d是指定切割符,-f1是取第一个。
    #count=`wc -l /etc/passwd | awk -F " " '{print $1}'` #也可以使用awk
    for I in `seq $count`;do #这里使用seq生成列表,以1开始,跨度为1,到$count结束。
       u_shell=`head -$I /etc/passwd | tail -1 | cut -d: -f7` #head取前I行,然后管道到tail -1拿到最后一行,实际上就是第I行,然后管道到cut按:切割,取第七个(shell)。
       u_n=`head -$I /etc/passwd | tail -1 | cut -d: -f1` #取用户
       if [ $u_shell == '/bin/bash' ];then #判断字符串是否相等,相等表示可以登录。可以登录>就将/tmp/users.txt
          echo "$J $u_n " >> /tmp/users.txt #输出时加上编号,这里不能使用I,因为I是行号,业务要求是新的序号,需要连续的。
          J=$[$J+1] #相当于J++
       fi
    done

      执行脚本:

    sh test8.sh root

    d) for循环案例二

      业务描述:

      计算100以内所有能被3整除的整数的和。

      编写脚本:

    vim test9.sh
    #!/bin/bash
    #
    sum=0
    for I in {1..100};do
      S=$[$I%3] #对3取模(求余数)
      if [ $S -eq 0 ];then #数字比较只能用-eq,不能使用等号
        sum=$[$sum+$I]
      fi
    done
    echo "$sum"

      执行脚本:

    sh test9.sh

    e) for循环案例三

      业务描述:

      传给脚本一个参数:目录,输出该目录中文件最大的,文件名和文件大小。

      编写脚本:

    vim test10.sh
    #!/bin/bash
    #
    #首先判断参数是否正确
    if [ -f $1 ];then #-f 判断传过来的目录是否是文件
      echo "Arg is error."
      exit 2
    fi
    
    if [ -d $1 ];then #-d 是判断$1是目录
      c=`du -a $1 | sort -nr | wc -l`  #du -a $1是列出$1目录下的所有文件和目录; sort -nr是按数值降序排列; wc -l获取行数。但是这样把目录页选出来了
      for I in `seq $c`;do
        f_size=`du -a $1 | sort -nr | head -$I | tail -1 | awk '{print $1}'` #取到第I行,拿到文件大小
        f_path=`du -a $1 | sort -nr | head -$I | tail -1 | awk '{print $2}'` #取到第I行,拿到文件路径
        if [ -f $f_path ];then #判$f_path是否是文件,是文件就直接输出,并退出循环
           echo -e "the biggest file is $f_path 	 $f_size"
           break
        fi
      done
    fi

      执行脚本:

    sh test10.sh /path/

    4.5.2 while循环

    a) 格式一

    while 条件;do
        语句
        [break]
    done

    b) 格式二(死循环)

    while true
    do
      语句
    done 

    c) 格式三(死循环)

    while :
    do
        语句
    done

    d) 格式四(死循环)

    while [ 1 ]
    do
        语句
    done 

    e) 格式五(死循环)

    while [ 0 ]
    do
        语句
    done

    f) while循环案例 

      业务描述:

      使用echo输出10个随机数。

      编写脚本:

    vim /opt/shell/test11.sh
    #!/bin/bash
    #
    I=1
    while [ $I -le 10 ] ; do
            echo -n "$RANDOM"
            I=$[$I+1]
    done

      执行脚本:

    sh test11.sh
    作者:Huidoo_Yang
    本文版权归作者Huidoo_Yang和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    用 ArcMap 发布 ArcGIS Server FeatureServer Feature Access 服务 PostgreSQL 版本
    ArcMap 发布 ArcGIS Server OGC(WMSServer,MapServer)服务
    ArcScene 创建三维模型数据
    ArcMap 导入自定义样式Symbols
    ArcMap 导入 CGCS2000 线段数据
    ArcMap 导入 CGCS2000 点坐标数据
    ArcGis Server manager 忘记用户名和密码
    The view or its master was not found or no view engine supports the searched locations
    python小记(3)操作文件
    pytest(2) pytest与unittest的区别
  • 原文地址:https://www.cnblogs.com/yangp/p/8511321.html
Copyright © 2011-2022 走看看