shell– 脚本编程

编程基础

脚本基本格式

变量

运算

条件测试

流程控制

函数

数组

高级字符串操作

高级变量

配置用户环境

编程基础

程序:指令+数据

程序编程风格:
    过程式:以指令为中心,数据服务于指令
    对象式:以数据为中心,指令服务于数据
shell程序:提供了编程能力,解释执行

程序的执行方式

计算机:运行二进制指令;

编程语言:

低级:汇编
高级:
编译:高级语言-->编译器-->目标代码

解释:高级语言-->解释器-->机器代码
    shell, perl, python

编程基本概念

编程逻辑处理方式:

    顺序执行
    循环执行
    选择执行
shell编程:过程式、解释执行

编程语言的基本结构:

数据存储:变量、数组
表达式: a + b
语句:if

shell脚本基础

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

格式要求:首行shebang机制
    #!/bin/bash
    #!/usr/bin/python
    #!/usr/bin/perl

shell脚本的用途有:
    自动化常用命令
    执行系统管理和故障排除
    创建简单的应用程序
    处理文本或文件

创建shell脚本

第一步:使用文本编辑器来创建文本文件
    第一行必须包括shell声明序列:#!
    #!/bin/bash
    添加注释
    注释以#开头
第二步:运行脚本
    给予执行权限,在命令行上指定脚本的绝对或相对路径
        如果不输入绝对路径或相对路径shell先找¥PATH,所以找不到就运行不了。
        用户添加$PATH条目,家目录修改.bash_profile文件加入要修改的路径。 
    直接运行解释器,将脚本作为解释器程序的参数运行

系统为每个用户在家目录生成了以个bin的$PATH的环境变量。用户可以自己在自己的家目录建立一个bin的目录,下面放可执行脚本即可在命令提示符下直接运行,不需要路径就能执行。

shell脚本范例

#!/bin/bash
#author: wang
#Version: 1.0
#Description:Thisscriptdisplayssomeinformationaboutyour# environment
echo"Greetings.Thedateandtimeare$(date)" echo"Yourworkingdirectoryis:$(pwd)"

bash的配置文件

两类:
    profile类:为交互式登录的shell进程提供配置
    bashrc类:为非交互式登录的shell进程提供配置

登陆类型

    交互式登陆shell进程:
        直接通过某终端输入账号和密码后登录打开的shell进程
        使用su命令:su - USERNAME ,或使用su -l USERNAME 执行的登录切换
    非交互式登录shell进程
        su USERNAME 执行的登录切换
        图形界面下打开的终端
        运行脚本

