zoukankan      html  css  js  c++  java
  • shell

    Shell

    Shell脚本入门

     Shell脚本与Windows/Dos下的批处理相似,也就是用各类命令预先放入到一个文件中,方便一次性执行的一个程序文件, Shell就是一个命令行解释器,Shell本身是一个用C语言编写的程序, Shell既是一种命令语言,又是一种程序设计语言(就是你所说的shell脚本)。shell脚本脚本文件通常以.sh作为后缀名,第一行以#!开头指定执行脚本的程序:

    #!/usr/bin/bash

    #是shell脚本中的行注释符。

    这里我们来使用最常用的bash

    1.新建文件hello_shell

    $ vi hello_shell

    2. 输入Shello命令

    #! /bin/bash

    echo "Hello Shell!"
    3、保存

    echo是一个输出命令,就是输出一句话

    4. 赋权限

    我们要让系统知道我们刚才新建的文件是可执行的,所以我们要赋给其可执行的权限

    现在我们的文件是不可执行的,只有读写权限:

     

    下面是赋权

    $ chmod 711 hello_shell

     

    5. 执行

    ./ 表示再当前目录查找命令,如果什么都不加的话,系统默认会在PATH里寻找,而的当前目录通常不在PATH里,所以找不到命令。

    通常有三种执行脚本的方式:

    1、sh start.sh: 在终端中创建一个sh子进程执行脚本, 执行者需要拥有脚本的读权限。该方式实际上是将脚本路径作为参数传递给了sh命令。

    2、source start.sh: 在终端中执行脚本,相当于将脚本中的指令逐条复制到终端执行。脚本中局部变量将保留在终端环境变量中, 脚本的pid和工作目录等环境也与终端一致。

    3、./start.sh: 根据HashBang指定的程序,在子进程中执行脚本。

    变量

    1. 变量的声明和定义

    Shell里的变量类型:字符串、数值。定义的方式其实是一样的,字符串用单引号或双引号标识。

    Shell里变量命名规范:首个字符必须为字母(a-z,A-Z)任何变量都只能由字母(包括大小写)、数字和下划线组成变量中不能有空格,不能使用bash里的关键字(可用help命令查看保留关键字)

    变量在使用前无需声明,在为变量赋值时=左右不能添加空格。

    y_name="yuguiyang"

    y_age=24

    2. 变量的使用

    使用一个定义过的变量,只要在变量名前面加美元符号$即可

    y_name="yuguiyang"

    y_age=24

    echo "name:$y_name"

    echo "age:$y_age"

    我们也可以使用 {}将变量括起来,加花括号是为了帮助解释器识别变量的边界

    A=a

    AB=ab

    echo ${A}B

    可以把命令的输出作为返回值, 如:

    PWD=$(pwd)

    在单引号标识的字符串中$不被作为变量标识符, 而双引号则会将$替换为变量内容。

    A="abc"

    echo '$A' # $A

    echo "$A" # abc

    字符串拼接不需要任何运算符,只需要将它们写在一起即可:

    A="abc"

    B="123"

    echo "$A+$B"  # abc+123

    echo "$A$B"  # abc123

    echo "$Adef"  # abcdef

     

    3、整型变量

    shell仅支持整型计算, declare命令可以声明整型变量,let指令用于算术运算:

    declare -i a=1

    let a=a+1

    echo $a  # 2

    let a+=1

    echo $a  # 3

    let指令支持算术运算符包括:

    +:加法

    -: 减法

    *: 乘法

    /: 除法

    **: 乘方

    %: 取余

    let指令也支持算术运算符对应的算术赋值运算符,如+=。

    4. 只读变量

    使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变

    #!/bin/bash

    y_id=1990

    readonly y_id

    echo "y_id:${y_id}"

    y_id=2014

    echo "y_id:${y_id}"

    这里我们尝试修改只读变量y_id,运行时,会报错,提示该变量为只读变量:

    5. 删除变量

    使用 unset 命令可以删除变量,变量被删除后不能再次使用;

    unset 命令不能删除只读变量。

    #!/bin/bash

    y_id=1990

    y_name="yuguiyang"

    echo "y_id:${y_id}"

    echo "y_name:${y_name}"

    readonly y_id

    unset y_id

    unset y_name

    echo "y_id:${y_id}"

    echo "y_name:${y_name}"


    由于y_id 是只读变量,所以不会被删除,y_name被删除后,输出为空

    结果:

    6. 变量的作用范围

    运行shell时,会同时存在三种变量:

    1) 局部变量

    局部变量在脚本或命令中定义,作用域仅限执行脚本的进程,即仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。

    2) 环境变量(全局变量)

    所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。子进程可以继承父进程的全局变量。环境变量配置保存于/etc/profile文件中。可以通过export来查看。也可以通过export来设置环境变量:export  xxx=”yyy”

    set |grep xxx可以查看某个具体的变量的值

    3) shell变量

    shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行

    7. 特殊变量

    shell中预定义了一些特殊变量,通过这些变量可以获得环境信息:

    ●$$: 执行当前脚本的进程ID(pid)

    ●$?: 上一条命令的返回值,如果上一个命令成功执行则$?的值为0,否则为其他非零值

    ●$!: 上一条后台指令的执行进程的ID

    上述变量在交互式终端中同样有效。

    还有一些变量可以获得执行脚本时传入的参数:

    ●$0: 当前脚本的文件名

    ●$1~$n: 传给脚本的第n个参数

    ●$#: 传入脚本的参数的个数

    ●$@: 参数列表

    ●$*: 单个字符串形式的参数列表

    数组

    bash中可以使用圆括号定义数组,元素之间用空格分割,数组下标从0开始:

    1. 定义

    Shell脚本中支持一维数组,下标从0开始

    我们可以这样

    y_books[0]="today"

    y_books[1]="onepiece"


    可以这样:

    y_books=("one" "two" "three")


    还可以这样:

    y_books=(

            "one"

            "two"

            "three")

    2. 读取数组内容

    我们使用下标来获取数组的信息

    echo "book:${y_books[0]}"

    echo "book:${y_books[1]}"

    3. 获取所有的元素

    使用*或者@

    示例:

    echo "books:${y_books[*]}"

    echo "books:${y_books[@]}"

    4. 获取数组长度

    echo "数组长度"

    echo "length:${#y_books[*]}"

    echo "length:${#y_books[@]}"

     

    读取键盘输入

    之前我们都是直接将内容输出,那怎样可以交互呢?我们使用read命令,read可以直接使用而不需要在前面声明这个变量。

    #!/bin/bash

    echo "请输入你的名字:"

    read y_name

    echo "你好,${y_name}"

     

    #!/bin/bash

    echo ""

    echo "姓名:"

    read y_name

    echo "年龄:"

    read y_age

    echo ""

    echo "${y_name},今年${y_age}岁。"

    运算符

    Linux中有3类运算符:算术运算符、逻辑运算符、比较运算符

    1. 算术运算符

    在学习运算符之前我们先看个东西,之前没有注意到:

    #!/bin/bash

    y_price=10.9

    y_total=${y_price}+3

    echo "y_total:${y_total}"


    y_total应该输出13.9?不是的,输出值为:10.9+3。因为Shell中默认把变量看作是字符串,所以才会显示成这样。要解决这个问题,我们需要使用let命令

    let val="3 + 6"

    echo "val:${val}"

    a=5

    b=9

    let val="a + b"

    echo "val:${val}"

    let val="${a} + ${b}"

    echo "val:${val}"


    注意:等号(=)两边没有空格,而加号(+)两边有空格,且所有的运算符两边都要有空格。

    从上面的例子可以发现:let表达式后,调用变量时可以不使用$符号let也可以使用(())代替

    ((val="10 + 30"))

    echo "val:${val}"


    注意:let只可以计算整数,不可以算浮点数

    2. 逻辑运算符

     

    3. 比较运算符

     

    #!/bin/bash

    a=3

    b=4

    test ${a} -eq ${b}

    echo "$?"

    我们这里使用test来判断a和b是否相等,$?可以返回真、假。对于运算符,我们先说到这,我们会在后面的练习中使用

    条件控制语句: if

    这里的if语句和其他开发语言中的差不多, Shell脚本中的if语句有3种

    1. if ... then ... fi

     

    Condition如果为真,则执行then后面的语句,为假则结束。这里需要注意的是:Condition和方括号之间需要有空格,否则就会报错

    #!/bin/bash

    echo "3+3=?"

    read y_result

    if [ ${y_result} -eq 6 ]

    then

            echo "Ha,good."

    fi

    结果:

     

    2.  if ... then ... else ... fi

     

    这个多了个else,可以对不符合表达式时做些处理

    #!/bin/bash

    echo "3+3=?"

    read y_result

    if [ ${y_result} -eq 6 ]

    then

            echo "Ha,good."

    else

            echo "Oh,wrong."

    Fi

     

    3.  if ... elif ... fi

     

    有时我们想要在else的时候,再做些判断,可以使用elif

    #!/bin/bash

    echo "3+3=?"

    read y_result

    if [ ${y_result} -eq 6 ]

    then

            echo "Ha,good."

    elif [ ${y_result} == -1 ]

    then

            echo "Hehe,you find me."

    Fi

     

    <, >等运算符在[]中只能用于字符串的比较, 而在[[]]中<, >可以用于整型和字符串的大小比较, 也可以使用&&和||来书写逻辑表达式。

    if的条件判断不一定使用[]或[[]]表达式,它可以是任何一个命令。命令的返回值为0则if判断为真, 非0判断为假。

    []和[[]]转义表达式也可以像普通指令一样执行,判断为真则返回0,假则返回非0值。

    [ 2 -gt 1 -a 3 -lt 4 ] && echo 'ok'

    判断字符串相等

    if [ ${NAME} = 'tmp' ]; then
        echo "name is tmp"
    fi

    判断文件是否存在

    if [ -e tmp ]; then
        echo "tmp exists"
    fi 

    判断tmp是否存在,tmp可以是目录或文件。

    判断是否为普通文件

    if [ -f tmp ]; then
        echo "file tmp exists"
    fi

    判断tmp是否为文件,tmp不能是目录。

    判断是否为目录

    if [ -d tmp ]; then
        echo "directory tmp exists"
    fi

    判断是否具有执行权限

    if [ -x tmp ]; then
        echo "tmp is executable"
    fi

    不判断文件是否可执行,只判断是否拥有x权限。 因此,tmp为有x权限的目录时也会判断为真。类似的还有,-w判断是否拥有写入权限, -r判断是否拥有读取权限。

    判断是否为空文件

    if [ -s tmp ]; then
        echo "file is not empty"
    fi

    条件控制语句: case

    条件控制语句还有一个case,对于需要多个elif的可以使用case尝试下

     

    #!/bin/bash

    echo "choose a number from 1 to 4."

    read y_num

    case ${y_num} in

    1)

            echo "you select 1."

            ;;

    2)

            echo "you select 2."

            ;;

    3)

            echo "you select 3."

            ;;

    4)

            echo "you select 4."

            ;;

    *)

            echo "please choose a number from 1 to 4."

            ;;

    esac


    *可以代表一种默认情况

    ;; 与其他语言中的 break 类似,意思是跳到整个 case 语句的最后。

     

    循环控制语句: while

    前面我们介绍了条件控制语句,这里我们介绍下循环控制语句while

     

    同样的,Condition左右都需要有空格,while循环和Java中的都差不多,这里也不赘述什么了

    #!/bin/bash

    clear

    echo "while demo"

    y_result="ygy"

    while [ ${y_result} != "lufei" ]

    do

            echo "Who are you ?"

            read y_result

    done

    echo "Haha"


    这里我们判断y_result变量的内容是否为“lufei",否则一直循环

     

    #!/bin/bash

    clear

    echo "乘法表"

    let y=1

    while [ ${y} -le 9 ]

    do

            let x=1

            while [ ${x} -le ${y} ]

            do

                    let rs="${x} * ${y}"

                    printf "${x} * ${y} = ${rs}  "

                    let x="${x} + 1"

            done

            let y="${y} + 1"

            echo ""

    done

    代码还好,就是个逻辑,相信大家会有更好的方法来实现

     

    循环控制语句:for in

     

    for会把wordlist中的值按顺序赋给变量,并执行循环体中的内容

    wordlist是一个列表,我们看个例子就知道了

    #!/bin/bash

    echo ""

    for loop in 1 2 3 4 5

    do

            echo "hello ${loop} ."

    done

    一些命令的输出也可以作为序列:

    for i in $(ls); do

      echo $i

    done

    函数

    Shell脚本里面也可以定义函数,函数就像是脚本中的子脚本

    1.函数的定义和调用

    我们使用 function 来定义一个函数,需要用括号括起来

    function show_menu {

            echo ""

            echo "1.显示所有联系人"

            echo "2.退出"

            echo ""

            echo "请选择:"

    }

    调用的话,直接使用函数名进行调用

    下面的函数我们显示个界面,根据用户输入进行判断

    #!/bin/bash

    #显示菜单

    function show_menu {

            echo ""

            echo "1.显示所有联系人"

            echo "2.退出"

            echo ""

            echo "请选择:"

    }

    #循环标识,为1时进行循环

    let flag=1

    while [ ${flag} == 1 ]

    do

            #显示菜单信息

            show_menu

           

            #读取输入

            read rs

            case ${rs} in

                    1)

                           echo "暂无联系人。"

                           ;;

                    2)

                           echo "Bye."

                           let flag=0

                           ;;

                    *)

                           echo "请选择1或者2"

                           ;;

            esac

    done

    2. 函数的参数

    函数同样可以传递参数的,我们可以使用 ${1},${2}..来使用

    #!/bin/bash

    function logon {

            if [ ${1} == "lufei" ] && [ ${2} == "haha" ]

            then

                    echo "登录成功!"

            else

                    echo "登录失败!"

            fi

    }

    let flag=1

    while [ ${flag} -eq 1 ]

    do

            echo "请输入用户名:"

            read u_name

            echo "请输入密码:"

            read u_password

            logon ${u_name} ${u_password}

    done


    这里是一个简单的登录验证示例,我们将获取的用户名和密码传递给函数logon去验证

    4. 参数个数验证

    在这里调用函数的时候很可能参数不够,导致程序出错;我们可以使用$#来获取参数的个数进行判断

    #!/bin/bash

    function logon {

            #判断参数个数,2个参数就进行信息验证

            if [ $# -eq  2 ]

            then

                    if [ ${1} == "lufei" ] && [ ${2} == "haha" ]

                    then

                           echo "登录成功!"

                    else

                           echo "登录失败!"

                    fi

            else

                    #提示错误信息

                    echo "参数个数不符"

            fi

    }

    let flag=1

    while [ ${flag} -eq 1 ]

    do

            echo "请输入用户名:"

            read u_name

            echo "请输入密码:"

            read u_password

           

            #调用时,只传了一个参数

            logon ${u_name}

    done

    这个程序,暂时还不能退出,可以使用CTRL+C强制退出

     

     

    5. 函数返回值

    函数可以有返回值,return,但是这里的话,返回值必须是0~256之间的一个整数

    #!/bin/bash

    function test_return {

            echo "please input a number:"

            read  num

            return ${num}

    }

    test_return

    echo "haha,you input: $?"


    子程序

    在实际开发中,我们的程序可能比较大,需要按模块开发,有不同的子程序

    每个子程序都是独立的一个 文件,我们可以在一个主程序中调用他们

    #!/bin/bash

    clear

    echo ""

    echo "1.Add"

    echo "2.Delete"

    echo "3.Display"

    echo "4.Quit"

    read selection

    case ${selection} in

    "1")

            ./child_add

            ;;

    "2")

            ./child_delete

            ;;

    "3")

            ./child_display

            ;;

    "4")

            ./child_quit

            ;;

    *)

            echo "oh,no."

            ;;

    esac


    这是我们的一个主程序,我们根据输入,调用不同的程序

     

    就子程序的话,先说到这,就是一个简单的例子

    3. 显示文件中的内容

    #!/bin/bash

    echo "显示文件中的信息"

    echo "f_users.bat"

    echo "---------------"

    cat f_users.bat

    输出重定向

    1. linux下常用的输入输出操作符

    标准输入(stdin): < 或者 <<

    标准输出(stdout): >或者 >>;

    标准正确输出(stderr): 1>或者1>>   >或者 >>  ;

    标准错误输出(stderr): 2>或者2>>;

    2. 输出重定向

    我们使用 > 或者 >>

    我们使用 ls命令来显示2个文件,其中file02存在,而file03不存在,这样我们会输出一条错误信息,一条正确信息

    1. 我们将正确信息输出到文件中

    默认会将正确信息输出,所以这2种写法都可以

    输出到文件同样使用echo命令:

    1. 写入文件

    #!/bin/bash

    echo "write to file."

    echo "iput your name:"

    read y_name

    echo "Hello,${y_name}" > f_users.bat


    我们这里使用 > 将信息重定向到了f_users.bat这个文件中。如果原来已经有一个同名的文件,使用大于号(>)会覆盖这个文件

    2. 追加信息

    我们使用 >>可以向文件中追加信息

    #!/bin/bash

    echo "write to file."

    echo "iput your name:"

    read y_name

    echo "Hello,${y_name}" >> f_users.bat

    echo

    在我们之前的例子中,我们经常使用echo命令将一些信息输出,这回我们来详细了解下echo这个命令

    echo -n :输出后不会自动换行

    echo -e :会对一些字符做特殊处理

    printf

    printf同样可以输出信,但printf没有像echo一样自动换行

    printf可以对输出进行格式化

     

    如果需要限定输出的宽度,格式为%flags width.precision format-specifier,width表示宽度,是整数,默认是右边对齐,如果需要左边对齐,在前面加“-”,

    例如"%-20s"表示从左边开始对齐,宽度为20,如果字符串长度少于20,通过空格补齐,长度超过20,则会完全显示。

    precision在浮点值中提供四舍五入。例如%5.6G,长度为5,精度为6。精度是可选的。长度和精度的值可以参数中指定,例如printf "%*.*G/n" 5 6 $myvalue。

    长度指显示中占的字符长度,与字符长度的同义。如果长度比实际的少,例如实际字符长度更大或者所要求的精度更大,则显示按实际长度。

    后台执行

    shell可以执行一行指令后立即返回, 返回后可以通过$?变量获得执行进程的ID:

    $ sleep 10 &

    [1] 79403

    $ echo $!

    79403

    多命令逻辑执行顺序

    Linux中可以使用分号“;”、双and号“&&”和双竖线“||”来连接多个命令。单"&"符号也算命令连接符号,只不过它是将其前面的命令放入后台执行,所以可以变相地实现命令并行执行。

    1.3.1 分号;

    command1 ; command2

    命令之间没有逻辑关系。分号连接的命令会按照顺序从前向后依次执行,但分号两端的命令之间没有任何逻辑关系,所有写出来的命令最终都会被执行,即使分号前面的命令出错也不影响后面的命令。

    [root@xuexi ~]# ls das;echo "hdakl"

    ls: cannot access das: No such file or directory

    hdakl

    1.3.2 &&

    command1  &&  command2

    逻辑与。&&连接的命令会按照顺序从前向后执行,但只有当command1正确执行才执行command2,如果command1不正确执行,则不执行command2。在bash中,通过预定义变量“$?”来判断命令是否正确执行,如果"$?"的值为0则表示前一条命令正确执行,其他任意值都表示不正确执行。

    [root@xuexi ~]# echo "hdakl" && ls ds

    hdakl

    ls: cannot access ds: No such file or directory

    [root@xuexi ~]# ls das && echo "hdakl"

    ls: cannot access das: No such file or directory

    1.3.3 ||

    command1 || command2

    逻辑或。||连接的命令会按照顺序从前向后执行,但只有当command1不正确执行才执行command2,command1正确执行则不会执行command2。||和&&都是短路符号,符号左右的命令之间具有逻辑关系。

    [root@xuexi ~]# ls das || echo "hdakl"

    ls: cannot access das: No such file or directory

    hdakl

    [root@xuexi ~]# echo "hdakl" || ls ds  

    hdakl

    一般要联合使用&&和||的时候,基本上都会先逻辑与再逻辑或:command1 && command2 || command3。因为在实际中,command2和command3应该都是想要执行的命令。如果command1正确执行,$?就等于0,执行command2,再看情况执行command3,如果command1错误执行,$?就不等于0,所以不执行command2,根据$?为非0值,判断了 || 右边的命令应该被执行。

    通俗点的理解方法是根据语义判断。“如果...就...否则...就...”的语句使用“cmd1 && cmd2 || cmd3”,“如果不...就...否则...就...”使用“!cmd1 && cmd2 || cmd3”。

    例如,如果用户user1存在,就显示用户已经存在,否则,就添加此用户。

    [root@xuexi tmp]# id user1 && echo "user1 exists" || useradd user1

    如果用户user2不存在,则添加此用户,否则显示用户已存在。

    [root@xuexi tmp]# !id user2 && useradd user2 || echo "user2 exists"

    如果用户user3不存在,则添加此用户,并设定其密码为用户名本身,否则显示用户已存在。

    [root@xuexi tmp]# !id user3 && useradd user3 && echo "user3" | passwd --stdin user3 || echo "user3 exists"

    1.3.4 &

    command1 &

    command1 & command2

    &表示将其前面的命令放入后台执行,放入后台后会立即返回到bash环境让用户可以继续和bash交互。如果&符号连接了两个命令,则其前面的命令被放入后台,立即执行后面的命令,所以可以简单地认为这两个命令是并行执行的,两端的命令之间也没有任何逻辑关系。

    需要注意的一点是,在终端的bash环境下,子shell中的后台的进程不受终端控制,在终端被关闭时它会挂靠在init/systemd进程下,因此退出终端或脚本shell环境,无法中断这些后台进程。例如:

    [root@xuexi ~]# (sleep 10 &)     # 终端1上执行,立即关闭该终端

    [root@xuexi ~]# ps aux | grep slee[p]     # 终端2上捕捉sleep进程

    root 5732 0.0 0.0 107892 624 ? S 00:28 0:00 sleep 10

    注意ps结果中的"?",它表示非终端进程,即脱离了终端。

  • 相关阅读:
    rpm常见命令使用说明
    终端传值给Makefile、Makefile传值给C++代码
    redis实现异步队列
    redis实现分布式锁
    正则表达式
    nginx安装
    metaq
    zeromq
    ActiveMq
    http压力测试
  • 原文地址:https://www.cnblogs.com/tester-l/p/11398779.html
Copyright © 2011-2022 走看看