精华内容
下载资源
问答
  • 在代码复用及可维护性方面,函数有着巨大的优势,因此,把常用功能封装函数是一件非常平常的事。shell脚本中,怎么定义函数及使用函数呢? 函数定义: # func_name 函数名 function func_name(){ #函数体...
    在代码复用及可维护性方面,函数有着巨大的优势,因此,把常用功能封装成函数是一件非常平常的事。shell脚本中,怎么定义函数及使用函数呢?
    函数定义:

    # func_name 函数名  
    function func_name(){
    	#函数体内容
    }
    或
    # func_name 函数名
    func_name(){
    #函数体内容
    }
    函数调用:

    func_name parm 

    函数体中,可以通过$1 $2 ...$9接受函数调用中的变量

    函数可以通过return 返回函数执行的结果

    可以通过下面一个简单例子,来看下在shell脚本中怎么定义使用函数

    1、在/root/bin目录下新建函数文件main.fun

    vim /root/bin/main.fun  

    2、在main.fun文件中编写函数findit内容

    #!/bin/sh
    function findit(){        
    	if [ $# -lt 1 ] ; then  #判断函数参数个数               
    	 echo "Usage: findit filename"               
    	 return 1        
    	fi	    
    	for loop #遍历调用函数的参数          
    		do
    	                find $HOME -name $loop -print
    	        done    
    	return 0
    }

    3、Shell中载入函数文件/root/bin/main.fun

    . /root/bin/main.fun

    4检查载入文件
    set 

    set命令将在shell中显示所有的载入函数

    5、调用函数

     findit mysql php #在home 目录查找文件mysql 和文件php






    展开全文
  • Linux shell 脚本编程-高级篇 (一)

    千次阅读 2018-08-03 10:57:27
    可以将 shell 脚本代码放进函数封装起来,这样就能在脚本中的任何地方多次使用它了。 1.1 基本的脚本函数 在开始编写较复杂的 shell 脚本时,会发现自己重复使用了部分能够执行特定任务的代码。这些代码有时很...

    1. 创建函数


    可以将 shell 脚本代码放进函数中封装起来,这样就能在脚本中的任何地方多次使用它了。


    1.1 基本的脚本函数


    在开始编写较复杂的 shell 脚本时,会发现自己重复使用了部分能够执行特定任务的代码。这些代码有时很简单,比如显示一条文本消息,或者从脚本用户那里获得一个答案;有时则会比较复杂,需要作为大型处理过程中的一部分被多次使用。

    在后一类情况下,在脚本中一遍又一遍地编写同样的代码会很烦人。如果能只写一次,随后在脚本中可多次引用这部分代码就好了。

    bash shell 提供了这种功能。函数是一个脚本代码块,可以为其命名并在代码中任何位置重用。要在脚本中使用该代码块时,只要使用所起的函数名就行了
    (这个过程称为调用函数)。


    1.1.1 创建函数
    -----------------------------------------------------------------------------------------------------------------------------------------
    有两种格式可以用来在 bash shell 脚本中创建函数。第一种格式采用关键字 function,后跟分配给该代码块的函数名。

        function name {
            commands
        }

    name 属性定义了赋予函数的唯一名称。脚本中定义的每个函数都必须有一个唯一的名称。
    commands 是构成函数的一条或多条 bash shell 命令。在调用该函数时,bash shell 会按命令在函数中出现的顺序依次执行,就像在普通脚本中一样。

    在 bash shell 脚本中定义函数的第二种格式更接近于其他编程语言中定义函数的方式:

        name() {
            commands
        }

    函数名后的空括号表明正在定义的是一个函数。这种格式的命名规则和之前定义 shell 脚本函数的格式一样。


    1.1.2 使用函数
    -----------------------------------------------------------------------------------------------------------------------------------------
    要在脚本中使用函数,只需要像其他 shell 命令一样,在行中指定函数名就行了。

    示例:
        [devalone@devalone 17]$ cat test1.sh
        #/bin/bash
        # using a function in a script

        function func1 {
                echo "This is an example of function"
        }

        count=1
        while [ $count -le 5 ]
        do
                func1
                count=$[ $count + 1 ]
        done

        echo "This is the end of the loop"
        func1
        echo "Now this is the end of the script"

    运行:
        [devalone@devalone 17]$ chmod a+x test1.sh
        [devalone@devalone 17]$ ./test1.sh
        This is an example of function
        This is an example of function
        This is an example of function
        This is an example of function
        This is an example of function
        This is the end of the loop
        This is an example of function
        Now this is the end of the script


    每次引用函数名 func1 时,bash shell 会找到 func1 函数的定义并执行在那里定义的命令。

    函数定义不一定非得是 shell 脚本中首先要做的事,但一定要小心。如果在函数被定义前使用函数,会收到一条错误消息。

    示例:
        [devalone@devalone 17]$ cat test2.sh
        #/bin/bash
        # using a function located in the middle of the script

        echo "This line comes before the function definition"
        function func1 {
                echo "This is an example of function"
        }

        count=1
        while [ $count -le 5 ]
        do
                func1
                count=$[ $count + 1 ]
        done

        echo "This is the end of the loop"
        func2
        echo "Now this is the end of the script"

        function func2 {
                echo "This is an example of a function"
        }

    运行:
        [devalone@devalone 17]$ test2.sh
        This line comes before the function definition
        This is an example of function
        This is an example of function
        This is an example of function
        This is an example of function
        This is an example of function
        This is the end of the loop
        ./test2.sh:行17: func2: 未找到命令
        Now this is the end of the script

    第一个函数 func1 的定义出现在脚本中的几条语句之后,这当然没任何问题。当 func1 函数在脚本中被使用时,shell 知道去哪里找它。

    然而,脚本试图在 func2 函数被定义之前使用它。由于 func2 函数还没有定义,脚本运行函数调用处时,产生了一条错误消息。

    也必须注意函数名。记住,函数名必须是唯一的,否则也会有问题。如果重定义了函数,新定义会覆盖原来函数的定义,这一切不会产生任何错误消息。

    示例:
        [devalone@devalone 17]$ cat test3.sh
        #/bin/bash
        # test using a duplicate function name

        function func1 {
                echo "This is the first definition of the function name"
        }

        func1

        function func1 {
                echo "This is a repeat fo the same function name"
        }

        func1
        echo "This is the end of the script"

    运行:
        [devalone@devalone 17]$ chmod a+x test3.sh
        [devalone@devalone 17]$ test3.sh
        This is the first definition of the function name
        This is a repeat fo the same function name
        This is the end of the script

    func1 函数最初的定义工作正常,但重新定义该函数后,后续的函数调用都会使用第二个定义。


    1.2 返回值
    -----------------------------------------------------------------------------------------------------------------------------------------
    bash shell 会把函数当作一个小型脚本,运行结束时会返回一个退出状态码。有 3 种不同的方法来为函数生成退出状态码。


    1.2.1 默认退出状态码
    -----------------------------------------------------------------------------------------------------------------------------------------
    默认情况下,函数的退出状态码是函数中最后一条命令返回的退出状态码。在函数执行结束后,可以用标准变量 $? 来确定函数的退出状态码。

    示例:
        [devalone@devalone 17]$ cat test4.sh
        #!/bin/bash
        # testing the exit status of a function

        func1 () {
            echo "tryint to display a non-existent file"
            ls -l badfile
        }

        echo "testing the function"
        func1
        echo "The exit code status is: $?"

    运行:
        [devalone@devalone 17]$ chmod a+x test4.sh
        [devalone@devalone 17]$ test4.sh
        testing the function
        tryint to display a non-existent file
        ls: 无法访问'badfile': No such file or directory
        The exit code status is: 2

    函数的退出状态码是 2,这是因为函数中的最后一条命令没有成功运行。但无法知道函数中其他命令中是否成功运行。

    示例:
        [devalone@devalone 17]$ cat test4b.sh
        #!/bin/bash
        # testing the exit status of a function
        func1() {
            ls -l badfile
            echo "This was a test of a bad command"
        }
        echo "testing the function:"
        func1
        echo "The exit status is: $?"

    运行:
        [devalone@devalone 17]$ chmod a+x test4b.sh
        [devalone@devalone 17]$ test4b.sh
        testing the function:
        ls: 无法访问'badfile': No such file or directory
        This was a test of a bad command
        The exit status is: 0

    这次,由于函数最后一条语句 echo 运行成功,该函数的退出状态码就是 0,尽管其中有一条命令并没有正常运行。使用函数的默认退出状态码是很危险的。
    幸运的是,有几种办法可以解决这个问题。


    1.2.2 使用 return 命令
    -----------------------------------------------------------------------------------------------------------------------------------------
    bash shell 使用 return 命令来退出函数并返回特定的退出状态码。return 命令允许指定一个整数值来定义函数的退出状态码,从而提供了一种简单的途径
    来编程设定函数退出状态码。

    示例:
        [devalone@devalone 17]$ cat test5.sh
        #!/bin/bash
        # using the return command in a funcion

        function db1 {
            read -p "Enter a value: " value
            echo "doubling the value"
            return $[ $value * 2 ]
        }

        db1
        echo "The new value is $?"

    dbl函数会将 $value 变量中用户输入的值翻倍,然后用 return 命令返回结果。脚本用 $? 变量显示了该值。

    但当用这种方法从函数中返回值时,要小心了。记住下面两条技巧来避免问题:

        □ 记住,函数一结束就取返回值;
        □ 记住,退出状态码必须是 0~255。

    如果在用 $? 变量提取函数返回值之前执行了其他命令,函数的返回值就会丢失。记住,$? 变量会返回执行的最后一条命令的退出状态码。

    第二个问题界定了返回值的取值范围。由于退出状态码必须小于 256,函数的结果必须生成一个小于 256 的整数值。任何大于256的值都会产生一个错误值。

    运行:
        [devalone@devalone 17]$ test5.sh
        Enter a value: 5
        doubling the value
        The new value is 10
        
        [devalone@devalone 17]$ test5.sh
        Enter a value: 200
        doubling the value
        The new value is 144


    1.2.3 使用函数输出
    -----------------------------------------------------------------------------------------------------------------------------------------
    正如可以将命令的输出保存到 shell 变量中一样,也可以对函数的输出采用同样的处理办法。可以用这种技术来获得任何类型的函数输出,并将其保存到
    变量中:

        result=`dbl`

    这个命令会将 dbl 函数的输出赋给 $result 变量。

    示例:

        [devalone@devalone 17]$ cat test5b.sh
        #!/bin/bash
        # using the echo to return a value

        function db1 {
            read -p "Enter a value: " value
            echo $[ $value * 2 ]
        }

        result=$(db1)
        echo "The new value is $result"

    运行:
        [devalone@devalone 17]$ chmod a+x test5b.sh
        [devalone@devalone 17]$ test5b.sh
        Enter a value: 200
        The new value is 400

    新函数用 echo 语句来显示计算的结果。该脚本会获取 dbl 函数的输出,而不是查看退出状态码。

    这个例子中演示了一个不易察觉的技巧。dbl 函数实际上输出了两条消息。read 命令输出了一条简短的消息来向用户询问输入值。bash shell脚本非常聪明,
    并不将其作为 STDOUT 输出的一部分,并且忽略掉它。如果用 echo 语句生成这条消息来向用户查询,那么它会与输出值一起被读进 shell 变量中。

        NOTE:
        -------------------------------------------------------------------------------------------------------------------------------------
        通过这种技术,还可以返回浮点值和字符串值。这使它成为一种获取函数返回值的强大方法。


    1.3 在函数中使用变量
    -----------------------------------------------------------------------------------------------------------------------------------------
    在函数中使用变量时,需要注意它们的定义方式以及处理方式。这是 shell 脚本中常见错误的根源。


    1.3.1 向函数传递参数
    -----------------------------------------------------------------------------------------------------------------------------------------
    bash shell 会将函数当作小型脚本来对待。这意味着可以像普通脚本那样向函数传递参数。

    函数可以使用标准的参数环境变量来表示命令行上传给函数的参数。例如,函数名会在 $0 变量中定义,函数命令行上的任何参数都会通过 $1、$2 等定义。
    也可以用特殊变量 $# 来判断传给函数的参数数目。

    在脚本中指定函数时,必须将参数和函数放在同一行,像这样:

        func1 $value1 10

    然后函数可以用参数环境变量来获得参数值。

    示例:
        [devalone@devalone 17]$ cat test6.sh
        #!/bin/bash
        # passing paramters to a function

        function addem {
            if [ $# -eq 0 ] || [ $# -gt 2 ]
            then
                echo -1
            elif [ $# -eq 1 ]
            then
                echo $[ $1 + $1 ]
            else
                echo $[ $1 + $2 ]
            fi
        }

        echo -n "Adding 10 and 15: "
        value=$(addem 10 15)
        echo $value
        echo -n "Let's try adding just one number:"
        value=$(addem 10)
        echo $value
        echo -n "Now trying adding no numbers:"
        value=$(addem)
        echo $value
        echo -n "Finially, try adding three numbers:"
        value=$(addem 10 15 20)
        echo $value

    运行:
        [devalone@devalone 17]$ test6.sh
        Adding 10 and 15: 25
        Let's try adding just one number:20
        Now trying adding no numbers:-1
        Finially, try adding three numbers:-1

    text6.sh 脚本中的 addem 函数首先会检查脚本传给它的参数数目。如果没有任何参数,或者参数多于两个,addem 会返回值 -1。如果只有一个参数,
    addem 会将参数与自身相加。如果有两个参数,addem 会将它们进行相加。

    由于函数使用特殊参数环境变量作为自己的参数值,因此它无法直接获取脚本在命令行中的参数值。下面的例子将会运行失败:

        [devalone@devalone 17]$ cat badtest1.sh
        #!/bin/bash
        # trying to access script parameters inside a function

        function badfunc1 {
            echo $[ $1 * $2 ]
        }

        if [ $# -eq 2 ]
        then
            value=$(badfunc1)
            echo "The result is $value"
        else
            echo "Usage: badtest1 a b"
        fi

    运行:
        [devalone@devalone 17]$ chmod a+x badtest1.sh
        [devalone@devalone 17]$ badtest1.sh
        Usage: badtest1 a b
        [devalone@devalone 17]$ badtest1.sh 10 15
        ./badtest1.sh:行5: *  : 语法错误: 需要操作数 (错误符号是 "*  ")
        The result is

    尽管函数也使用了 $1 和 $2 变量,但它们和脚本主体中的$1和$2变量并不相同。要在函数中使用这些值,必须在调用函数时手动将它们传过去。

    示例:
        [devalone@devalone 17]$ cat test7.sh
        #!/bin/bash
        # trying to access script parameters inside a function

        function func7 {
            echo $[ $1 * $2 ]
        }

        if [ $# -eq 2 ]
        then
            value=$(func7 $1 $2)
            echo "The result is $value"
        else
            echo "Usage: test7 a b"
        fi

    运行:
        [devalone@devalone 17]$ chmod a+x test7.sh
        [devalone@devalone 17]$ test7.sh
        Usage: test7 a b
        [devalone@devalone 17]$ test7.sh 10 15
        The result is 150

    通过将 $1 和 $2 变量传给函数,它们就能跟其他变量一样供函数使用了。


    1.3.2 在函数中处理变量
    -----------------------------------------------------------------------------------------------------------------------------------------
    给 shell 脚本程序员带来麻烦的原因之一就是变量的作用域。作用域是变量可见的区域。函数中定义的变量与普通变量的作用域不同。也就是说,对脚本的
    其他部分而言,它们是隐藏的。

    函数使用两种类型的变量:

        □ 全局变量
        □ 局部变量

    ■ 全局变量
    -----------------------------------------------------------------------------------------------------------------------------------------
    全局变量是在 shell 脚本中任何地方都有效的变量。如果在脚本的主体部分定义了一个全局变量,那么可以在函数内读取它的值。类似地,如果在函数内定
    义了一个全局变量,可以在脚本的主体部分读取它的值。

    默认情况下,在脚本中定义的任何变量都是全局变量。在函数外定义的变量可在函数内正常访问。

    示例:
        [devalone@devalone 17]$ cat test8.sh
        #!/bin/bash
        # using a global variable to pass a value

        function db1 {
            value=$[ $value * 2 ]
        }

        read -p "Enter a value: " value
        db1
        echo "the new value is: $value"

    运行:    
        [devalone@devalone 17]$ chmod a+x test8.sh
        [devalone@devalone 17]$ test8.sh
        Enter a value: 400
        the new value is: 800

    $value 变量在函数外定义并被赋值。当 dbl 函数被调用时,该变量及其值在函数中都依然有效。如果变量在函数内被赋予了新值,那么在脚本中引用该
    变量时,新值也依然有效。

    但这其实很危险,尤其是如果想在不同的 shell 脚本中使用函数的话。它要求清清楚楚地知道函数中具体使用了哪些变量,包括那些用来计算非返回值的
    变量。下面的例子可说明事情是如何搞砸的:

        [devalone@devalone 17]$ cat badtest2.sh
        #!/bin/bash
        # demonstrating a bad use of variables

        function func1 {
            temp=$[ $value + 5 ]
            result=$[ $temp * 2 ]
        }

        temp=4
        value=6

        func1

        echo "The result is $result"
        if [ $temp -gt $value ]
        then
            echo "temp is larger"
        else
            echo "temp is smaller"
        fi

    运行:
        [devalone@devalone 17]$ chmod a+x badtest2.sh
        [devalone@devalone 17]$ badtest2.sh
        The result is 22
        temp is larger

    由于函数中用到了 $temp变量,它的值在脚本中使用时受到了影响,产生了意想不到的后果。


    ■ 局部变量
    -----------------------------------------------------------------------------------------------------------------------------------------
    无需在函数中使用全局变量,函数内部使用的任何变量都可以被声明成局部变量。要实现这一点,只要在变量声明的前面加上 local 关键字就可以了。

        local temp

    也可以在变量赋值语句中使用 local 关键字:

        local temp=$[ $value + 5 ]

    local 关键字保证了变量只局限在该函数中。如果脚本中在该函数之外有同样名字的变量,那么 shell 将会保持这两个变量的值是分离的。现在就能很轻松
    地将函数变量和脚本变量隔离开了,只共享需要共享的变量。

    示例:
        [devalone@devalone 17]$ cat test9.sh
        #!/bin/bash
        # demonstrating the local keyword

        function func1 {
            local temp=$[ $value + 5 ]
            result=$[ $temp * 2 ]
        }

        temp=4
        value=6

        func1
        echo "The result is $result"
        if [ $temp -gt $value ]
        then
            echo "temp is larger"
        else
            echo "temp is smaller"
        fi

    运行:
        [devalone@devalone 17]$ chmod a+x test9.sh
        [devalone@devalone 17]$ test9.sh
        The result is 22
        temp is smaller

    现在,在 func1 函数中使用 $temp 变量时,并不会影响在脚本主体中赋给 $temp 变量的值。


    1.4 数组变量和函数
    -----------------------------------------------------------------------------------------------------------------------------------------
    在函数中使用数组变量值有点麻烦,而且还需要一些特殊考虑。


    1.4.1 向函数传数组参数
    -----------------------------------------------------------------------------------------------------------------------------------------
    向脚本函数传递数组变量的方法会有点不好理解。将数组变量当作单个参数传递的话,它不会起作用。

    示例:

        [devalone@devalone 17]$ cat badtest3.sh
        #!/bin/bash
        # trying to pass an array variable

        function testit {
                echo "The parameters are: $@"
                thisarray=$1
                echo "The received array is ${thisarray[*]}"
        }

        myarray=(1 2 3 4 5)
        echo "The original array is: ${myarray[*]}"
        testit $myarray

    运行:
        [devalone@devalone 17]$ chmod a+x badtest3.sh
        [devalone@devalone 17]$ badtest3.sh
        The original array is: 1 2 3 4 5
        The parameters are: 1
        The received array is 1

    如果试图将该数组变量作为函数参数,函数只会取数组变量的第一个值。

    要解决这个问题,必须将该数组变量的值分解成单个的值,然后将这些值作为函数参数使用。在函数内部,可以将所有的参数重新组合成一个新的变量。

    示例:
        [devalone@devalone 17]$ cat test10.sh
        #!/bin/bash
        # array variable to function test

        function testit {
            local newarray
            newarray=(`echo "$@"`)
            echo "The new array value is: ${newarray[*]}"
        }

        myarray=(1 2 3 4 5)
        echo "The original array is ${myarray[*]}"
        testit ${myarray[*]}

    运行:
        [devalone@devalone 17]$ chmod a+x test10.sh
        [devalone@devalone 17]$ test10.sh
        The original array is 1 2 3 4 5
        The new array value is: 1 2 3 4 5

    该脚本用 $myarray 变量来保存所有的数组元素,然后将它们都放在函数的命令行上。该函数随后从命令行参数中重建数组变量。在函数内部,数组仍然
    可以像其他数组一样使用。

    示例:
        [devalone@devalone 17]$ cat test11.sh
        #!/bin/bash
        # adding values in an array

        function addarray {
            local sum=0
            local newarray
            newarray=($(echo "$@"))
            for value in ${newarray[*]}
            do
                    sum=$[ $sum + $value ]
            done

            echo $sum
        }

        myarray=(1 2 3 4 5)
        echo "The original array is ${myarray[*]}"

        arg1=$(echo ${myarray[*]})
        result=$(addarray $arg1)
        echo "The result is $result"

    运行:
        [devalone@devalone 17]$ chmod a+x test11.sh
        [devalone@devalone 17]$ test11.sh
        The original array is 1 2 3 4 5
        The result is 15


    addarray 函数会遍历所有的数组元素,将它们累加在一起。可以在 myarray 数组变量中放置任意多的值,addarry 函数会将它们都加起来。


    1.4.2 从函数返回数组
    -----------------------------------------------------------------------------------------------------------------------------------------
    从函数里向 shell 脚本传回数组变量也用类似的方法。函数用 echo 语句来按正确顺序输出单个数组值,然后脚本再将它们重新放进一个新的数组变量中。

    示例:
        [devalone@devalone 17]$ cat test12.sh
        #!/bin/bash
        # return an array value

        function arraydblr {
            local origarray
            local newarray
            local elements
            local i
            origarray=($(echo "$@"))
            newarray=($(echo "$@"))
            elements=$[ $#-1 ]
            for ((i=0; i<= $elements; i++))
                {
                    newarray[$i]=$[ ${origarray[$i]} * 2 ]
                }
                echo ${newarray[*]}

        }

        myarray=(1 2 3 4 5)
        echo "The original array is: ${myarray[*]}"
        arg1=$(echo ${myarray[*]})
        result=($(arraydblr $arg1))
        echo "The new array is: ${result[*]}"

    运行:
        [devalone@devalone 17]$ chmod a+x test12.sh
        [devalone@devalone 17]$ test12.sh
        The original array is: 1 2 3 4 5
        The new array is: 2 4 6 8 10

    该脚本用 $arg1 变量将数组值传给 arraydblr 函数。arraydblr 函数将该数组重组到新的数组变量中,生成该输出数组变量的一个副本。然后对数据元素
    进行遍历,将每个元素值翻倍,并将结果存入函数中该数组变量的副本。

    arraydblr 函数使用 echo 语句来输出每个数组元素的值。脚本用 arraydblr 函数的输出来重新生成一个新的数组变量。


    1.5 函数递归
    -----------------------------------------------------------------------------------------------------------------------------------------
    局部函数变量的一个特性是自成体系。除了从脚本命令行处获得的变量,自成体系的函数不需要使用任何外部资源。

    这个特性使得函数可以递归地调用,也就是说,函数可以调用自己来得到结果。通常递归函数都有一个最终可以迭代到的基准值。许多高级数学算法用递归
    对复杂的方程进行逐级规约,直到基准值定义的那级。

    递归算法的经典例子是计算阶乘。一个数的阶乘是该数之前的所有数的乘积乘以该数的值。因此,要计算5的阶乘,可以执行如下方程:

        5! = 1 * 2 * 3 * 4 * 5 = 120

    使用递归,方程可以简化成以下形式:
        
        x! = x * (x-1)!
        
    也就是说,x 的阶乘等于 x 乘以 x-1 的阶乘。这可以用简单的递归脚本表达为:

        function factorial {
            if [ $1 -eq 1 ]
            then
                echo 1
            else
                local temp=$[ $1 - 1 ]
                local result='factorial $temp'
                echo $[ $result * $1 ]
            fi
        }    

    阶乘函数用它自己来计算阶乘的值:
        [devalone@devalone 17]$ cat test13.sh
        #!/bin/bash
        # using recursion

        function factorial
        {
                if [ $1 -eq 1 ]
                then
                        echo 1
                else
                        local temp=$[ $1 - 1 ]
                        local result=$(factorial $temp)
                        echo $[ $result * $1 ]
                fi
        }

        read -p "Enter value: " value
        result=$(factorial $value)
        echo "The factorial of $value is: " $result

    运行:
        [devalone@devalone 17]$ chmod a+x test13.sh
        [devalone@devalone 17]$ test13.sh
        Enter value: 5
        The factorial of 5 is:  120


    1.6 创建库
    -----------------------------------------------------------------------------------------------------------------------------------------
    使用函数可以在脚本中省去一些输入工作,这一点是显而易见的。但如果碰巧要在多个脚本中使用同一段代码呢?显然,为了使用一次而在每个脚本中都
    定义同样的函数太过麻烦。

    有个方法能解决这个问题!bash shell 允许创建函数库文件,然后在多个脚本中引用该库文件。

    这个过程的第一步是创建一个包含脚本中所需函数的公用库文件。这里有个叫作 myfuncs 的库文件,它定义了 3 个简单的函数。

    示例:
        [devalone@devalone 17]$ cat myfuncs
        # my script functions
        function addem {
                 echo $[ $1 + $2 ]
        }

        function multem {
                echo $[ $1 * $2 ]
        }

        function divem {
                 if [ $2 -ne 0 ]
                 then
                        echo $[ $1 / $2 ]
                 else
                        echo -1
                 fi
        }
        [devalone@devalone 17]$ chmod a+x myfuncs

    下一步是在用到这些函数的脚本文件中包含 myfuncs 库文件。从这里开始,事情就变复杂了。

    问题出在 shell 函数的作用域上。和环境变量一样,shell 函数仅在定义它的 shell 会话内有效。如果在 shell 命令行界面的提示符下运行 myfuncs shell 脚本,shell 会创建一个新的 shell 并在其中运行这个脚本。它会为那个新 shell 定义这三个函数,但当运行另外一个要用到这些函数的脚本时,它们是无法使用的。

    这同样适用于脚本。如果尝试像普通脚本文件那样运行库文件,函数并不会出现在脚本中。

    示例:
        [devalone@devalone 17]$ cat badtest4.sh
        #!/bin/bash
        # using a library file the wrong way

        ./myfuncs

        result=$(addem 10 15)
        echo "The result is $result"

    运行:
        [devalone@devalone 17]$ chmod a+x badtest4.sh
        [devalone@devalone 17]$ badtest4.sh
        ./badtest4.sh:行6: addem: 未找到命令
        The result is

    使用函数库的关键在于 source 命令。source 命令会在当前 shell 上下文中执行命令,而不是创建一个新 shell。可以用 source 命令来在 shell 脚本中运行库文件脚本。这样脚本就可以使用库中的函数了。

    source 命令有个快捷的别名,称作点操作符(dot operator)。要在 shell 脚本中运行 myfuncs 库文件,只需添加下面这行:

        . ./myfuncs

    这个例子假定 myfuncs 库文件和 shell 脚本位于同一目录。如果不是,需要使用相应路径访问该文件。

    示例:
        [devalone@devalone 17]$ cat test14.sh
        #!/bin/bash
        # using functions defined in a library file

        . ./myfuncs

        value1=10
        value2=5

        result1=$(addem $value1 $value2)
        result2=$(multem $value1 $value2)
        result3=$(divem $value1 $value2)

        echo "The result of adding them is: " $result1
        echo "The result of multiplying them is: " $result2
        echo "The result of dividing them is: " $result3

    运行:
        [devalone@devalone 17]$ chmod a+x test14.sh
        [devalone@devalone 17]$ test14.sh
        The result of adding them is:  15
        The result of multiplying them is:  50
        The result of dividing them is:  2

    该脚本成功地使用了 myfuncs 库文件中定义的函数。


    1.7 在命令行上使用函数
    -----------------------------------------------------------------------------------------------------------------------------------------
    可以用脚本函数来执行一些十分复杂的操作。有时也很有必要在命令行界面的提示符下直接使用这些函数。

    和在 shell 脚本中将脚本函数当命令使用一样,在命令行界面中也可以这样做。因为一旦在 shell 中定义了函数,就可以在整个系统中使用它了,无需担心脚本是不是在 PATH 环境变量里。重点在于让 shell 能够识别这些函数。有几种方法可以实现。


    1.7.1 在命令行上创建函数
    -----------------------------------------------------------------------------------------------------------------------------------------
    因为 shell 会解释用户输入的命令,所以可以在命令行上直接定义一个函数。有两种方法。

    一种方法是采用单行方式定义函数:

        [devalone@devalone 17]$ function divem { echo $[ $1 / $2 ]; }
        [devalone@devalone 17]$ divem 100 5
        20
        
    当在命令行上定义函数时,必须记得在每个命令后面加个分号,这样 shell 就能知道在哪里是命令的起止了。

    示例:
        [devalone@devalone 17]$ function doubleit { read -p "Enter value: " value; echo $[ $value * 2 ]; }
        [devalone@devalone 17]$ doubleit
        Enter value: 20
        40

    另一种方法是采用多行方式来定义函数。在定义时,bash shell 会使用次提示符来提示输入更多命令。用这种方法,不用在每条命令的末尾放一个分号,只
    要按下回车键就行。

    示例:
        [devalone@devalone 17]$ function multem {
        > echo $[ $1 * $2 ]
        > }
        [devalone@devalone 17]$ multem 2 5
        10

    在函数的尾部使用花括号,shell 就会知道已经完成了函数的定义。


        NOTE:
        -------------------------------------------------------------------------------------------------------------------------------------
        在命令行上创建函数时要特别小心。如果给函数起了个跟内建命令或另一个命令相同的名字,函数将会覆盖原来的命令。
        
        
    1.7.2 在.bashrc 文件中定义函数
    -----------------------------------------------------------------------------------------------------------------------------------------
    在命令行上直接定义 shell 函数的明显缺点是退出 shell时,函数就消失了。对于复杂的函数来说,这可是个麻烦事。

    个非常简单的方法是将函数定义在一个特定的位置,这个位置在每次启动一个新 shell 的时候,都会由 shell 重新载入。

    最佳地点就是 .bashrc 文件。bash shell 在每次启动时都会在主目录下查找这个文件,不管是交互式 shell 还是从现有 shell 中启动的新 shell。


    ■ 直接定义函数
    -----------------------------------------------------------------------------------------------------------------------------------------
    可以直接在主目录下的 .bashrc 文件中定义函数。许多 Linux 发行版已经在 .bashrc文件中定义了一些内容,所以注意不要误删了。把自己写的函数放在
    文件末尾就行了。

    示例:
        [devalone@devalone 17]$ cat ~/.bashrc
        # .bashrc

        # Source global definitions
        if [ -f /etc/bashrc ]; then
                . /etc/bashrc
        fi

        # Uncomment the following line if you don't like systemctl's auto-paging feature:
        # export SYSTEMD_PAGER=

        # User specific aliases and functions


        function addem {
                echo $[ $1 + $2 ]
        }

    该函数会在下次启动新 bash shell 时生效。随后就能在系统上任意地方使用这个函数了。


    ■ 读取函数文件
    -----------------------------------------------------------------------------------------------------------------------------------------
    只要是在 shell 脚本中,都可以用 source 命令(或者它的别名点操作符)将库文件中的函数添加到的 .bashrc 脚本中。

    示例:
        [devalone@devalone 17]$ cat ~/.bashrc
        # .bashrc

        # Source global definitions
        if [ -f /etc/bashrc ]; then
                . /etc/bashrc
        fi

        # Uncomment the following line if you don't like systemctl's auto-paging feature:
        # export SYSTEMD_PAGER=

        # User specific aliases and functions

        export REPO_URL='https://mirrors.tuna.tsinghua.edu.cn/git/git-repo/'

        . /home/devalone/study/shell-script/17/myfuncs

    运行:
        [devalone@devalone 17]$ bash
        [devalone@devalone 17]$ addem 10 5
        15
        [devalone@devalone 17]$ multem 10 5
        50
        [devalone@devalone 17]$ divem 10 5
        2
        [devalone@devalone 17]$ exit
        exit

    更好的是,shell 还会将定义好的函数传给子shell 进程,这样一来,这些函数就自动能够用于该 shell 会话中的任何 shell 脚本了。
    示例:
        [devalone@devalone 17]$ cat test15.sh
        #!/bin/bash
        # using a function defined in the .bashrc file
        value1=10
        value2=5
        result1=$(addem $value1 $value2)
        result2=$(multem $value1 $value2)
        result3=$(divem $value1 $value2)
        echo "The result of adding them is: $result1"
        echo "The result of multiplying them is: $result2"
        echo "The result of dividing them is: $result3"

    运行:
        $ ./test15
        The result of adding them is: 15
        The result of multiplying them is: 50
        The result of dividing them is: 2

     

    系列目录:

    Linux shell 脚本编程-高级篇 (一)

    Linux shell 脚本编程-高级篇 (二)

    Linux shell 脚本编程-高级篇 (三)

    Linux shell 脚本编程-高级篇 (四)

    Linux shell 脚本编程-高级篇 (五)

    Linux shell 脚本编程-高级篇 (六)

    Linux shell 脚本编程-高级篇 (七)

     

     

    -----------------------------------------------------------------------------------------------------------------------------------------
    参考:

        《Linux 命令行与 shell 脚本编程大全》 第 3 版 —— 2016.8(美)Richard Blum  Cristine Bresnahan

    展开全文
  • linux shell 脚本编程基本语法

    千次阅读 2017-06-06 17:32:03
    linux shell 脚本编程打印ip命令: ifconfig eth0 | grep ‘inet’| awk ‘{print $2}’ 第一行:#!/bin/sh:表示有bin/sh来执行 -a:表示and,类似于C语言中的& -o:表示or,类似C语言中的| linux有很多sheel ...

    linux shell脚本编程基本语法

    Shell script是利用shell的功能所写的一个“程序”,这个程序是使用纯文本文件,将一些Linux Shell的语法与命令(含外部命令)写在里面,搭配正则表达式、管道命令与数据流重定向等功能,以达到我们所想要的处理目的。Shell script提供数组、循环、条件与逻辑判断等重要功能,让用户也可以直接以shell来编写程序,而不必使用类似C程序来完成相关功能。Shell Script就像是早期DOS年代的批处理文件(.bat)。

    编程格式

    在写C程序的时候,代码的开头我们通常会给版本信息。shell脚本也不例外。
    一般声明如下。

    #!/bin/sh
    # Program:
    #   This program shows "Hello World" in your srceen
    # History:
    #   2017-6-5 liwanneng  V1.0.0
    
    echo -e "Hello world"
    
    • 第一行 #!/bin/sh 声明该shell script使用的shell名称,linux默认使用的是bash shell
    • 第二行Program后面是程序内容的说明,这个shellscript中,除了第一行外,其他#符号表示注释
    • 第四行History后面是时间,作者以及版本信息
    • 从echo开始,后面表示程序正文

    shell script中常见语法

    从屏幕中获取输入

    在shell script中,使用read -p 来获取标准输入
    例如

    #!/bin/sh
    # Program:
    #   This program User input his first name and last name, Program shows his full name
    # History:
    #   2017-6-5 liwanneng  V1.0.0
    read -p "Please input your first name:" firstname #提示用户输入,末尾的first为变量名
    read -p "Please input your last name:" lastname 
    echo -e "/nYour full name is :$firstname $lastname" #结果由屏幕输出

    其输出结果如图
    这里写图片描述

    数值运算,简单的加减乘除

    在shell script脚本中,可以使用$( (计算式) )来进行整数数值运算

    #!/bin/sh
    # Program:
    #   This program 
    # History:
    #   2017-6-5 liwanneng  V1.0.0
    
    echo -e "Please Input TWO numbers\n"
    read -p "the first number is:" num1#提示用户输入,末尾的num1为变量名
    read -p "the second number is:" num2
    product=$(($num1*$num2)) 
    sum=$(($num1+$num2)) 
    red=$(($num1-$num2)) 
    div=$(($num1/$num2)) 
    echo -e "$num1 x $num2 = $product"
    echo -e "$num1 + $num2 = $sum"
    echo -e "$num1 - $num2 = $red"
    echo -e "$num1 ÷ $num2 = $div"

    结果:
    这里写图片描述

    shell script中的默认变量($0, $1…)

    在bashshell中,$0表示脚本程序本身,$1表示脚本程序后面跟的第一个参数,以此类推,$2表示脚本程序后面跟的第二个参数。除了这些数字变量之外,还有一些较为特殊的变量可以在script中使用来调用这些参数。
    * $#:表示脚本程序后面跟的参数个数
    * \$@:表示 “\$1”, “\$2”, “\$3”之意,每个变量是独立的。
    * \$*:表示”\$1c\$2c\$3”,其中c为分隔字符,默认为空格键。
    来看个例子

    #!/bin/sh
    # Program
    #   Program shows script name, paramters...
    # History
    #   2017-6-4
    echo -e "The script name is $0"
    echo -e "Total parameter numher is $#"
    [ "$#" -lt 2 ] && echo -e "The number of paramter is less than 2. Stop here.    " && exit 0
    echo -e "Your whole paramter is $@"
    echo -e "Your first paramter is $1"
    echo -e "Your second paramter is $2"
    

    执行结果
    这里写图片描述

    shell script中的判断

    test命令的测试

    在linux中,可以使用test命令来测试系统上面某些文件的相关属性。例如,我要检查sh01.sh是否存在使用:test -e ./sh01.sh
    单独执行该命令不会显示任何结果,我们可以在后面加上&&以及||来显示结果,如图:
    这里写图片描述
    其中 test命令后面跟的 -e表示:判断文件是否存在。除了-e外,test还可以跟其他许多的参数来获取文件或变量的相关属性如

    • 关于某个文件名的文件类型判断,如 test -e filename
      -e:该文件名是否存在
      -f:该文件名是否存在且为文件
      -d:该文件名是否存在且为目录
      -b:该文件名是否存在且为块设备文件
      -c:该文件名是否存在且为字符设备文件
      -S:该文件名是否存在且为socket文件(大写S)
      -p:该文件名是否存在且为FIFO文件
      -L:该文件名是否存在且为链接文件
    • 关于文件的权限检测,如test -r filename
      -r:该文件名是否存在且具有“可读”权限
      -w:该文件名是否存在且具有“可写”权限
      -x:该文件名是否存在且具有“可执行”权限
      -u:该文件名是否存在且具有“SUID”属性
      -g:该文件名是否存在且具有“SGID”属性
      -k:该文件名是否存在且具有“sticky bit”属性
      -s:该文件名是否存在且为“非空白文件”(小写s)
    • 两个文件之间的比较,如:test file1 -nt file2
      -nt:newer than,判断file1是否比file2新
      -ot:older than,判断file1是否比file2旧
      -ef:判断file1与file2是否为同一文件,可用在判断hard link的判定上。主要意义在于判定两个文件是否指向统一个inode
    • 关于两个整数之间的判定,如test num1 eq num2
      -eq:equal,num1等于num2
      -ne:not equal,num1不等于num2
      -gt:greater than,num1大于num2
      -lt:less than,num1小于num2
      -ge:great equal,num1大于等于num2
      -le:less equal,num1小于等于num2
    • 判定字符串的数据
      test -z string:判定字符串是否为0,若string为空字符串,则为true
      test -n string:判定字符串是否为非零,若string为空字符串,则为false
    • 多重条件判定,例如:test -r filename -a -x filename
      -a:两个条件同时成立!例如 test -r file -a -xfile,则file同时具有 r 与 x 权限时,才回传true
      -o:任何一个条件成立!例如 test -r file -o -x file,则file具有 r 或者 x 权限时,才回传true
      !:反向状态!如test !-x file,当file不具有x时,回传true。

    利用判断符号[]

    除了利用test命令判断外,我们可以利用判断符号“[]”(英文输入状态下的中括号)来进行数据的判断。这个符号在shell script中经常与if条件判断搭配使用。
    在bash的语法中使用“[]”做判断必须要注意的几点
    1. 中括号两端需要空格符来分隔
    2. 在中括号[]内每个组件都需要有空格来分隔
    3. 中括号内的变量和常量最好都用双引号括起来

    假如我空格号使用“口”符号来表示,那么在这些地方需要有空格。

    口[口"$HOME"口==口"$MAIL"口]口

    上面这句话的意思是变量HOME与变量MAIL是否相同的意思,相当于test$HOME=$MAIL

    条件判断式

    if… then是shell script中常用到的条件判断式。简单来说就是当符合某项条件的时候,就进行某项工作。其格式一般为

    if [ 条件1 ] ; then
       程序段
    #在shell script中用elif,功能类似于C语言中的else if语句
    elif [ 条件2 ]; then
       程序段
    else
       程序段
    fi   #fi表示if的结束

    下面来看一个例子

     #!/bin/sh
     # Program
     #   This program shows user's choice
     # History
     #   2017-6-4 liwanneng V1.0.0
    read -p "Please input Y/N:" yn
    if [ "$yn" == "Y" ] || [ "$yn" == "y" ]; then
          echo "Clever boy! You will Continue"
          exit 0
    elif [ "$yn" == "N" ] || [ "$yn" == "n" ]; then
          echo "You will interrupt the program!!!"
          exit 0
    else
          echo "Please input Y/y or N/n!"
          exit 0
    fi

    运行结果如下:
    这里写图片描述

    利用case…esac判断

    在shell script中除了用if…then语句来判断,我们还可以使用case…esac来表示多重条件判断,类似于C语言中的Case语句。其格式为:

    case $变量名称 in  #关键字为case,还有$符号
       "第一个变量内容") #每个变量内容用括号括起来,关键字则为小括号
          程序段
          ;;  #每个类型的结尾使用英文输入法状态下的两个分号来表示
       "第二个变量内容")
          程序段
          ;;
       *) #最后一个变量使用*来表示其他值
          exit 1
          ;;
    easc  #由esac来表示case语句的最终结尾

    来看一个例子

     #!/bin/sh
       # Program
       #   Program show "hello" from $1...by using case ...esac
       # History
       # 2017-6-4 10:32:33
    
       case $1 in
           "hello")
               echo "Hello,how are you!"
              ;;
          "bye")
              echo "bye-bye!"
              ;;
          "") #如果没有输入
              echo "You must input parameter, ex> {$0 someword}"
              ;;
          *)
              echo "use $0 {hello or bye}"
              ;;
      esac
    

    来看程序执行的结果
    这里写图片描述

    whlie do done,until do done 不定循环

    在C语言中,经常用到whle循环和for循环来表示程序的反复执行。在shell script中同样有while循环和for循环,只是shell script 中的while和for 的用法与C语言中稍有不同。

    while do done不定循环

    在shell script中while…done不定循环的常见格式如下

    while [ condition ] #中括号内的状态就是判断式   
    do #do是循环的开始
    done  #done是循环的结束

    while的在英语中的意思是“当…时”,所以while…done这种循环表示当condition条件成立时就进行循环,直到condition条件不成立时才停止循环。
    来看一个例子

      #!/bin/sh
       # Program
       #   Repet quesstion until input correct answer.
       # History
       #   2017-6-6 liwanneng V1.0.0
    
      while [ "$yn" != "yes" -a "$yn" != "YES" ]
      do
           read -p "Please input yes/YES to stop this program:" yn
      done
      echo -e "OK,you input the correct answer."

    上面这个程序为:当满足条件 “$yn” != “yes” -a “$yn” != “YES” 即输入不为yes/YES时,程序一直循环,调用read语句一直等待用户输入。当不满足条件 “$yn” != “yes” -a “$yn” != “YES” 即输入为yes/YES时,程序退出循环,调用echo语句。程序执行结果如图。
    这里写图片描述

    until do done不定循环

    在shell script中还有一种不定循环 until do done循环,这种循环恰恰与while相反,它是当条件成立时才进行循环,条件不成立时,程序退出循环。其一般格式如下

    ubtil [ condition ]
    do
        程序段
    done 

    同样来看一个例子

    #!/bin/sh
    # Program
    #   Repeat question ubtil user input correct answer
    # History
    #   2017-6-6 liwanneng V1.0.0
    
    until [ "$yn" == "yes" -o "$yn" == "YES" ]
    do
      read -p "Please input yes/YES to stop the program:" yn
    done
    echo -e "Ok,you input correct answer."

    程序执行结果和上面有while循环的程序执行结果一样。但是程序逻辑却有所区别。当不满足条件”$yn” == “yes” -o “$yn” == “YES” 即输入不为yes或YES的时候,程序一直循环,调用read语句等待用户输入。直到满足条件 “$yn” == “yes” -o “$yn” == “YES” 即输入为yes 或 YES的时候,程序退出循环,执行echo语句。
    这里写图片描述

    for … do … done固定循环以及数值处理

    相对于while,until的循环方式是必须要“符合某个条件”,for循环则是“已经知道要循环几次”的状态。它的语法是

    for var in con1 con2 con3 #var为变量,con1,con2,con3分别为条件1,条件2,条件3
    do
        程序段
    done

    上面这段例子的意思是,当变量var1.var的内容为con1
    2. 第二次循环时,varcon23.var的内容为con3

    来看一个具体的例子,加入有三种动物分别是cat,dog,bird,每一行都输出一种动物,程序可以写为:

    #!/bin/sh
    # Program 
    #   Using for ...loop to print 3 animal
    # History
    #   2017-6-6 liwanneng V1.0.0
    for animal in dog cat bird
    do
        echo "There is ${animal}..."
    done

    程序执行结果如下
    这里写图片描述

    for… do… done的数值处理

    除了上面的方法外,for循环还有另外一种写法,也就是C语言中for循环的用法,语句如下:

    for ( ( 初始值;限定值;执行步长) )
    do
        程序段
    dne

    for后面三串内容的意义:
    1. 初始值:某个变量在循环中的初始值,比如常见的 i=1;
    2. 限定值:当变量在限定值范围之内,程序一直循环直到超出限定值,如 i<=100;
    3. 执行步长:没做一次循环的变化量,如i = i+1;
    来看一个具体的例子,输入一个数字num,程序做1+2+3…+num运算

    #!/bin/sh
    # Program
    #   Caculate 1+2+3+...$(input)
    # HIstory
    #   2017-6-6 liwanneng V1.0.0
    read -p "Please input a num:" num
    for(( i=1; i<$num; i=i+1 ))
    do
        sum=$(($sum+$i))
    done
    echo "The result of 1+2+3+...$num is:$sum" 

    来看程序执行结果
    这里写图片描述

    function函数功能

    在C语言中我们经常自己封装函数来表示执行同样功能的一段代码,在shell script中同样有function功能。其一般格式为

    function fname(){
     程序段
    }

    fname为封装函数的名字,需要注意的是在shell script中不能像C语言一样现在调用函数之前声明,然后可在调用函数之后实现其内容,而只能在调用fname之前就要实现其内容。话不多说,来看例子

    #!/bin/sh
    # Program
    # Use function to repeat information
    # History
    # 2017-6-6 liwanneng V1.0.0
    function printit(){
        echo -n "Your choice is :" #加上-n可以不断行继续在同一行显示
    }
    
    echo "This program will print your selection."
    case $1 in
    "one")
         printit;echo $1 | tr 'a-z' 'A-Z'#将小写转换成大写
         ;;
     "two")
         printit;echo $1 | tr 'a-z' 'A-Z'
         ;;
     "three")
         printit;echo $1 | tr 'a-z' 'A-Z'
         ;;
     *)
         echo "Usage $0 {one|two|three}"
         ;;
      esac
    

    程序执行结果:
    这里写图片描述

    shell script的追踪与调试

    是程序就一定有bug,如果程序出了错,在C语言中我们又自己的调试方法。在shell script中有没有办法调试程序呢,答案是有的。我们可以直接以bash的相关参数来进行判断。sh -n/v/x script.sh

    • -n:不要执行script,仅查询语法是否正确
    • -v:执行script之前,先将script的内容输出到屏幕
    • -x:将使用到的script内容显示到屏幕上(经常用到)

    懂了以上这些shell脚本编程语法,一般的shell脚本程序看懂应该没问题了。但是要想自己会写shell脚本程序,还得多加练习!多加练习!多加练习!

    展开全文
  • linux下system函数详解

    千次阅读 2018-10-10 14:54:33
    一、system函数的简单介绍 头文件 #include &lt;stdlib.h&gt; 函数定义 int system(const char * string); 函数说明 system()会调用fork()产生子进程,由子进程来调用/bin/sh-c string来执行参数...

    一、system函数的简单介绍

    头文件 
     #include <stdlib.h>
    
    函数定义
     int system(const char * string); 
    

    函数说明

    system()会调用fork()产生子进程,由子进程来调用/bin/sh-c string来执行参数string字符串所代表的命令。此命
    令执行完后随即返回原调用的进程。在调用system()期间SIGCHLD 信号会被暂时搁置,SIGINT和SIGQUIT 信
    号则会被忽略。

    二、system函数的返回值

    status = system("./test.sh")

    1、先统一两个说法:
    (1)system返回值:指调用system函数后的返回值,比如上例中status为system返回值
    (2)shell返回值:指system所调用的shell命令的返回值,比如上例中,test.sh中返回的值为shell返回值。

    2、man中对于system的说明

       RETURN VALUE       
       The value returned is -1 on error (e.g.  fork() failed), and the return    status  of  the command otherwise. 
       This latter return status is in the       format specified in wait(2).  Thus, the exit code of the  command 
       will   be  WEXITSTATUS(status).   In  case  /bin/sh could not be executed, the exit status will be that of a
       command that does exit(127).
    

    system函数对返回值的处理,涉及3个阶段:
    阶段1:创建子进程等准备工作。如果失败,返回-1。
    阶段2:调用/bin/sh拉起shell脚本,如果拉起失败或者shell未正常执行结束(参见备注1),原因值被写入到status的低8~15比特位中。system的man中只说明了会写了127这个值,但实测发现还会写126等值。
    阶段3:如果shell脚本正常执行结束,将shell返回值填到status的低8~15比特位中。

    只要能够调用到/bin/sh,并且执行shell过程中没有被其他信号异常中断,都算正常结束。比如:不管shell脚本中返回什么原因值,是0还是非0,都算正常执行结束。即使shell脚本不存在或没有执行权限,也都算正常执行结束。如果shell脚本执行过程中被强制kill掉等情况则算异常结束。如何判断阶段2中,shell脚本是否正常执行结束呢?系统提供了宏:WIFEXITED(status)。如果WIFEXITED(status)为真,则说明正常结束。如何取得阶段3中的shell返回值?你可以直接通过右移8bit来实现,但安全的做法是使用系统提供的宏:WEXITSTATUS(status)。

    由于我们一般在shell脚本中会通过返回值判断本脚本是否正常执行,如果成功返回0,失败返回正数。所以综上,判断一个system函数调用shell脚本是否正常结束的方法应该是如下3个条件同时成立:
    (1)-1 != status
    (2)WIFEXITED(status)为真
    (3)0 == WEXITSTATUS(status)

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/wait.h>
    #include <sys/types.h>
     int main()
     {   
     	 pid_t status;    
       	 status = system("./test.sh"); 
         if (-1 == status) 
         {     
         	 printf("system error!");  
         }   
         else 
         {    
               printf("exit status value = [0x%x]\n", status);    
                if (WIFEXITED(status))     
                 {        
                        if (0 == WEXITSTATUS(status))    
                         {              
                               printf("run shell script successfully.\n");        
                         }          
                          else        
                           {             
                               printf("run shell script fail, script exit code: %d\n", WEXITSTATUS(status));      
                           }      
                   }      
                   else  
                    {    
                            printf("exit status = [%d]\n", WEXITSTATUS(status));   
                      }   
             }    
            return 0;
      }
    

    为了更好的理解system()函数返回值,需要了解其执行过程,实际上system()函数执行了三步操作:
    1.fork一个子进程;
    2.在子进程中调用exec函数去执行command;
    3.在父进程中调用wait去等待子进程结束。

    对于fork失败,system()函数返回-1。 如果exec执行成功,也即command顺利执行完毕,则返回command通过exit或return返回的值。 (注意,command顺利执行不代表执行成功,比如command:“rm debuglog.txt”,不管文件存不存在该command都顺利执行了) 如果exec执行失败,也即command没有顺利执行,比如被信号中断,或者command命令根本不存在,system()函数返回127. 如果command为NULL,则system()函数返回非0值,一般为1.

    看完这些,我想肯定有人对system()函数返回值还是不清楚,看源码最清楚,下面给出一个system()函数的实现:

    int system(const char * cmdstring) 
    { 
    	pid_t pid;  
        int status; 
        if(cmdstring == NULL) 
        { 
           return (1); //如果cmdstring为空,返回非零值,一般为1 
        } 
        if((pid = fork())<0) 
        { 
           status = -1; //fork失败,返回-1 
        } else if(pid == 0)     
        { 
    		execl("/bin/sh", "sh", "-c", cmdstring, (char *)0); 
            _exit(127); // exec执行失败返回127,注意exec只在失败时才返回现在的进程,成功的话现在的进程就不存在啦~~
        } 
        else //父进程 
        {
            while(waitpid(pid, &status, 0) < 0) 
    	   { 
    			if(errno != EINTR) 
    		    { 
    				 status = -1; //如果waitpid被信号中断,则返回-1 
    				 break; 
    		    } 
    	   } 
         } 
       return status; //如果waitpid成功,则返回子进程的返回状态 
     } 
    

    三、调用system函数的问题

    先看一下问题 简单封装了一下system()函数:

    int pox_system(const char *cmd_line) 
    { 
    	return system(cmd_line); 
    } 
    

    函数调用:

    int ret = 0; 
    ret = pox_system("gzip -c /var/opt/I00005.xml > /var/opt/I00005.z"); 
    if(0 != ret) 
    { 
     	Log("zip file failed\n"); 
    }
    

    问题现象:每次执行到此处,都会zip failed。而单独把该命令拿出来在shell里执行却总是对的,事实上该段代码已运行了很长时间,从没出过问题。
    分析log时,我们只能看到“zip file failed”这个我们自定义的信息,至于为什么fail,毫无线索。 那好,我们先试着找出更多的线索:

     int ret = 0; 
     ret = pox_system("gzip -c /var/opt/I00005.xml > /var/opt/I00005.z"); 
     if(0 != ret) 
     { 
     	Log("zip file failed: %s\n", strerror(errno)); //尝试打印出系统错误信息 
     } 
    

    我们增加了log,通过system()函数设置的errno,我们得到一个非常有用的线索:system()函数失败是由于“ No child processes”。
    我们通过上面的线索,知道system()函数设置了errno为ECHILD,然而从system()函数的man手册里我们找不到任何有关EHILD的信息。我们知道system()函数执行过程为:fork()->exec()->waitpid(). 很显然waitpid()有重大嫌疑,我们去查一下man手册,看该函数有没有可能设置

    ECHILD: ECHILD (for waitpid() or waitid()) The process specified by pid (waitpid()) or idtype and id (waitid()) 
    does not exist or is not a child of the calling process. (This can happen for one's own child if the action for 
    SIGCHLD is set to SIG_IGN. See also the Linux Notes section about threads.) 
    

    果然有料,如果SIGCHLD信号行为被设置为SIG_IGN时,waitpid()函数有可能因为找不到子进程而报ECHILD错误。似乎我们找到了问题的解决方案:在调用system()函数前重新设置SIGCHLD信号为缺省值,即signal(SIGCHLD, SIG_DFL)。

    system()函数之前没出错,是因为systeme()函数依赖了系统的一个特性,那就是内核初始化进程时对SIGCHLD信号的处理方式为SIG_DFL,这是什么什么意思呢?即内核发现进程的子进程终止后给进程发送一个SIGCHLD信号,进程收到该信号后采用SIG_DFL方式处理,那么SIG_DFL又是什么方式呢?SIG_DFL是一个宏,定义了一个信号处理函数指针,事实上该信号处理函数什么也没做。这个特性正是system()函数需要的,system()函数首先fork()一个子进程执行command命令,执行完后system()函数会使用waitpid()函数对子进程进行收尸。 通过上面的分析,我们可以清醒的得知,system()执行前,SIGCHLD信号的处理方式肯定变了,不再是SIG_DFL了,至于变成什么暂时不知道,事实上,我们也不需要知道,我们只需要记得使用system()函数前把SIGCHLD信号处理方式显式修改为SIG_DFL方式,同时记录原来的处理方式,使用完system()后再设为原来的处理方式。

    问题分析到这里,解决方法也清晰了,于是我们修改了我们的pox_system()函数:

    typedef void (*sighandler_t)(int); 
    int pox_system(const char *cmd_line) 
    { 
     int ret = 0;     
     sighandler_t old_handler;     
     old_handler = signal(SIGCHLD, SIG_DFL);     
     ret = system(cmd_line);     
     signal(SIGCHLD, old_handler);     
     return ret;     
    } 
    

    四、了解popen函数

    标准I/O函数库提供了popen函数,它启动另外一个进程去执行一个shell命令行。
    这里我们称调用popen的进程为父进程,由popen启动的进程称为子进程。
    popen函数还创建一个管道用于父子进程间通信。父进程要么从管道读信息,要么向管道写信息,至于是读还是写取决于父进程调用popen时传递的参数。下在给出popen、pclose的定义。

    #include <stdio.h>
    /*
    函数功能:popen()会调用fork()产生子进程,然后从子进程中调用/bin/sh -c来执行参数command的指令。
            参数type可使用“r”代表读取,“w”代表写入。
            依照此type值,popen()会建立管道连到子进程的标准输出设备或标准输入设备,然后返回一个文件指针。
            随后进程便可利用此文件指针来读取子进程的输出设备或是写入到子进程的标准输入设备中
    返回值:若成功则返回文件指针,否则返回NULL,错误原因存于errno中
    */
    FILE * popen( const char * command,const char * type);
    
    /*
    函数功能:pclose()用来关闭由popen所建立的管道及文件指针。参数stream为先前由popen()所返回的文件指针
    返回值:若成功返回shell的终止状态(也即子进程的终止状态),若出错返回-1,错误原因存于errno中
    */
    int pclose(FILE * stream);
    

    下面通过例子看下popen的使用:

    假如我们想取得当前目录下的文件个数,在shell下我们可以使用:

    ls | wc -l
    

    我们可以在程序中这样写:

    /*取得当前目录下的文件个数*/
    #include <stdio.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <sys/wait.h>
    
    #define MAXLINE 1024
    
    int main()
    {
    	char result_buf[MAXLINE], command[MAXLINE];
    	int rc = 0; // 用于接收命令返回值
    	FILE *fp;
    
    	/*将要执行的命令写入buf*/
    	snprintf(command, sizeof(command), "ls ./ | wc -l");
    
    	/*执行预先设定的命令,并读出该命令的标准输出*/
    	fp = popen(command, "r");
    	if(NULL == fp)
    	{
    		perror("popen执行失败!");
    		exit(1);
    	}
    	while(fgets(result_buf, sizeof(result_buf), fp) != NULL)
    	{
    		/*为了下面输出好看些,把命令返回的换行符去掉*/
    		if('\n' == result_buf[strlen(result_buf)-1])
    		{
    			result_buf[strlen(result_buf)-1] = '\0';
    		}
    		printf("命令【%s】 输出【%s】\r\n", command, result_buf);
    	}
    
    	/*等待命令执行完毕并关闭管道及文件指针*/
    	rc = pclose(fp);
    	if(-1 == rc)
    	{
    		perror("关闭文件指针失败");
    		exit(1);
    	}
    	else
    	{
    		printf("命令【%s】子进程结束状态【%d】命令返回值【%d】\r\n", command, rc, WEXITSTATUS(rc));
    	}
    
    	return 0;
    }
    

    编译并执行:
    $ gcc popen.c

    $ ./a.out

    命令【ls ./ | wc -l】 输出【2】

    命令【ls ./ | wc -l】子进程结束状态【0】命令返回值【0】

    上面popen只捕获了command的标准输出,如果command执行失败,子进程会把错误信息打印到标准错误输出,父进程就无法获取。比如,command命令为“ls nofile.txt” ,事实上我们根本没有nofile.txt这个文件,这时shell会输出“ls: nofile.txt: No such file or directory”。这个输出是在标准错误输出上的。通过上面的程序并无法获取。

    注:如果你把上面程序中的command设成“ls nofile.txt”,编译执行程序你会看到如下结果:

    $ gcc popen.c

    $ ./a.out

    ls: nofile.txt: No such file or directory

    命令【ls nofile.txt】子进程结束状态【256】命令返回值【1】

    需要注意的是第一行输出并不是父进程的输出,而是子进程的标准错误输出。

    有时子进程的错误信息是很有用的,那么父进程怎么才能获取子进程的错误信息呢?

    这里我们可以重定向子进程的错误输出,让错误输出重定向到标准输出(2>&1),这样父进程就可以捕获子进程的错误信息了。例如command为“ls nofile.txt 2>&1”,输出如下:

    命令【ls nofile.txt 2>&1】 输出【ls: nofile.txt: No such file or directory】

    命令【ls nofile.txt 2>&1】子进程结束状态【256】命令返回值【1】

    五、用来popen函数代替system函数

    int my_system(const char * cmd) 
    { 
        FILE * fp;     
        int res; char buf[1024]; 
        if (cmd == NULL) 
        { 
           printf("my_system cmd is NULL!\n");
           return -1;
        } 
        if ((fp = popen(cmd, "r") ) == NULL) 
        { 
           perror("popen");
           printf("popen error: %s/n", strerror(errno));
           return -1; 
        } 
       else
       {
           while(fgets(buf, sizeof(buf), fp)) 
          { 
              printf("%s", buf); 
          } 
          if ( (res = pclose(fp)) == -1) 
          { 
             printf("close popen file pointer fp error!\n"); return res;
          } 
          else if (res == 0) 
          {
              return res;
          } 
         else 
         { 
             printf("popen res is :%d\n", res);
             return res; 
         } 
       }
    } 
    

    转载出处:https://blog.csdn.net/dilireba/article/details/78645755

    展开全文
  • 1.如果自己平台安装了scala,好办,在shell脚本直接 scala filename.scala args args 2.如果没装scala 1)检查自己hadoop平台安装的spark环境,cd到路径 2)执行./bin/spark-shell是否启动scala 3)在脚本中...
  • 附录 C 常用的 Linux C 语言函数库本附录列出 Linux 的常见发行版(如 RedHat Linux)中包含的函数库。除特别指出外,这些函数库的使用和发行适用 GPL 条款或 LGPL 条款。需要注意的是,Linux 上的函数库正在以非常...
  • Linux内核链接脚本

    千次阅读 2012-02-25 14:49:08
    1. 什么是链接脚本 链接器主要有两个作用,一是将若干输入文件(.o文件)根据一定规则合并为一个输出文件(例如ELF格式的可执行文件);一是将符号与地址绑定(当然加载器也要完成这一部分工作)。关于
  • C常用的LinuxC语言函数

    千次阅读 2008-01-14 22:05:00
    本附录列出 Linux 的常见发行版(如 RedHat Linux)中包含的函数库。除特别指出外,这些函数库的使用和发行适用 GPL 条款或 LGPL 条款。 需要注意的是,Linux 上的函数库正在以非常快的速度增加和更新...
  • linux shell脚本编程基本语法

    千次阅读 2018-03-29 13:11:54
    文章转自https://blog.csdn.net/Edroid1530/article/details/72885590linux shell脚本编程基本语法Shell script是利用shell的功能所写的一个“程序”,这个程序是使用纯文本文件,将一些Linux Shell的语法与命令(含...
  • 为了成为一个灵活的...有三种有效的方法可以使Linux脚本使用参数。第一种使用定位参数。脚本根据在命令行出现参数的位置调用参数。因为其他两种依赖于定位参数,所以先讨论这个。 Bash变量使用“$0”标示脚本的路径
  • linux中shell脚本入门(变量使用)shell脚本局部变量环境变量总结 shell脚本 shell可以将命令封装在以.sh结尾的文件中,复用性强,在本文中将介绍shell脚本中的变量。在shell脚本中通常具有两种变量:局部变量 以及...
  • 问题:在当前的Shell中,如何调用封装在其他脚本里的函数 解决方式: Code :封装一个Shell函数脚本log.sh中 #!/bin/bash function store_result(){ date | tee -a $1 echo "$2" | tee -a $1 } 在终端使用...
  • 以python程序为例,在linux运行中方式为python xxx.py 假如我们这个程序有一个循环,那当我们python xxx.py程序以后将无法再在这个...这个可以百度搜索一下,我这里的理解是linux底层会将这个目录中的脚本封装为后台
  • Shell 脚本也可以看做是一个一个的函数,将命令进行了封装。 看看自己的例子: outTop.sh 是最顶层的。【负责调用其他的shell 脚本】out1.sh 是第2层。【被outTop.sh调用】outInsert是第2层。【被outTop.sh调用】 ...
  • 这四个函数第一个参数都是可执行程序或者脚本的程序名, execl 、 execv 需要带有完整的路径,第二参数为任意字符,起到占位作用,第三个或者后面的字符为调用者的参数,参数列表最后以 NULL 结尾,而 execlp 、 ...
  • linux内核链接脚本详解

    万次阅读 2013-07-10 23:46:39
    1. 什么是链接脚本 链接器主要有两个作用,一是将若干输入文件(.o文件)根据一定规则合并为一个输出文件(例如ELF格式的可执行文件);一是将符号与地址绑定(当然加载器也要完成这一部分工作)。关于链接器的工作...
  • 这是我前几天在马哥的带领下写的6个脚本,他们串起来就可以实现(一)篇一所有的功能,很方便吧我们以后要通过不断倒腾这个小linux,来由浅入深的学习linux的系统构架和调优,最后实现编译内核,真正意义上打造自己...
  • 文章目录Linux - 如何把一个脚本注册为命令,成为系统的新命令1、创建一个存放命令的目录2、编写一个脚本文件用来响应命令3、注册命令4、执行脚本 Linux - 如何把一个脚本注册为命令,成为系统的新命令 1、创建一个...
  • Linux下拨号采用的是PPP协议,这与windows是一致的.只是windows下采用拨号程序对协议用图形化界面加以封装,因此我们在拨号时不易察觉....在Linux下拨号采用的脚本为ppp-on,ppp-on-dialer,ppp-off在默认安装
  • Linux软件包安装与Shell脚本编程

    千次阅读 2018-05-25 16:46:51
    Linux软件包安装二进制软件包管理:RPM与YUMRPM软件包例子:jdk-7u80-linux-x64.rpm其中包括软件名(jdk),版本号(7u80),适用平台(linux-x64)RPM管理1、卸载# rpm -e jdk注:如果其他软件包有依赖关系,卸载时会...
  • Linux 常用时间统计函数

    千次阅读 2012-07-22 09:46:32
    这样,我们将这段代码封装函数中,就可以在需要测量的代码前后均加上这个函数即可。最后得到的hi和lo值都是两个,除了相减得到间隔值外,还要进行一些处理,在此不表。  不得不提出的是,周期计数方式还有一个...
  • linux中fork函数是很非常重要的函数,它是从已存在进程中创建一个新进程,原进程是父进程,新进程是子进程。 #include&lt;unistd.h&gt; pid_t fork(void);返回值:子进程返回0,父进程返回子进程的id,...
  • mongodb shell 运行js脚本mongodb eval 执行服务器端脚本db.eval 执行服务器端脚本存储javascriptmongodb shell 运行js脚本的四种方式交互式 mongo shellmongo --eval 运行一段脚本在OS命令行下,运行一个js文件在...
  • Linux下拨号采用的是PPP协议,这与windows是一致的. 只是windows下采用拨号程序对协议用图形化界面加以封装,因此我们在拨号时不易察觉. 而在Linux下,采利用的是...在Linux下拨号采用的脚本为ppp-on,ppp-on-dialer,pp
  • 通过在命令行中执行./output 20 30 输出20+30的值; 脚本中使用sum()函数封装代码并通过调用sum函数返回结果 #!/bin/bash sum() { sum=$[$1+$2] echo $sum } result=sum $1 $2 echo $result
  • 1.系统调用:  应用程序和内核间的桥梁,是应用程序访问内核的... API常以c库(libc)的形式提供,c库提供了绝大部分API,每个系统调用在c库中都有对应的封装函数(通常封装函数与系统调用的名称相同)。系统调用与c库函
  • #!/bin/bash declare -a func func[0]="max_days" func[1]="length" func[2]="Complexity" max_days(){ echo "i love you" } length(){ echo "length:2" } Complexity(){ echo "easy" ...exp...
  • 2.调用shell脚本:exec函数,参数编程shell文件,可以添加参数; import java.io.BufferedReader; import java.io.InputStreamReader; public class RunShell { public static void main ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 36,089
精华内容 14,435
关键字:

linux脚本封装函数

linux 订阅