profile类

    全局:对所有用户都生效
        /etc/profile
        /etc/proflie.d/*.sh
    用户个人:仅对当前用户有效。
        ~/.bash_profile
    功用:
        1、用于定义环境变量
        2、运行命令或脚本

bashrc类

    全局:
        /etc/bashrc
    用户个人:
        ~/.bashrc
    功用:
        1、定义本地变量
        2、定义命令别名

注意:仅管理员可以修改全局配置文件。 

交互式登录shell进程:
    /etc/profile-->/etc/profile.d/*-->~/.bashrc-->/etc/bashrc
用户配置可以覆盖系统配置。例如$PATH这个变量。

非交互式登录shell进程(脚本运行读取配置文件的顺序)
    ~/.bashrc-->/etc/bashrc-->/etc/profile.d/*

脚本调试

bash -n /path/to/some_script
检测脚本中的语法错误

bash -x /path/to/some_script
调试执行
shell脚本时在当前终端开启子shell后运行。

变量

变量:命名的内存空间

数据存储方式:
    字符:
    数值:整型,浮点型

变量:变量类型
    变量类型决定了:变量的存储格式,数据范围,参与的运算

数值:整型、浮点型 bash不支持浮点型,除非借助工具。

编程程序语言分类

强类型:定义变量时必须指定类型、参与运算必须符合类型要求;调用未声明变量会产生错误
    如java,python

弱类型:无须指定类型,默认均为字符型;参与运算会自动进行隐式类型转换;变量无须事先定义可直接调用
    如:bash 不支持浮点数

变量命名法则:
    1、不能使程序中的保留字:例如if, for;
    2、只能使用数字、字母及下划线,且不能以数字开头
    3、见名知义
    4、统一命名规则:驼峰命名法,每个单词的第一个字母大写 
        大驼峰:每个单词的第一字母大写
        小驼峰:第一单词的第一个字母小写,以后的都大写

bash中变量的种类

根据变量的生效范围等标准:
    本地变量:生效范围为当前shell进程;对当前shell之外的其它shell进程,包括当前shell的子shell进程均无效

    环境变量:生效范围为当前shell进程及其子进程

    局部变量:生效范围为当前shell进程中某代码片断(通常指函数)

    位置变量:$1, $2, ...来表示,用于让脚本在脚本代码中调用通过命令行传递给它的参数

        注:脚本运行是在当前shell的子shell下运行的,如有(command)括号中的再开一个子shell,脚本shell之下开启的子shell,不可以调用的其他脚本的本地变量

    特殊变量:$?, $0, $*, $@, $#

本地变量

变量赋值:name=‘value’,

可以使用引用value:
    (1) 可以是直接字串; name=“root"
            name=var
            name=“xxx xxx  xx”多个字符串中间有空格的需要用引号如果不引用其他变量或引用命令的话单双引号都可以。
    (2) 变量引用:
        $name, ${name} 
                $a
                $b
                $ab
                ${a}b

        name="$USER" 通过引用起它变量的值赋值
    (3) 命令引用:name=`COMMAND`, name=$(COMMAND)

            注:可以多行内容赋值到一个变量。

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

            "弱引用----双引号
            [root@localhost ~]# echo "echo $USER"
            echo root

            ''强引用------单引号
            [root@localhost ~]# echo 'ehco $USER'
            ehco $USER

            ``命令引用----~下的符号
            [root@localhost ~]# echo `echo $USER`
            Root

显示已定义的所有变量:set

删除变量:unset name 脚本运行完毕要释放变量。养成良好习惯。

练习 1、编写脚本/root/bin/systeminfo.sh,显示当前主机系统信息,包括主机名,IPv4地址,操作系统版本,内核版本,CPU型号,内存大小,硬盘大小。

2、编写脚本/root/bin/backup.sh,可实现每日将/etc/目录备份到/root/etcYYYY-mm-dd中

3、编写脚本/root/bin/disk.sh,显示当前硬盘分区中空间利用率最大的值

4、编写脚本/root/bin/links.sh,显示正连接本主机的每个远程主机的IPv4地址和连接数,并按连接数从大到小排序


scp:通过终端登录其他远程终端,复制内容到当前终端。

scp file ipadd:path
把file拷贝到path下

环境变量

环境变量的使用必须先声明
变量声明、赋值:
    export name=VALUE
    declare -x name=VALUE

    declare - name=number 声明变量为数值型
变量引用:$name, ${name}

显示所有环境变量:
    export
    env
    printenv

删除:unset name

bash有许多内建的环境变量:PATH, SHELL, USRE,UID, HISTSIZE, HOME, PWD, OLDPWD, HISTFILE, PS1

    声明环境变量后,shell的子进程及其子进程的子进程都可使用。
    新的终端后就不可以使用。

注
source 脚本 和 . 脚本 执行脚本效果是一样的,都是在当前shell下执行。
当前shell下的用户可调用该脚本的本地变量

通过 bash 脚本和直接执行 ./脚本 效果是一样的,都是在当前shell下开启子shell后运行的。
当前shell不可以调用该脚本定义的本地变量。

只读和位置变量

只读变量:只能声时,但不能修改和删除,不能使用unset撤销。
    readonlyname=VLAUE
    declare -r name=VLAUE

        可以与-x一起使用 declare -rx name=VALUE 只读的环境变量

位置变量:在脚本代码中调用通过命令行传递给脚本的参数
    $1, $2, ...:对应第1、第2等参数,shift [n]换位置
    $0: 命令本身,脚本名称。
    $*: 传递给脚本的所有参数,全部参数合为一个字符串
    $@: 传递给脚本的所有参数,每个参数为独立字符串
    $#: 传递给脚本的参数的个数
        $@ $* 只在被双引号包起来的时候才会有差异

            [root@yangyouwei ~]# ./test1.sh a b
            first a b
            secd 
            all a b
            ========
            first a
            secd b
            all a b

            [root@yangyouwei ~]# cat test1.sh 
            #!/bin/bash
            ./test2.sh "$*"
            echo ========
            ./test2.sh "$@"

            [root@yangyouwei ~]# cat test2.sh 
            #!/bin/bash
            echo first "$1"
            echo secd "$2"
            echo all "$*"

shift

位置参数可以用shift命令左移。比如shift 3表示原来的$4现在变成$1,原来的$5现在变成$2等等,原来的$1、$2、$3丢弃,$0不移动。不带参数的shift命令相当于shift 1。

脚本test1加上参数a和b 调用脚本test2
    test1中
            test2运行两次但是调用的 参数不同
            (虽然某些情况下$*和$@效果是一样的,在这里这两个变量的值作为test2的参数,参与test2的执行。)

示例:判断给出的文件的行数

    linecount="$(wc-l $1| cut -d' ' -f1)"
    echo "$1 has $linecountlines."

declare

-i 声明为整数

-a 声明为数组

-f 声明为函数

-r 声明为只读

算术运算

bash中的算术运算:help let
    +, -, *, /, %取模(取余), **(乘方)

实现算术运算:
    (1) let var=算术表达式
            let var+=5等于let var=var+5
            let var-=5等于let var=var-5
            let var*=5等于let var=var#5
            let var/=5等于let var=var/5
            let var%=5等于let var=var%5

                [root@yangyouwei bin]# var=5
                [root@yangyouwei bin]# let var=var+10
                [root@yangyouwei bin]# echo $var
                15

    (2) var=$[算术表达式]
            括号中的表达式中变量可以加$也可以不加

                [root@yangyouwei bin]# echo $var
                15
                [root@yangyouwei bin]# var=$[var+30]
                [root@yangyouwei bin]# echo $var
                45


    (3) var=$((算术表达式))
            括号中的表达式中变量可以加$也可以不加

                [root@yangyouwei bin]# echo $var
                75
                [root@yangyouwei bin]# var=$((var+30))
                [root@yangyouwei bin]# echo $var
                105

    (4) var=$(expr arg1 arg2 arg3 ...) 命令引用
            expr是命令
            expr 2 + 3  数字与运算符之间必须有空格,作为三个参数。

                [root@yangyouwei bin]# expr 5 + 6
                11

    (5) declare –i var=数值  声明变量是整数

    (6) echo ‘算术表达式’ | bc 管道输出给bc计算

乘法符号有些场景中需要转义,如*
        expr 2 \* 3

$RANDOM随机数

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

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

增强型赋值:let支持 不需要写$符号

        +=, -=, *=, /=, %=

    let varOPERvalue
        例如:let count+=3
            自加3后自赋值
            等于 let count=count+3

自增,自减

    自增,自减:
        相同结果
            let var+=1
            let var++
            let ++var

        相同结果
            let var-=1
            let var--
            let --var


    还有有区别的 赋值和运算 的先后区别

        [root@yangyouwei ~]# i=100
        [root@yangyouwei ~]# let ++i
        [root@yangyouwei ~]# echo $i
        101
        [root@yangyouwei ~]# let i++
        [root@yangyouwei ~]# echo $i
        102
        [root@yangyouwei ~]# let v=i++
        [root@yangyouwei ~]# echo $i  
        103
        [root@yangyouwei ~]# echo $v
        102

练习

练习1:写一个脚本/root/bin/sumid.sh,计算/etc/passwd文件中的第10个用户和第20用户的ID之和

练习2:写一个脚本/root/bin/sumspace.sh,传递两个文件路径作为参数给脚本,计算这两个文件中所有空白行之和

练习3:写一个脚本/root/bin/sumfile.sh,统计/etc, /var, /usr目录中共有多少个一级子目录和文件

逻辑运算

true, false
    1, 0

与:
    1 与1 = 1
    1 与0 = 0
    0 与1 = 0
    0 与0 = 0

或:
    1 或1 = 1
    1 或0 = 1
    0 或1 = 1
    0 或0 = 0

非:!取反
    ! 1 = 0
    ! 0 = 1

短路运算:

    短路与:
        第一个为0,结果必定为0;
        第一个为1,第二个必须要参与运算;

    短路或:
        第一个为1,结果必定为1;
        第一个为0,第二个必须要参与运算;

异或:^
    异或的两个值,相同为假,不同为真

聚集命令

有两种聚集命令的方法:

    •复合式:date; who | wc -l

        命令会一个接一个地运行
        (date; who) | wc -l
            相当于date和who开了一个子shell运行之后传给wc

    •子shell:(date; who | wc -l ) >>/tmp/trace
        所有的输出都被发送给单个STDOUT和STDERR
            相当于标准输出和标准错误输出都是子shell的标准输出

退出状态

进程使用退出状态来报告成功或失败
    •0 代表成功,1-255代表失败
    •$? 变量保存最近的命令退出状态

例如:
    $ping-c1-W1hostdown&>/dev/null
    $echo$?

退出状态码

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

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

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

    如果放在脚本的子shell中,则退出的是子shell脚本不会退出。

条件测试

判断某需求是否满足,需要由测试机制来实现;
    专用的测试表达式需要由测试命令辅助完成测试过程;

评估布尔声明,以便用在条件性执行中
    •若真,则返回0
    •若假,则返回1

测试命令:
    •test EXPRESSION  test是个命令
            后面的都是参数。可用双引号。个参数需要用空格隔开

    •[ EXPRESSION ]
        [ $a = $b ]
        [ $a ] 可以比较变量是否定义过。
            [root@yangyouwei bin]# [ $kf ]
            [root@yangyouwei bin]# echo $?
            1
            [root@yangyouwei bin]# [ $a ]
            [root@yangyouwei bin]# echo $?
            0

    •[[ EXPRESSION ]]
        有时候单括号和双括号还是有差别的。
        双括号比较保险。

注意:中括号内的EXPRESSION前后必须有空白字符
        表达式中两个比较的内容要与中间的比较的符号用空格分开
            test $a = $b
            [ $a = $b ]
    字符串比较可以用两个等号
        [ $a == $b ]

条件性的执行操作符

根据退出状态而定,命令可以有条件地运行
    •&& 代表条件性的AND THEN
    COMMAND1 && COMMAND2
        COMMAND1执行成功则执行COMMAND2

    •|| 代表条件性的OR ELSE
    COMMAND1 || COMMAND2
        COMMAND1执行失败才会执行COMMAND2

!取反

例如:
    $grep-qno_such_user/etc/passwd\
    ||echo'Nosuchuser'
    Nosuchuser
    $ping-c1-W2station1&>/dev/null\
    >&&echo"station1isup"\ 
    >||(echo'station1isunreachable';exit1)
    station1isup

test命令

长格式的例子:
    $test "$A" == "$B" && echo "Stringsareequal"
     注意空格
    $test “$A” -eq “$B”\
    &&echo"Integersareequal"
     注意空格
简写格式的例子:
    $["$A"=="$B"]&&echo"Stringsareequal"
    $["$A"-eq"$B"]&&echo"Integersareequal"


注:可以不加引号

bash的测试类型 man test 可以看见测试符号帮助文件。

数值测试:
    -gt: 是否大于;
    -ge: 是否大于等于;
    -eq: 是否等于;
    -ne: 是否不等于;
    -lt: 是否小于;
    -le: 是否小于等于

字符串测试:

    ==:是否等于;
    >: ascii码是否大于ascii码
    <: 是否小于
    !=: 是否不等于
    =~: 左侧字符串是否能够被右侧的PATTERN(扩展正则表达式)所匹配
        注意: 此表达式一般用于[[ ]]中;
    -z "STRING":字符串是否为空,空为真,不空为假
    -n "STRING":字符串是否不空,不空为真,空为假

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

练习

1、写一个脚本/root/bin/argsnum.sh,接受一个文件路径作为参数;如果参数个数小于1,则提示用户“至少应该给一个参数”,并立即退出;如果参数个数不小于1,则显示第一个参数所指向的文件中的空白行数

2、写一个脚本/root/bin/hostping.sh,接受一个主机的IPv4地址做为参数,测试是否可连通。如果能ping通,则提示用户“该IP地址可访问”;如果不可ping通,则提示用户“该IP地址不可访问”

文件测试

存在性测试
    -a FILE:同-e
    -e FILE: 文件存在性测试,存在为真,否则为假;

存在性及类别测试
    -b FILE:是否存在且为块设备文件;
    -c FILE:是否存在且为字符设备文件;
    -d FILE:是否存在且为目录文件;
    -f FILE:是否存在且为普通文件;
    -h FILE 或-L FILE:存在且为符号链接文件;
    -p FILE:是否存在且为命名管道文件;
    -S FILE:是否存在且为套接字文件;

文件权限测试:判断的是用户对文件的实际权限。
    -r FILE:是否存在且可读
    -w FILE: 是否存在且可写
    -x FILE: 是否存在且可执行

文件特殊权限测试:
    -g FILE:是否存在且拥有sgid权限;
    -u FILE:是否存在且拥有suid权限;
    -k FILE:是否存在且拥有sticky权限;

文件大小测试:
        -s FILE: 是否存在且非空;
        -z FILE: 文件是否为空

文件是否打开:
        -t fd: fd表示文件描述符是否已经打开且与某终端相关
        -N FILE:文件自动上一次被读取之后是否被修改过
        -O FILE:当前有效用户是否为文件属主
        -G FILE:当前有效用户是否为文件属组


双目测试:测试两个文件的inod是否相同,硬链接

    FILE1 -ef FILE2 : FILE1与FILE2是否指向同一个设备上的相同inode
    FILE1 -nt FILE2 : FILE1是否新于FILE2;指的是mtime。
    FILE1 -ot FILE2 : FILE1是否旧于FILE2;指的是mtime。

示例:[ -f /bin/cat -a -x /bin/cat ]

组合测试条件

第一种方式:
    COMMAND1 && COMMAND2 并且
    COMMAND1 || COMMAND2 或者
    ! COMMAND 非
    如:[ -e FILE ] && [ -r FILE ]

第二种方式:
    EXPRESSION1 -a EXPRESSION2 并且
    EXPRESSION1 -o EXPRESSION2 或者
    ! EXPRESSION

        用于测试命令 的条件组合。;

    # [ -z “$HOSTNAME” -o $HOSTNAME " == "localhost.localdomain" ] && hostname www.magedu.com
注:可以不加引号

    # [ -f /bin/cat -a -x /bin/cat ] && cat /etc/fstab

练习

1、chmod -rw /tmp/file1,编写脚本/root/bin/per.sh,判断当前用户对/tmp/fiile1文件是否不可读且不可写

2、编写脚本/root/bin/nologin.sh和login.sh,实现禁止和充许普通用户登录系统。


预习练习

1、添加用户,用户存在提示推出,不存在创建用户和密码显示创建成功。

[root@yangyouwei ~]# cat aduser.sh 
#!/bin/bash
if [[ $# -lt 1 ]] ;then
        echo "please give username."
        exit 2
fi
if grep "^$1\>" /etc/passwd &> /dev/null ;then
        echo "The user is exsit"
else
        useradd $1
        echo $1 | passwd --stdin $1 &> /dev/null
        echo -e "userad is finished.\n id $1"
fi

2、通过命令行参数给定两个数字,显示较大的数值

$# =2

3、通过命令行参数给定用户名,判断id是奇数还是偶数。

4、通过命令行参数给定两个文本文件名,如果文件不存在则退出,如果都存在则,显示每个文件的空行数,并显示空行数多的。


脚本获取变量的方式

可以自己定义变量 NAME=VLAUE

也可以是使用read 通过用户键盘输入来定

使用read命令来接受输入

使用read来把输入值分配给一个或多个shell变量:
    -p指定要显示的提示
    -t TIMEOUT
    read从标准输入中读取值,给每个单词分配一个变量
    所有剩余单词都被分配给最后一个变量
    read -p “Enter a filename:“ FILE

流程控制

过程式编程语言:
顺序执行
选择执行
循环执行

if 、 while 、 until 都是判定条件 的 $? true or falus
判定前可使用  ! 取反
return可直接设置返回值
return 0
return 1

条件选择if语句

选择执行:
注意:if语句可嵌套

单分支
    if 判断条件:then
        条件为真的分支代码
    fi

双分支
    if 判断条件; then
        条件为真的分支代码
    else
        条件为假的分支代码
    fi

多分支
if CONDITION1; then
    if-true
elifCONDITION2; then
    if-ture
elifCONDITION3; then
    if-ture
    ...
else
    all-false
fi
逐条件进行判断,第一次遇为“真”条件时,执行其分支,而后结束整个if语句


根据命令的退出状态来执行命令

if ping-c1-W2station1&>/dev/null;then
    echo'Station1isUP' 
elif grep"station1"~/maintenance.txt&>/dev/null
    then
    echo'Station1isundergoingmaintenance‘
else 
    echo'Station1isunexpectedlyDOWN!' 
    exit1
fi

条件判断:case语句

case 变量引用in
PAT1)
    分支1
    ;;
PAT2)
    分支2
    ;;
...
*) #任意长度任意字符 (最终会匹配的条件)可以不写这个条件
    默认分支
    ;;
esac

pat)通配模式
case支持glob风格的通配符:
    *: 任意长度任意字符
    ?: 任意单个字符
    []:指定范围内的任意单个字符
    a|b: a或b

注case 分支语句要用双分号结尾。

练习 1、写一个脚本/root/bin/createuser.sh,实现如下功能:使用一个用户名做为参数,如果指定参数的用户存在,就显示其存在,否则添加之;显示添加的用户的id号等信息

[root@yangyouwei ~]# cat aduser.sh 
#!/bin/bash
if [[ $# -lt 1 ]] ;then
        echo "please give username."
        exit 2
fi
if grep "^$1\>" /etc/passwd &> /dev/null ;then
        echo "The user is exsit"
else
        useradd $1
        echo $1 | passwd --stdin $1 &> /dev/null
        echo -e "userad is finished.\n id $1"
fi

2、写一个脚本/root/bin/yesorno.sh,提示用户输入yes或no,并判断用户输入的是yes还是no,或是其它信息

#!/bin/bash

read -p "please enter yes or not:" chosice
if [[ $chosice == [yY][Ee][sS] ]] ;then
        echo "you chose yes"
elif [[ $chosice == [nN][oO] ]] ; then
        echo "you chose no"
else
        echo "please check you enter "
fi

还可以用case写

3、写一个脚本/root/bin/filetype.sh,判断用户输入文件路径,显示其文件类型(普通,目录,链接,其它文件类型)

read -p "please enter file path" files
if [[ -f $files ]] ; then
    echo "file is comment file"
elif [[ -d $files ]] ; then
    echo "file is dir"
elif [[ -h $files ]] ; then
    echo "file is link"
else
    echo "unknow file"
fi

应该先判断是否是软连接再判断其他
也可以用case写

4、写一个脚本/root/bin/checkint.sh,判断用户输入的参数是否为正整数

[[ $1 -ge 0 ]]

循环

循环执行
    将某代码段重复运行多次
    重复运行多少次:
        循环次数事先已知
        循环次数事先未知
有进入条件和退出条件

for, while, until

for循环

for 变量名in 列表;do
    循环体
    ..
done

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

for循环

列表生成方式:
    (1) 直接给出列表
    (2) 整数列表:
        (a) {start..end}
        (b) $(seq[start [step]] end)
    (3) 返回列表的命令
        $(COMMAND)
    (4) 使用glob,如:*.sh
    (5) 变量引用;
        $@, $*
    (6)可以使用路径放在in后面,for直接使用路径下的文件名称作为列表

练习:用for实现

1、判断/var/目录下所有文件的类型

#!/bin/bash
#author yangyouwei
read -p "give a path:" path_file

for type in `ls -A ${path_file}`;do
        file "${path_file}/${type}"
done
~

2、添加10个用户user1-user10,密码同用户名

#!/bin/bash
#author yangyouwei
for user_name in user{1..10};do
    if grep "^$user_name\>" /etc/passwd &> /dev/null ;then
            echo "The user is exsit"
    else
          useradd $user_name
          echo $user_name | passwd --stdin $user_name &> /dev/null
      echo -e "userad is finished.\n "
    fi  
done

3、/etc/rc.d/rc3.d目录下分别有多个以K开头和以S开头的文件;分别读取每个文件,以K开头的文件输出为文件加stop,以S开头的文件输出为文件名加start; “K34filename stop” “S66filename start”

#!/bin/bash
for files in $(ls -A /etc/rc.d/rc3.d) ; do
        if echo $files | grep -i "^k.*$" > /dev/null ; then
                echo "$files stop"
        elif echo $files | grep -i "^s.*$" > /dev/null ; then
                echo "$files start"
        fi
done

4、写一个脚本,提示输入正整数n的值,计算1+2+3+…n的总和

#!/bin/bash
read -p "please enter a int_number>0:" sum_num
[[ $sum_num -gt 0 ]] || echo "please enter a int_number>0" ; exit
for add_num in $(seq -s "+" 1 "$sum_num" ) ; do
        echo $add_num | bc
done

5、写一个脚本,提示请输入网络地址,如192.168.0.0,判断输入的网段中主机在线状态

#!/bin/bash
for ipp_add in {0..254} ; do
        if ping -c1 -w0.5 172.18.19.$ipp_add > /dev/null ; then
                echo "$ipp_add is up"
        else
                echo "$ipp_add is down"
        fi
done

6、打印九九乘法表

#!/bin/bash
for mast_num in $(seq 1 9); do
        for sec_num in $(seq 1 $mast_num);do
                echo -n "${mast_num}x${sec_num}=$[ mast_num * sec_num ] "
        done
        echo -e "\n"
done

while循环

while CONDITION; do

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

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

进入条件:CONDITION为true;

退出条件:CONDITION为false

示例

[root@localhost ~]# i=1 ;while [ $i -le 9 ] ;do echo $i; let i++ ; done
1
2
3
4
5
6
7
8
9

死循环

while ture ; do
COMMAND
done

练习:用while实现

1、求100以内所有正整数之和

2、通过ping命令探测172.16.250.1-254范围内的所有主机的在线状态,统计在线主机和离线主机各多少。

3、打印九九乘法表

4、利用变量RANDOM生成10个随机数字,输出这个10数字,并显示其中的最大者和最小者

5、打印国际象棋棋盘

until循环

until CONDITION; do
    循环体
done

进入条件:CONDITION 为false
退出条件:CONDITION 为true

循环控制语句continue—停止本次循环的执行或本次循环最后的某段代码的执行,但是以后的循环继续执行 默认是1

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

while CONDTIITON1; do
    CMD1
    ...
    if CONDITION2; then
    continue
    fi
    CMDn
    ...
done

循环控制语句break—跳出循环 ,并停止整个循环。默认是1

用于循环体中

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

    while CONDTIITON1; do
    CMD1
    ...
    if CONDITION2; then
    break
    fi
    CMDn
    ...
done

创建无限循环

while true; do5
    循环体
done

until false; do
    循环体
Done

练习 1、每隔3秒钟到系统上获取已经登录的用户的信息;如果发现用户hacker登录,则将登录时间和主机记录于日志/var/log/login.log中,并提示该用户退出系统。

#!/bin/bash

lg=none

while [[ $lg != hacker ]] ; do
        sleep 3
        logname=$(w | grep -o "^hacker\>")
        tervar=$(  w | sed -n '/^hacker\>/p' | tr -s " "  | cut -d" " -f2 )
        lg=$logname
done
echo "warrning please quit!" > "/dev/$tervar"

2、随机生成10以内的数字,实现猜字游戏,提示比较大或小,相等则退出。

!/bin/bash

read -p "please enter a nmuber(1-10): " gamnum random1=$[RANDOM%10] while [[ $gamnum -ne $random1 ]] ; do [[ $gamnum -lt $random1 ]] && echo "小了" || echo "大了" read -p "again !please enter a nmuber(1-10): " gamnum done

echo $[$RANDOM%20]

特殊用法

while循环的特殊用法(遍历文件的每一行):
    while read line; do
    循环体
    done < /PATH/FROM/SOMEFILE
依次读取/PATH/FROM/SOMEFILE文件中的每一行,且将行赋值给变量line
练习

扫描/etc/passwd文件每一行,如发现GECOS字段为空,则填充用户名和单位电话为62985600,并提示该用户的GECOS信息修改成功。

特殊用法 双小括号方法,即((…))格式,也可以用于算术运算等于 let EXPRESSION

双小括号方法也可以使bash Shell实现C语言风格的变量操作
    #I=10
    #((I++))

for循环的特殊格式:  help for

for ((控制变量初始化;条件判断表达式;控制变量的修正表达式))
do
    循环体
done

控制变量初始化:仅在运行到循环代码段时执行一次

控制变量的修正表达式:每轮循环结束会先进行控制变量修正运算,而后再做条件判断





for ((: for (( exp1; exp2; exp3 )); do COMMANDS; done
Arithmetic for loop.

Equivalent to
    (( EXP1 ))
    while (( EXP2 )); do
            COMMANDS
            (( EXP3 ))
    done
EXP1, EXP2, and EXP3 are arithmetic expressions.  If any expression is
omitted, it behaves as if it evaluates to 1.

Exit Status:
Returns the status of the last command executed.

select 循环与菜单

select variable in list
    do
        循环体命令
    done

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

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

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

select 与case

select 是个无限循环,因此要记住用break 命令退出循环,或用exit 命令终止脚本。也可以按ctrl+c 退出循环。

select 经常和case 联合使用

与for 循环类似,可以省略in list ,此时使用位置参量

函数介绍

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

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

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

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

定义函数

函数由两部分组成:函数名和函数体。
语法一:

    function f_name{
    ...函数体...
    }

语法二:

    f_name() {
    ...函数体...
    }

函数使用

函数的定义和使用:
    可在交互式环境下定义函数
    可将函数放在脚本文件中作为它的一部分
    可放在只包含函数的单独文件中

调用:函数只有被调用才会执行;
    调用:给定函数名
    函数名出现的地方,会被自动替换为函数代码

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

函数返回值

函数有两种返回值:

函数的执行结果返回值:

    (1) 使用echo或printf命令进行输出
    (2) 函数体中调用命令的输出结果

函数的退出状态码:
    (1) 默认取决于函数中执行的最后一条命令的退出状态码
    (2) 自定义退出状态码,其格式为:
    return 从函数中返回,用最后状态命令决定返回值
    return 0 无错误返回。
    return 1-255 有错误返回

交互式环境下定义和使用函数

示例:
    $dir() {
    > ls-l
    > }


定义该函数后,若在$后面键入dir,其显示结果同ls-l的作用相同。
$dir

该dir函数将一直保留到用户从系统退出,或执行了如下所示的unset命令:
$ unsetdir

在脚本中定义及使用函数

函数在使用前必须定义,因此应将函数定义放在脚本开始部分,直至shell首次发现它后才能使用
调用函数仅使用其函数名即可。
示例:
    $cat func1
    #!/bin/bash
    # func1
    hello()
    {
    echo "Hello there today's date is `date +%F`"
    }
    echo "now going to the function hello"
    hello
    echo "back from the function

使用函数文件

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

文件名可任意选取,但最好与相关任务有某种联系。例如:functions.main

一旦函数文件载入shell,就可以在命令行或脚本中调用函数。可以使用set命令查看所有定义的函数,其输出列表包括已经载入shell的所有函数。

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

创建函数文件

函数文件示例:

    $cat functions.main
    #!/bin/bash
    #functions.main
    findit()
    {
        if [ $# -lt1 ] ; then
            echo "Usage:finditfile"
            return 1
        fi
    find / -name $1 –print
    }

载入函数

函数文件已创建好后,要将它载入shell

定位函数文件并载入shell的格式:

    . filename 或source filename
注意:此即<点> <空格> <文件名>
    这里的文件名要带正确路径

示例:上例中的函数,可使用如下命令:
    $ . functions.main

检查载入函数

使用set命令检查函数是否已载入。set命令将在shell中显示所有的载入函数。

示例:
    $set
    findit=()
    {
        if [ $# -lt1 ]; then
            echo "usage :finditfile";
            return 1
        fi
    find / -name $1 -print
    }

执行shell函数

要执行函数,简单地键入函数名即可:
示例:
    $finditgroups
    /usr/bin/groups
    /usr/local/backups/groups.bak

删除shell函数

现在对函数做一些改动。首先删除函数,使其对shell不可用。使用unset命令完成此功能.

命令格式为:
unset function_name

实例:
    $unset findit
    再键入set命令,函数将不再显示

函数参数

函数可以接受参数:

传递参数给函数:调用函数时,在函数名后面以空白分隔给定参数列表即可;例如“testfuncarg1 arg2 …”

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

函数变量

变量作用域:
    环境变量:当前shell和子shell有效
    本地变量:只在当前shell进程有效,为执行脚本会启动专用子shell进程;因此,本地变量的作用范围是当前shell脚本程序文件,包括脚本中的函数。
    局部变量:函数的生命周期;函数结束时变量被自动销毁

注意:如果函数中有局部变量,如果其名称同本地变量,使用局部变量。

在函数中定义局部变量的方法
    local NAME=VALUE

函数递归实例

函数递归:
    函数直接或间接调用自身
    注意递归层数

递归实例:
    阶乘是基斯顿·卡曼于1808 年发明的运算符号,是数学术语
    一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且有0的阶乘为1。自然数n的阶乘写作n!。
    n!=1×2×3×...×n。
    阶乘亦可以递归方式定义:0!=1,n!=(n-1)!×n。
    n!=n(n-1)(n-2)...1
    n(n-1)! = n(n-1)(n-2)!

函数递归示例

    示例:fact.sh

        #!/bin/bash
        #
        fact() {
        if [ $1 -eq0 -o $1 -eq1 ]; then
        echo 1
        else
        echo $[$1*$(fact $[$1-1])]
        fi
        }
        fact 5

练习

1、写一个服务脚本/root/bin/testsrv.sh,完成如下要求

(1) 脚本可接受参数:start, stop, restart, status (2) 如果参数非此四者之一,提示使用格式后报错退出 (3) 如是start:则创建/var/lock/subsys/SCRIPTNAME, 并显示“启动成功” 考虑:如果事先已经启动过一次,该如何处理? (4) 如是stop:则删除/var/lock/subsys/SCRIPTNAME, 并显示“停止完成” 考虑:如果事先已然停止过了,该如何处理? (5) 如是restart,则先stop, 再start 考虑:如果本来没有start,如何处理? (6) 如是status, 则如果/var/lock/subsys/SCRIPTNAME文件存在,则显示“SCRIPTNAMEis running…” 如果/var/lock/subsys/SCRIPTNAME文件不存在,则显示“SCRIPTNAME is stopped…” 其中:SCRIPT_NAME为当前脚本名 (7)在所有模式下禁止启动该服务,可用chkconfig和service命令管理

2、编写一个脚本/root/bin/copycmd.sh

(1) 提示用户输入一个可执行命令名称; (2) 获取此命令所依赖到的所有库文件列表 (3) 复制命令至某目标目录(例如/mnt/sysroot)下的对应路径下; 如:/bin/bash ==> /mnt/sysroot/bin/bash /usr/bin/passwd==> /mnt/sysroot/usr/bin/passwd (4) 复制此命令依赖到的所有库文件至目标目录下的对应路径下: 如:/lib64/ld-linux-x86-64.so.2 ==> /mnt/sysroot/lib64/ld-linux-x86-64.so.2 (5)每次复制完成一个命令后,不要退出,而是提示用户键入新的要复制的命令,并重复完成上述功能;直到用户输入quit退出

3、写一个函数实现两个数字做为参数,返回最大值

4、写一个函数实现数字的加减乘除运算,例如输入1 + 2,,将得出正确结果

5、斐波那契数列又称黄金分割数列,因数学家列昂纳多·斐波那契以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:0、1、1、2、3、5、8、13、21、34、……,斐波纳契数列以如下被以递归的方法定义:F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)(n≥2) 写一个函数,求n阶斐波那契数列

6、汉诺塔(又称河内塔)问题是源于印度一个古老传说。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。 利用函数,实现N片盘的汉诺塔的移动步骤

函数可以定义到交互式环境下。类似于变量

可以单独执行,使用set可以查看到。

优先级 : 别名 函数 内部命令 外部命令

函数放在调用函数的代码之前

return 退出函数 制定退出状态码 0 1-255

在函数中使用exit的话直接退出脚本

脚本中调用函数,而函数存为文本格式放在其他地方。 在脚本中调用,用source 或点 调用该函数

lld commadn

原创文章,作者:yyw,如若转载,请注明出处:http://www.178linux.com/37884

(0)
yywyyw
上一篇 2016-08-21
下一篇 2016-08-21

相关推荐

  • 元数据、修改时间戳、文件名通配、定义命令的别名、I/O重定向及管道

      文件的元数据 文件的数据分为两类:一类为元数据,既属性数据;一种就是数据本身:  数据是指普通文件中的实际数据  元数据指用来描述一个文件的特征的系统数据,诸如访问权限、文件拥有者以及文件数据块的分布信息(inode…)等等 那么如何查看文件的详细信息? stat命令: 功能说明:    显示inode内容命令用法:    stat…

    Linux干货 2016-11-06
  • N22-℡浮生.若夢 ╮第九周作业

    1、写一个脚本,判断当前系统上所有用户的shell是否为可登录shell(即用户的shell不是/sbin/nologin);分别这两类用户的个数;通过字符串比较来实现; #!/bin/bash ## declare -i log_user declare -i notlog_user for i …

    Linux干货 2016-12-12
  • 使用httpd反向代理模块实现tomcat负载均衡集群(下)

    上一篇讲解了http使用mod_http和mod_ajp代理模块实现tomcat负载均衡,下面我们来讲解使用http的mod_jk实现taomcat的负载均衡集群: 注意:http的mod_jk是第三方扩展模块,在新http版本中以不支持,在httpd 1.3和2.0效果较好 6、使用mod_jk实现tomcat负载均衡集群 6.1安装mod_jk [roo…

    Linux干货 2015-07-21
  • Linux基础知识(三)

     本文的主要内容是:  1、列出当前系统上所有已经登录的用户的用户名,注意:同一个用户登录多次,则只显示一次即可。  2、取出最后登录到当前系统的用户的相关信息。  3、取出当前系统上被用户当作其默认shell的最多的那个shell。  4、将/etc/passwd中的第三个字段数值最大的后10个用户的信息全…

    Linux干货 2016-10-03
  • N25第四周 chmod chown以及 grep命令的常用示例

    1、复制/etc/skel目录为/home/tuser1,要求/home/tuser1及其内部文件的属组和其它用户均没有任何访问权限。     [root@localhost ~]# cp -r /etc/skel /home/tuser1   &n…

    Linux干货 2016-12-22
  • SELinux在httpd服务端中的使用

    一、启用SELinux策略并安装httpd服务,改变网站的默认主目录为/website,添加SELinux文件标签规则,使网站可访问(以CentOS7系统操作) 1、首先查看本系统是否已经安装httpd服务 2、查看httpd的配置文件所在路径 3、创建主目录为/website与网页文件"index.html",并更改httpd服务为该路…

    Linux干货 2016-09-16

评论列表(1条)

  • 马哥教育
    马哥教育 2016-08-22 09:33

    文章结构层次清晰,分类明确,课堂练习应该主动去找资料完成,或者请教其它同学尽量完成,而不要留空。