linuxshell_linuxshell- - CSDN
  • Linux下编写shell

    2018-06-17 23:12:48
    vi编辑器是所有linux/UNIX操作系统中的标准编辑器,也是目前linux系统最基本的文本编辑器,主要工作在字符模式下,由于不需要图形界面支持,因此它的效率很高。怎么进入vim呢?在命令行中输入 vim 文件名例如,我要...
    vi编辑器是所有linux/UNIX操作系统中的标准编辑器,也是目前linux系统最基本的文本编辑器,主要工作在字符模式下,
    由于不需要图形界面支持,因此它的效率很高。
    怎么进入vim呢?在命令行中输入 vim 文件名

    例如,我要创建一个helloworld文件就输入

    vim helloworld


    回车后就进入了编辑模式。下图就是编辑模式



    但是现在还不能直接输入,要按一下键盘上的i,注意到左下角的文件名变成了INSERT,这就可以开始,敲代码了,如图



    保存文件,推出编辑

    怎样保存文件退出呢?先按下esc,再按冒号,最后输入wq,回车,如图



    执行

    执行的时候用命令 bash 文件名,在本例中就是
    bash helloworld
    执行结果如图


    如果你想用

    ./helloworld

    这种方式执行,需要先赋予用户可执行权限,如下

    chmod u+x helloworld

    chmod 命令可以修改目录和文件的访问权限,u代表文件的所有者,+代表添加后面的权限,x代表可执行权限 ,如图


    到这里,整个过程就讲解完了。

    展开全文
  • Linux Bash Shell入门教程

    2018-05-24 17:00:08
    BASH 的基本语法最简单的例子 —— Hello World!关于输入、输出和错误输出BASH 中对变量的规定(与 C 语言的异同)BASH 中的基本流程控制语法函数的使用2.1 最简单的例子 —— Hello World!几乎所有的讲解编程的书...

    BASH 的基本语法

    • 最简单的例子 —— Hello World!

    • 关于输入、输出和错误输出

    • BASH 中对变量的规定(与 C 语言的异同)

    • BASH 中的基本流程控制语法

    • 函数的使用

    2.1     最简单的例子 —— Hello World!

    几乎所有的讲解编程的书给读者的第一个例子都是 Hello World 程序,那么我们今天也就从这个例子出发,来逐步了解 BASH。

    用 vi 编辑器编辑一个 hello 文件如下:

    #!/bin/bash 
    # This is a very simple example
    echo Hello World

    这样最简单的一个 BASH 程序就编写完了。这里有几个问题需要说明一下:

    一,第一行的 #! 是什么意思
    二,第一行的 /bin/bash 又是什么意思
    三,第二行是注释吗 
    四,echo 语句
    五,如何执行该程序

    #! 是说明 hello 这个文件的类型的,有点类似于 Windows 系统下用不同文件后缀来表示不同文件类型的意思(但不相同)。Linux 系统根据 "#!" 及该字串后面的信息确定该文件的类型,关于这一问题同学们回去以后可以通过 "man magic"命令 及 /usr/share/magic 文件来了解这方面的更多内容。在 BASH 中 第一行的 "#!" 及后面的 "/bin/bash" 就表明该文件是一个 BASH 程序,需要由 /bin 目录下的 bash 程序来解释执行。BASH 这个程序一般是存放在 /bin 目录下,如果你的 Linux 系统比较特别,bash 也有可能被存放在 /sbin 、/usr/local/bin 、/usr/bin 、/usr/sbin 或 /usr/local/sbin 这样的目录下;如果还找不到,你可以用 "locate bash" "find / -name bash 2> /dev/null" 或 "whereis bash" 这三个命令找出 bash 所在的位置;如果仍然找不到,那你可能需要自己动手安装一个 BASH 软件包了。

    第二行的 "# This is a ..." 就是 BASH 程序的注释,在 BASH 程序中从“#”号(注意:后面紧接着是“!”号的除外)开始到行尾的多有部分均被看作是程序的注释。的三行的 echo 语句的功能是把 echo 后面的字符串输出到标准输出中去。由于 echo 后跟的是 "Hello World" 这个字符串,因此 "Hello World"这个字串就被显示在控制台终端的屏幕上了。需要注意的是 BASH 中的绝大多数语句结尾处都没有分号。

    如何执行该程序呢?有两种方法:一种是显式制定 BASH 去执行:

    $ bash hello 或
    $ sh hello (这里 sh 是指向 bash 的一个链接,“lrwxrwxrwx 1 root root 4 Aug 20 05:41 /bin/sh -> bash”)

    或者可以先将 hello 文件改为可以执行的文件,然后直接运行它,此时由于 hello 文件第一行的 "#! /bin/bash" 的作用,系统会自动用/bin/bash 程序去解释执行 hello 文件的:

    $ chmod u+x hello
    $ ./hello

    此处没有直接 “$ hello”是因为当前目录不是当前用户可执行文件的默认目录,而将当前目录“.”设为默认目录是一个不安全的设置。

    需要注意的是,BASH 程序被执行后,实际上 Linux 系统是另外开设了一个进程来运行的。


    2.2     关于输入、输出和错误输出

    在字符终端环境中,标准输入/标准输出的概念很好理解。输入即指对一个应用程序 或命令的输入,无论是从键盘输入还是从别的文件输入;输出即指应用程序或命令产生的一些信息;与 Windows 系统下不同的是,Linux 系统下还有一个标准错误输出的概念,这个概念主要是为程序调试和系统维护目的而设置的,错误输出于标准输出分开可以让一些高级的错误信息不干扰正常的输出 信息,从而方便一般用户的使用。

    在 Linux 系统中:标准输入(stdin)默认为键盘输入;标准输出(stdout)默认为屏幕输出;标准错误输出(stderr)默认也是输出到屏幕(上面的 std 表示 standard)。在 BASH 中使用这些概念时一般将标准输出表示为 1,将标准错误输出表示为 2。下面我们举例来说明如何使用他们,特别是标准输出和标准错误输出。

    输入、输出及标准错误输出主要用于 I/O 的重定向,就是说需要改变他们的默认设置。先看这个例子:

    $ ls > ls_result
    $ ls -l >> ls_result

    上面这两个命令分别将 ls 命令的结果输出重定向到 ls_result 文件中和追加到 ls_result 文件中,而不是输出到屏幕上。">"就是输出(标准输出和标准错误输出)重定向的代表符号,连续两个 ">" 符号,即 ">>" 则表示不清除原来的而追加输出。下面再来看一个稍微复杂的例子:

    $ find /home -name lost* 2> err_result

    这个命令在 ">" 符号之前多了一个 "2","2>" 表示将标准错误输出重定向。由于 /home 目录下有些目录由于权限限制不能访问,因此会产生一些标准错误输出被存放在 err_result 文件中。大家可以设想一下 find /home -name lost* 2>>err_result 命令会产生什么结果?

    如果直接执行 find /home -name lost* > all_result ,其结果是只有标准输出被存入 all_result 文件中,要想让标准错误输出和标准输入一样都被存入到文件中,那该怎么办呢?看下面这个例子:

    $ find /home -name lost* > all_result 2>& 1

    上面这个例子中将首先将标准错误输出也重定向到标准输出中,再将标准输出重定向到 all_result 这个文件中。这样我们就可以将所有的输出都存储到文件中了。为实现上述功能,还有一种简便的写法如下:

    $ find /home -name lost* >& all_result

    如果那些出错信息并不重要,下面这个命令可以让你避开众多无用出错信息的干扰

    $ find /home -name lost* 2> /dev/null

    同学们回去后还可以再试验一下如下几种重定向方式,看看会出什么结果,为什么?

    $ find /home -name lost* > all_result 1>& 2 
    $ find /home -name lost* 2> all_result 1>& 2
    $ find /home -name lost* 2>& 1 > all_result

    另外一个非常有用的重定向操作符是 "-",请看下面这个例子:

    $ (cd /source/directory && tar cf - . ) | (cd /dest/directory && tar xvfp -)

    该命令表示把 /source/directory 目录下的所有文件通过压缩和解压,快速的全部移动到 /dest/directory 目录下去,这个命令在 /source/directory 和 /dest/directory 不处在同一个文件系统下时将显示出特别的优势。

    下面还几种不常见的用法:

    n<&- 表示将 n 号输入关闭 
    <&- 表示关闭标准输入(键盘)
    n>&- 表示将 n 号输出关闭
    >&- 表示将标准输出关闭

    2.3     BASH 中对变量的规定(与 C 语言的异同)

    好了下面我们进入正题,先看看 BASH 中的变量是如何定义和使用的。对于熟悉 C 语言的程序员,我们将解释 BASH 中的定义和用法与 C 语言中有何不同。

    2.3.1. BASH 中的变量介绍

    我们先来从整体上把握一下 BASH 中变量的用法,然后再去分析 BASH 中变量使用与 C 语言中的不同。BASH 中的变量都是不能含有保留字,不能含有 "-" 等保留字符,也不能含有空格。

    2.3.1.1 简单变量

    在 BASH 中变量定义是不需要的,没有 "int i" 这样的定义过程。如果想用一个变量,只要他没有在前面被定义过,就直接可以用,当然你使用该变量的第一条语句应该是对他赋初值了,如果你不赋初值也没关 系,只不过该变量是空( 注意:是 NULL,不是 0 )。不给变量赋初值虽然语法上不反对,但不是一个好的编程习惯。好了我们看看下面的例子:

    首先用 vi 编辑下面这个文件 hello2

    #!/bin/bash 
    # give the initialize value to STR
    STR="Hello World" 
    echo $STR 

    在上面这个程序中我们需要注意下面几点:

    一,变量赋值时,'='左右两边都不能有空格;
    二,BASH 中的语句结尾不需要分号(";");
    三,除了在变量赋值和在FOR循环语句头中,BASH 中的变量使用必须在变量前加"$"符号,同学们可以将上面程序中第三行改为 "echo STR" 再试试,看看会出什么结果。==>output: STR
    四,由于 BASH 程序是在一个新的进程中运行的,所以该程序中的变量定义和赋值不会改变其他进程或原始 Shell 中同名变量的值,也不会影响他们的运行。

    更细致的文档甚至提到以但引号括起来的变量将不被 BASH 解释为变量,如 '$STR' ,而被看成为纯粹的字符串。而且更为标准的变量引用方式是 ${STR} 这样的,$STR 自不过是对 ${STR} 的一种简化。在复杂情况下(即有可能产生歧义的地方)最好用带 {} 的表示方式。

    BASH 中的变量既然不需要定义,也就没有类型一说,一个变量即可以被定义为一个字符串,也可以被再定义为整数。如果对该变量进行整数运算,他就被解释为整数;如果对他进行字符串操作,他就被看作为一个字符串。请看下面的例子:

    #!/bin/bash 
    x=1999 
    let "x = $x + 1" 
    echo $x 
    x="olympic'"$x 
    echo $x

    关于整数变量计算,有如下几种:" + - * / % ",他们的意思和字面意思相同。整数运算一般通过 let 和 expr 这两个指令来实现,如对变量 x 加 1 可以写作:let "x = $x + 1" 或者 x=`expr $x + 1`

    在比较操作上,整数变量和字符串变量各不相同,详见下表:

    对应的操作
    整数操作
    字符串操作
    相同
    -eq
    =
    不同
    -ne
    !=
    大于
    -gt
    >
    小于
    -lt
    <
    大于或等于
    -ge

    小于或等于
    -le

    为空

    -z
    不为空

    -n


    比如:

    比较字符串 a 和 b 是否相等就写作:if [ $a = $b ]
    判断字符串 a 是否为空就写作: 
    if [ -z $a ]
    判断整数变量 a 是否大于 b 就写作:
    if [ $a -gt $b ]

    更细致的文档推荐在字符串比较时尽量不要使用 -n ,而用 ! -z 来代替。(其中符号 "!" 表示求反操作)

    BASH 中的变量除了用于对 整数 和 字符串 进行操作以外,另一个作用是作为文件变量。BASH 是 Linux 操作系统的 Shell,因此系统的文件必然是 BASH 需要操作的重要对象,如 if [ -x /root ] 可以用于判断 /root 目录是否可以被当前用户进入。下表列出了 BASH 中用于判断文件属性的操作符:

    运算符
    含义( 满足下面要求时返回 TRUE )
    -e file
    文件 file 已经存在
    -f file
    文件 file 是普通文件
    -s file
    文件 file 大小不为零
    -d file
    文件 file 是一个目录
    -r file
    文件 file 对当前用户可以读取
    -w file
    文件 file 对当前用户可以写入
    -x file
    文件 file 对当前用户可以执行
    -g file
    文件 file 的 GID 标志被设置
    -u file
    文件 file 的 UID 标志被设置
    -O file
    文件 file 是属于当前用户的
    -G file
    文件 file 的组 ID 和当前用户相同
    file1 -nt file2
    文件 file1 比 file2 更新
    file1 -ot file2
    文件 file1 比 file2 更老


    注意:上表中的 file 及 file1、file2 都是指某个文件或目录的路径

    2.3.1.1. 关于局部变量

    在 BASH 程序中如果一个变量被使用了,那么直到该程序的结尾,该变量都一直有效。为了使得某个变量存在于一个局部程序块中,就引入了局部变量的概念。BASH 中,在变量首次被赋初值时加上 local 关键字就可以声明一个局部变量,如下面这个例子

    #!/bin/bash 
    HELLO=Hello 
    function hello { 
    local HELLO=World 
    echo $HELLO 

    echo $HELLO 
    hello 
    echo $HELLO

    该程序的执行结果是:

    Hello
    World
    Hello

    这个执行结果表明全局变量 $HELLO 的值在执行函数 hello 时并没有被改变。也就是说局部变量 $HELLO 的影响只存在于函数那个程序块中。

    2.3.2. BASH 中的变量与 C 语言中变量的区别

    这里我们为原来不熟悉 BASH 编程,但是非常熟悉 C 语言的程序员总结一下在 BASH 环境中使用变量需要注意的问题。

    1,BASH 中的变量在引用时都需要在变量前加上 "$" 符号( 第一次赋值及在For循环的头部不用加 "$"符号 );
    2,BASH 中没有浮点运算,因此也就没有浮点类型的变量可用;
    3,BASH 中的整形变量的比较符号与 C 语言中完全不同,而且整形变量的算术运算也需要经过 let 或 expr 语句来处理;

    2.4     BASH 中的基本流程控制语法

    BASH 中几乎含有 C 语言中常用的所有控制结构,如条件分支、循环等,下面逐一介绍。

    2.4.1 if...then...else

    if 语句用于判断和分支,其语法规则和 C 语言的 if 非常相似。其几种基本结构为:

    if [ expression ]
    then
    statments
    fi

    或者

    if [ expression ]
    then
    statments
    else
    statments
    fi

    或者

    if [ expression ]
    then
    statments
    else if [ expression ]
    then 
    statments
    else
    statments 
    fi

    或者

    if [ expression ]
    then
    statments
    elif [ expression ]
    then
    statments
    else
    statments
    fi

    值得说明的是如果你将 if 和 then 简洁的写在一行里面,就必须在 then 前面加上分号,如:if [ expression ]; then ... 。下面这个例子说明了如何使用 if 条件判断语句:

    #!/bin/bash 

    if [ $1 -gt 90 ] 
    then 
    echo "Good, $1" 
    elif [ $1 -gt 70 ] 
    then 
    echo "OK, $1" 
    else 
    echo "Bad, $1" 
    fi 

    exit 0

    上面例子中的 $1 是指命令行的第一个参数,这个会在后面的“BASH 中的特殊保留字”中讲解。

    2.4.2 for

    for 循环结构与 C 语言中有所不同,在 BASH 中 for 循环的基本结构是:

    for $var in


    do
    statments
    done

    其中 $var 是循环控制变量,

    是 $var 需要遍历的一个集合,do/done 对包含了循环体,相当于 C 语言中的一对大括号。另外如果do 和 for 被写在同一行,必须在 do 前面加上 ";"。如: for $var in ; do 。下面是一个运用 for 进行循环的例子:

    #!/bin/bash 

    for day in Sun Mon Tue Wed Thu Fri Sat 
    do 
    echo $day 
    done 

    # 如果列表被包含在一对双引号中,则被认为是一个元素 
    for day in "Sun Mon Tue Wed Thu Fri Sat" 
    do 
    echo $day 
    done 

    exit 0

    注意上面的例子中,在 for 所在那行的变量 day 是没有加 "$" 符号的,而在循环体内,echo 所在行变量 $day 是必须加上 "$" 符号的。另外如果写成 for day 而没有后面的 in

    部分,则 day 将取遍命令行的所有参数。如这个程序:

    #!/bin/bash 

    for param 
    do 
    echo $param 
    done 

    exit 0

    上面这个程序将列出所有命令行参数。for 循环结构的循环体被包含在 do/done 对中,这也是后面的 while、until 循环所具有的特点。

    2.4.3 while

    while 循环的基本结构是:

    while [ condition ]
    do
    statments
    done

    这个结构请大家自己编写一个例子来验证。

    2.4.4 until

    until 循环的基本结构是:

    until [ condition is TRUE ]
    do
    statments
    done

    这个结构也请大家自己编写一个例子来验证。

    2.4.5 case

    BASH 中的 case 结构与 C 语言中的 switch 语句的功能比较类似,可以用于进行多项分支控制。其基本结构是:

    case "$var" in
    condition1 )
    statments1;;
    condition2 )
    statments2;;
    ...
    * )
    default statments;;
    esac

    下面这个程序是运用 case 结构进行分支执行的例子:

    #!/bin/bash 

    echo "Hit a key, then hit return." 
    read Keypress 

    case "$Keypress" in 
    [a-z] ) echo "Lowercase letter";; 
    [A-Z] ) echo "Uppercase letter";; 
    [0-9] ) echo "Digit";; 
    * ) echo "Punctuation, whitespace, or other";; 
    esac 

    exit 0

    上面例子中的第四行 "read Keypress" 一句中的 read 语句表示从键盘上读取输入。这个命令将在本讲义的 BASH 的其他高级问题中讲解。

    2.4.6 break/continue

    熟悉 C 语言编程的都很熟悉 break 语句和 continue 语句。BASH 中同样有这两条语句,而且作用和用法也和 C 语言中相同,break 语句可以让程序流程从当前循环体中完全跳出,而 continue 语句可以跳过当次循环的剩余部分并直接进入下一次循环。

    2.5     函数的使用

    BASH 是一个相对简单的脚本语言,不过为了方便结构化的设计,BASH 中也提供了函数定义的功能。BASH 中的函数定义很简单,只要向下面这样写就可以了:

    function my_funcname { 
    code block
    }

    或者

    my_funcname() { 
    code block
    }

    上面的第二种写法更接近于 C 语言中的写法。BASH 中要求函数的定义必须在函数使用之前,这是和 C 语言用头文件说明函数方法的不同。

    更进一步的问题是如何给函数传递参数和获得返回值。BASH 中函数参数的定义并不需要在函数定义处就制定,而只需要在函数被调用时用 BASH 的保留变量 $1 $2 ... 来引用就可以了;BASH 的返回值可以用 return 语句来指定返回一个特定的整数,如果没有 return 语句显式的返回一个返回值,则返回值就是该函数最后一条语句执行的结果(一般为 0,如果执行失败返回错误码)。函数的返回值在调用该函数的程序体中通过 $? 保留字来获得。下面我们就来看一个用函数来计算整数平方的例子:

    #!/bin/bash 

    square() { 
    let "res = $1 * $1" 
    return $res 


    square $1 
    result=$? 
    echo $result 

    exit 0


    BASH 中的特殊保留字

    • 保留变量

    • 随机数

    • 运算符

    • 变量的特殊操作

    3.1     保留变量

    BASH 中有一些保留变量,下面列出了一些:

    $IFS  这个变量中保存了用于分割输入参数的分割字符,默认识空格。 
    $HOME  这个变量中存储了当前用户的根目录路径。 
    $PATH  这个变量中存储了当前 Shell 的默认路径字符串。 
    $PS1  表示第一个系统提示符。 
    $PS2  表示的二个系统提示符。 
    $PWD  表示当前工作路径。 
    $EDITOR 表示系统的默认编辑器名称。 
    $BASH  表示当前 Shell 的路径字符串。
    $0, $1, $2, ... 
    表示系统传给脚本程序或脚本程序传给函数的第0个、第一个、第二个等参数。
    $#   表示脚本程序的命令参数个数或函数的参数个数。
    $$   表示该脚本程序的进程号,常用于生成文件名唯一的临时文件。 
    $?   表示脚本程序或函数的返回状态值,正常为 0,否则为非零的错误号。
    $*   表示所有的脚本参数或函数参数。
    $@   和 $* 涵义相似,但是比 $* 更安全。
    $!   表示最近一个在后台运行的进程的进程号。


    3.2    随机数

    随机数是经常要用到的,BASH 中也提供了这个功能,请看下面这个程序:

    #!/bin/bash 

    # Prints different random integer from 1 to 65536 

    a=$RANDOM 
    echo $a 

    exit 0

    这个程序可以在每次执行的时候随机的打印出一个大小在 1 到 65536 之间的整数。

    3.3     运算符

    算术运算符 
    + - * / % 表示加减乘除和取余运算
    += -= *= /= 同 C 语言中的含义

    位操作符
    << <<= >> >>= 表示位左右移一位操作
    & &= | |= 表示按位与、位或操作
    ~ ! 表示非操作
    ^ ^= 表示异或操作

    关系运算符 
    < > <= >= == != 表示大于、小于、大于等于、小于等于、等于、不等于操作
    && || 逻辑与、逻辑或操作

    3.4     变量的特殊操作

    BASH 中还有一些对变量的简洁、快速的操作,大家还记得 "${var}" 和 "$var" 同样是对变量的引用吧,对 ${var} 进行一些变化就可以产生一些新功能:
    ${var-default} 表示如果变量 $var 还没有设置,则保持 $var 没有设置的状态,并返回后面的默认值 default。
    ${var=default} 表示如果变量 $var 还没有设置,则取后面的默认值 default。 
    ${var+otherwise} 表示如果变量 $var 已经设置,则返回 otherwise 的值,否则返回空( null )。
    ${var?err_msg} 表示如果变量 $var 已经设置,则返回该变量的值,否则将后面的 err_msg 输出到标准错误输出上。

    请同学们自己尝试下面的例子:

    #!/bin/bash

    echo ${var?There is an error}

    exit 0

    还有下面一些用法,这些用法主要用于从文件路径字符串中提取有用信息:
    ${var#pattern}, ${var##pattern} 用于从变量 $var 中剥去最短(最长)的和 pattern 相匹配的最左侧的串。
    ${var%pattern}, ${var%%pattern} 用于从变量 $var 中剥去最短(最长)的和 pattern 相匹配的最右侧的串。

    另外 BASH 2 中还加入下面一些操作:
    ${var:pos} 表示去掉变量 $var 中前 pos 个字符。
    ${var:pos:len} 表示变量 $var 中去掉前 pos 个字符后的剩余字符串的前 len 个字符。
    ${var/pattern/replacement} 表示将变量 $var 中第一个出现的 pattern 模式替换为 replacement 字符串。
    ${var//pattern/replacement} 表示将变量 $var 中出现的所有 pattern 模式全部都替换为 replacment 字符串。


    BASH 中的其他高级问题

    • BASH 中对返回值的处理

    • 用 BASH 设计简单用户界面

    • 在 BASH 中读取用户输入

    • 一些特殊的惯用法

    • BASH 程序的调试

    • 关于 BASH2

    4.1     BASH 中对返回值的处理

    无论是在 Shell 中对 BASH 脚本返回值的处理,还是在脚本中对函数返回值的处理,都是通过 "$?" 系统变量来获得。BASH 要求返回值必须为一个整数,不能用 return 语句返回字符串变量。


    4.2     用 BASH 设计简单用户界面

    BASH 中提供了一个小的语句格式,可以让程序快速的设计出一个字符界面的用户交互选择的菜单,该功能就是由 select 语句来实现的,select 语句的语法为:

    select var in


    do
    statments use $var
    done

    上面的语法结构在执行后,BASH 会将

    中的所有项加上数字列在屏幕上等待用户选择,在用户作出选择后,变量 $var 中就包含了那个被选中的字符串,然后就可以对该变量进行需要的操作了。我们可以从下面的例子中更直观的来理解这个功能:

    #!/bin/bash 

    OPTIONS="Hello Quit" 
    select opt in $OPTIONS; do 
    if [ "$opt" = "Quit" ]; then 
    echo done 
    exit 
    elif [ "$opt" = "Hello" ]; then 
    echo Hello World 
    else 
    clear 
    echo bad option 
    fi 
    done 

    exit 0

    大家可以试着执行上面的程序,看看是什么执行结果。

    4.3     在 BASH 中读取用户输入

    BASH 中通过 read 函数来实现读取用户输入的功能,如下面这段程序:

    #!/bin/bash

    echo Please enter your name
    read NAME 
    echo "Hi! $NAME !"

    exit 0

    上面这个脚本读取用户的输入,并回显在屏幕上。

    另外 BASH 中还提供另外一种称为 here documents 的结构?,可以将用户需要通过键盘输入的字符串改为从程序体中直接读入,如密码。下面的小程序演示了这个功能:

    #!/bin/bash 

    passwd="aka@tsinghua" 
    ftp -n localhost <<FTPFTP
    user anonymous $passwd 
    binary 
    bye 
    FTPFTP 

    exit 0

    这个程序在用户需要通过键盘敲入一些字符时,通过程序内部的动作来模拟键盘输入。请注意 here documents 的基本结构为:

    command <<SOMESPECIALSTRING
    statments
    ...
    SOMESPECIALSTRING

    这里要求在需要键盘输入的命令后,直接加上 <<符号,然后跟上一个特别的字符串,在该串后按顺序输入本来应该由键盘输入的所有字符,在所有需要输入的字符都结束后,重复一遍前面 <<符号后的“特别的字符串”即表示该输入到此结束。

    4.4 一些特殊的惯用法

    在 BASH 中 () 一对括号一般被用于求取括号中表达式的值或命令的执行结果,如:(a=hello; echo $a) ,其作用相当于 `...` 。

    : 有两个含义,一是表示空语句,有点类似于 C 语言中的单个 ";" 。表示该行是一个空命令,如果被用在 while/until 的头结构中,则表示值 0,会使循环一直进行下去,如下例:

    while : 
    do 
    operation-1 
    operation-2 
    ... 
    operation-n 
    done

    另外 : 还可以用于求取后面变量的值,比如:

    #!/bin/bash 

    : ${HOSTNAME?} {USER?} {MAIL?} 
    echo $HOSTNAME 
    echo $USER 
    echo $MAIL 

    exit 0

    在 BASH 中 export 命令用于将系统变量输出到外层的 Shell 中了。

    4.5 BASH 程序的调试

    用 bash -x bash-script 命令,可以查看一个出错的 BASH 脚本到底错在什么地方,可以帮助程序员找出脚本中的错误。

    另外用 trap 语句可以在 BASH 脚本出错退出时打印出一些变量的值,以供程序员检查。trap 语句必须作为继 "#!/bin/bash" 后的第一句非注释代码,一般 trap 命令被写作: trap 'message $checkvar1 $checkvar2' EXIT 。

    4.6 关于 BASH2

    使用 bash -version 命令可以看出当前你正在使用的 BASH 是什么版本,一般版本号为1.14或其他版本。而现在机器上一般还安装了一个版本号为 2.0 的 BASH 。该 BASH 也在 /bin 目录下。BASH2 提供了一些新功能,有兴趣的同叙可以自己去看相关资料,或直接 man bash2 即可。

    展开全文
  • Linuxshell编程

    2018-09-22 10:23:51
     Shell的作用是解释执行用户的命令,用户输入一条命令,Shell就解释执行一条,这种方式称为交互式(Interactive),Shell还有一种执行命令的方式称为批处理(Batch),用户事先写一个Shell脚本(Script),其中有很...

    shell编程

    shell历史

           Shell的作用是解释执行用户的命令,用户输入一条命令,Shell就解释执行一条,这种方式称为交互式(Interactive),Shell还有一种执行命令的方式称为批处理(Batch),用户事先写一个Shell脚本(Script),其中有很多条命令,让Shell一次把这些命令执行完,而不必一条一条地敲命令。Shell脚本和编程语言很相似,也有变量和流程控制语句,但Shell脚本是解释执行的,不需要编译,Shell程序从脚本中一行一行读取并执行这些命令,相当于一个用户把脚本中的命令一行一行敲到Shell提示符下执行。

           由于历史原因,UNIX系统上有很多种Shell:

    1. sh(Bourne Shell):由Steve Bourne开发,各种UNIX系统都配有sh。
    2. csh(C Shell):由Bill Joy开发,随BSD UNIX发布,它的流程控制语句很像C语言,支持很多Bourne Shell所不支持的功能:作业控制,命令历史,命令行编辑。
    3. ksh(Korn Shell):由David Korn开发,向后兼容sh的功能,并且添加了csh引入的新功能,是目前很多UNIX系统标准配置的Shell,在这些系统上/bin/sh往往是指向/bin/ksh的符号链接。
    4. tcsh(TENEX C Shell):是csh的增强版本,引入了命令补全等功能,在FreeBSD、MacOS X等系统上替代了csh。
    5. bash(Bourne Again Shell):由GNU开发的Shell,主要目标是与POSIX标准保持一致,同时兼顾对sh的兼容,bash从csh和ksh借鉴了很多功能,是各种Linux发行版标准配置的Shell,在Linux系统上/bin/sh往往是指向/bin/bash的符号链接。虽然如此,bash和sh还是有很多不同的,一方面,bash扩展了一些命令和参数,另一方面,bash并不完全和sh兼容,有些行为并不一致,所以bash需要模拟sh的行为:当我们通过sh这个程序名启动bash时,bash可以假装自己是sh,不认扩展的命令,并且行为与sh保持一致。

    itcast$ vim /etc/passwd

           其中最后一列显示了用户对应的shell类型

            root:x:0:0:root:/root:/bin/bash

            nobody:x:65534:65534:nobody:/nonexistent:/bin/sh

            syslog:x:101:103::/home/syslog:/bin/false

            itcast:x:1000:1000:itcast,,,:/home/itcast:/bin/bash

            ftp:x:115:125:ftp daemon,,,:/srv/ftp:/bin/false

           用户在命令行输入命令后,一般情况下Shell会fork并exec该命令,但是Shell的内建命令例外,执行内建命令相当于调用Shell进程中的一个函数,并不创建新的进程。以前学过的cd、alias、umask、exit等命令即是内建命令,凡是用which命令查不到程序文件所在位置的命令都是内建命令,内建命令没有单独的man手册,要在man手册中查看内建命令,应该执行

    itcast$ man bash-builtins

           如export、shift、if、eval、[、for、while等等。内建命令虽然不创建新的进程,但也会有Exit Status,通常也用0表示成功非零表示失败,虽然内建命令不创建新的进程,但执行结束后也会有一个状态码,也可以用特殊变量$?读出。

    执行脚本

             编写一个简单的脚本test.sh:

    #! /bin/sh

    cd ..

    ls

            Shell脚本中用#表示注释,相当于C语言的//注释。但如果#位于第一行开头,并且是#!(称为Shebang)则例外,它表示该脚本使用后面指定的解释器/bin/sh解释执行。如果把这个脚本文件加上可执行权限然后执行:

    itcast$ chmod a+x test.sh

    itcast$ ./test.sh

           Shell会fork一个子进程并调用exec执行./test.sh这个程序,exec系统调用应该把子进程的代码段替换成./test.sh程序的代码段,并从它的_start开始执行。然而test.sh是个文本文件,根本没有代码段和_start函数,怎么办呢?其实exec还有另外一种机制,如果要执行的是一个文本文件,并且第一行用Shebang指定了解释器,则用解释器程序的代码段替换当前进程,并且从解释器的_start开始执行,而这个文本文件被当作命令行参数传给解释器。因此,执行上述脚本相当于执行程序

    itcast$ /bin/sh ./test.sh

            以这种方式执行不需要test.sh文件具有可执行权限。

            如果将命令行下输入的命令用()括号括起来,那么也会fork出一个子Shell执行小括号中的命令,一行中可以输入由分号;隔开的多个命令,比如:

    itcast$ (cd ..;ls -l)

             和上面两种方法执行Shell脚本的效果是相同的,cd ..命令改变的是子Shell的PWD,而不会影响到交互式Shell。然而命令

    itcast$ cd ..;ls -l

             则有不同的效果,cd ..命令是直接在交互式Shell下执行的,改变交互式Shell的PWD,然而这种方式相当于这样执行Shell脚本:

    itcast$ source ./test.sh

            或者

    itcast$ . ./test.sh

            source或者.命令是Shell的内建命令,这种方式也不会创建子Shell,而是直接在交互式Shell下逐行执行脚本中的命令。

    基本语法

    变量

    按照惯例,Shell变量通常由字母加下划线开头,由任意长度的字母、数字、下划线组成。有两种类型的Shell变量:

           1、环境变量

            环境变量可以从父进程传给子进程,因此Shell进程的环境变量可以从当前Shell进程传给fork出来的子进程。用printenv命令可以显示当前Shell进程的环境变量。

           2、本地变量

            只存在于当前Shell进程,用set命令可以显示当前Shell进程中定义的所有变量(包括本地变量和环境变量)和函数。

            环境变量是任何进程都有的概念,而本地变量是Shell特有的概念。在Shell中,环境变量和本地变量的定义和用法相似。在Shell中定义或赋值一个变量:

    itcast$ VARNAME=value

            注意等号两边都不能有空格,否则会被Shell解释成命令和命令行参数。

            一个变量定义后仅存在于当前Shell进程,它是本地变量,用export命令可以把本地变量导出为环境变量,定义和导出环境变量通常可以一步完成:

    itcast$ export VARNAME=value

              也可以分两步完成:

    itcast$ VARNAME=value

    itcast$ export VARNAME

             用unset命令可以删除已定义的环境变量或本地变量。

    itcast$ unset VARNAME

            如果一个变量叫做VARNAME,用 ' VARNAME ' 可以表示它的值,在不引起歧义的情况下也可以用VARNAME表示它的值。通过以下例子比较这两种表示法的不同:

    itcast$ echo $SHELL

           注意,在定义变量时不用“'”取变量值时要用。和C语言不同的是,Shell变量不需要明确定义类型,事实上Shell变量的值都是字符串,比如我们定义VAR=45,其实VAR的值是字符串45而非整数。Shell变量不需要先定义后使用,如果对一个没有定义的变量取值,则值为空字符串。

    文件名代换(Globbing)

             这些用于匹配的字符称为通配符(Wildcard),如:* ? [ ] 具体如下:

         * 匹配0个或多个任意字符

          ? 匹配一个任意字符

           [若干字符] 匹配方括号中任意一个字符的一次出现

     

    itcast$ ls /dev/ttyS*

    itcast$ ls ch0?.doc

    itcast$ ls ch0[0-2].doc

    itcast$ ls ch[012] [0-9].doc

            注意,Globbing所匹配的文件名是由Shell展开的,也就是说在参数还没传给程序之前已经展开了,比如上述ls ch0[012].doc命令,如果当前目录下有ch00.doc和ch02.doc,则传给ls命令的参数实际上是这两个文件名,而不是一个匹配字符串。

    命令代换

             由“`”反引号括起来的也是一条命令,Shell先执行该命令,然后将输出结果立刻代换到当前命令行中。例如定义一个变量存放date 命令的输出:

    itcast$ DATE=`date`

    itcast$ echo $DATE

            命令代换也可以用$()表示:

    itcast$ DATE=$(date)

    算术代换

            使用$(()),用于算术计算,(())中的Shell变量取值将转换成整数,同样含义的$[ ]等价例如:

    itcast$ VAR=45

    itcast$ echo $(($VAR+3)) 等价于 echo $[VAR+3]或 $[$VAR+3]

           $(())中只能用+-*/和()运算符,并且只能做整数运算。

           $[base#n],其中base表示进制,n按照base进制解释,后面再有运算数,按十进制解释。

    echo $[2#10+11]

    echo $[8#10+11]

    echo $[16#10+11]

    转义字符

           和C语言类似,\在Shell中被用作转义字符,用于去除紧跟其后的单个字符的特殊意义(回车除外),换句话说,紧跟其后的字符取字面值。例如:

    itcast$ echo $SHELL

    /bin/bash

    itcast$ echo \$SHELL

    $SHELL

    itcast$ echo \\

    \

            比如创建一个文件名为“$ $”的文件($间含有空格)可以这样:

    itcast$ touch \$\ \$

           还有一个字符虽然不具有特殊含义,但是要用它做文件名也很麻烦,就是-号。如果要创建一个文件名以-号开头的文件,这样是不正确的:

    itcast$ touch -hello

    touch: invalid option -- h

    Try `touch --help' for more information.

             即使加上\转义也还是报错:

    itcast$ touch \-hello

    touch: invalid option -- h

    Try `touch --help' for more information.

           因为各种UNIX命令都把-号开头的命令行参数当作命令的选项,而不会当作文件名。如果非要处理以-号开头的文件名,可以有两种办法:

    itcast$ touch ./-hello

           或者

    itcast$ touch -- -hello

           \还有一种用法,在\后敲回车表示续行,Shell并不会立刻执行命令,而是把光标移到下一行,给出一个续行提示符>,等待用户继续输入,最后把所有的续行接到一起当作一个命令执行。例如:

    itcast$ ls \

    > -l

    (ls -l命令的输出)

    单引号

            和C语言同,Shell脚本中的单引号和双引号一样都是字符串的界定符(双引号下一节介绍),而不是字符的界定符。单引号用于保持引号内所有字符的字面值,即使引号内的\和回车也不例外,但是字符串中不能出现单引号。如果引号没有配对就输入回车,Shell会给出续行提示符,要求用户把引号配上对。例如:

    itcast$ echo '$SHELL'

    $SHELL

    itcast$ echo 'ABC\(回车)

    > DE'(再按一次回车结束命令)

    ABC\

    DE

    双引号

              被双引号用括住的内容,将被视为单一字串。它防止通配符扩展,但允许变量扩展。这点与单引号的处理方式不同

    itcast$ DATE=$(date)

    itcast$ echo "$DATE"

    itcast$ echo '$DATE'

             再比如:

    itcast$ VAR=200

    itcast$ echo $VAR

    200

    itcast$ echo '$VAR'

    $VAR

    itcast$ echo "$VAR"

    200

     

    Shell脚本语法

    条件测试

             命令test或 [ 可以测试一个条件是否成立,如果测试结果为真,则该命令的Exit Status为0,如果测试结果为假,则命令的Exit Status为1(注意与C语言的逻辑表示正好相反)。例如测试两个数的大小关系:

    itcast@ubuntu:~$ var=2

    itcast@ubuntu:~$ test $var -gt 1

    itcast@ubuntu:~$ echo $?

    0

    itcast@ubuntu:~$ test $var -gt 3

    itcast@ubuntu:~$ echo $?

    1

    itcast@ubuntu:~$ [ $var -gt 3 ]

    itcast@ubuntu:~$ echo $?

    1

    itcast@ubuntu:~$

           虽然看起来很奇怪,但左方括号 [ 确实是一个命令的名字,传给命令的各参数之间应该用空格隔开,比如:$VAR、-gt、3、] 是 [ 命令的四个参数,它们之间必须用空格隔开。命令test或 [ 的参数形式是相同的,只不过test命令不需要 ] 参数。以 [ 命令为例,常见的测试命令如下表所示:

    [ -d DIR ] 如果DIR存在并且是一个目录则为真

    [ -f FILE ] 如果FILE存在且是一个普通文件则为真

    [ -z STRING ] 如果STRING的长度为零则为真

    [ -n STRING ] 如果STRING的长度非零则为真

    [ STRING1 = STRING2 ] 如果两个字符串相同则为真

    [ STRING1 != STRING2 ] 如果字符串不相同则为真

    [ ARG1 OP ARG2 ] ARG1和ARG2应该是整数或者取值为整数的变量,OP是-eq(等于)-ne(不等于)-lt(小于)-le(小于等于)-gt(大于)-ge(大于等于)之中的一个

             和C语言类似,测试条件之间还可以做与、或、非逻辑运算:

    [ ! EXPR ] EXPR可以是上表中的任意一种测试条件,!表示“逻辑反(非)”

    [ EXPR1 -a EXPR2 ] EXPR1和EXPR2可以是上表中的任意一种测试条件,-a表示“逻辑与”

    [ EXPR1 -o EXPR2 ] EXPR1和EXPR2可以是上表中的任意一种测试条件,-o表示“逻辑或”

           例如:

    $ VAR=abc

    $ [ -d Desktop -a $VAR = 'abc' ]

    $ echo $?

    0

            注意,如果上例中的$VAR变量事先没有定义,则被Shell展开为空字符串,会造成测试条件的语法错误(展开为[ -d Desktop -a = ‘abc’ ]),作为一种好的Shell编程习惯应该总是把变量取值放在双引号之中(展开为[ -d Desktop -a “” = ‘abc’ ]):

    $ unset VAR

    $ [ -d Desktop -a $VAR = 'abc' ]

    bash: [: too many arguments

    $ [ -d Desktop -a "$VAR" = 'abc' ]

    $ echo $?

    1

    分支

    if/then/elif/else/fi

           和C语言类似,在Shell中用if、then、elif、else、fi这几条命令实现分支控制。这种流程控制语句本质上也是由若干条Shell命令组成的,例如先前讲过的

    if [ -f ~/.bashrc ]; then

        . ~/.bashrc

    fi

          其实是三条命令,if [ -f ∼/.bashrc ]是第一条,then . ∼/.bashrc是第二条,fi是第三条。如果两条命令写在同一行则需要用;号隔开,一行只写一条命令就不需要写;号了,另外,then后面有换行,但这条命令没写完,Shell会自动续行,把下一行接在then后面当作一条命令处理。和[命令一样,要注意命令和各参数之间必须用空格隔开。if命令的参数组成一条子命令,如果该子命令的Exit Status为0(表示真),则执行then后面的子命令,如果Exit Status非0(表示假),则执行elif、else或者fi后面的子命令。if后面的子命令通常是测试命令,但也可以是其它命令。Shell脚本没有{}括号,所以用fi表示if语句块的结束。见下例:

    #! /bin/sh

     

    if [ -f /bin/bash ]

    then

           echo "/bin/bash is a file"

    else

           echo "/bin/bash is NOT a file"

    fi

    if :; then echo "always true"; fi

            “:”是一个特殊的命令,称为空命令,该命令不做任何事,但Exit Status总是真。此外,也可以执行/bin/true或/bin/false得到真或假的Exit Status。再看一个例子:

    #! /bin/sh

     

    echo "Is it morning? Please answer yes or no."

    read YES_OR_NO

    if [ "$YES_OR_NO" = "yes" ]; then

           echo "Good morning!"

    elif [ "$YES_OR_NO" = "no" ]; then

           echo "Good afternoon!"

    else

           echo "Sorry, $YES_OR_NO not recognized. Enter yes or no."

           exit 1

    fi

    exit 0

           上例中的read命令的作用是等待用户输入一行字符串,将该字符串存到一个Shell变量中。

           此外,Shell还提供了&&和||语法,和C语言类似,具有Short-circuit特性,很多Shell脚本喜欢写成这样:

    test "$(whoami)" != 'root' && (echo you are using a non-privileged account; exit 1)

            &&相当于“if…then…”,而||相当于“if not…then…”。&&和||用于连接两个命令,而上面讲的-a和-o仅用于在测试表达式中连接两个测试条件,要注意它们的区别,例如:

    test "$VAR" -gt 1 -a "$VAR" -lt 3

              和以下写法是等价的

    test "$VAR" -gt 1 && test "$VAR" -lt 3

    case/esac

           case命令可类比C语言的switch/case语句,esac表示case语句块的结束。C语言的case只能匹配整型或字符型常量表达式,而Shell脚本的case可以匹配字符串和Wildcard,每个匹配分支可以有若干条命令,末尾必须以;;结束,执行时找到第一个匹配的分支并执行相应的命令,然后直接跳到esac之后,不需要像C语言一样用break跳出。

    #! /bin/sh

     

    echo "Is it morning? Please answer yes or no."

    read YES_OR_NO

    case "$YES_OR_NO" in

    yes|y|Yes|YES)

            echo "Good Morning!";;

    [nN]*)

            echo "Good Afternoon!";;

    *)

             echo "Sorry, $YES_OR_NO not recognized. Enter yes or no."

             exit 1;;

    esac

    exit 0

           使用case语句的例子可以在系统服务的脚本目录/etc/init.d中找到。这个目录下的脚本大多具有这种形式(以/etc/init.d/nfs-kernel-server为例):

    case "$1" in

           start)

                 ...

           ;;

           stop)

                ...

            ;;

            reload | force-reload)

                ...

            ;;

           restart)

                ...

           *)

               log_success_msg"Usage: nfs-kernel-server {start|stop|status|reload|force-reload|restart}"

               exit 1

           ;;

    esac

            启动nfs-kernel-server服务的命令是

    $ sudo /etc/init.d/nfs-kernel-server start

           $1是一个特殊变量,在执行脚本时自动取值为第一个命令行参数,也就是start,所以进入start)分支执行相关的命令。同理,命令行参数指定为stop、reload或restart可以进入其它分支执行停止服务、重新加载配置文件或重新启动服务的相关命令。

    循环

    for/do/done

            Shell脚本的for循环结构和C语言很不一样,它类似于某些编程语言的foreach循环。例如:

    #! /bin/sh

     

    for FRUIT in apple banana pear; do

          echo "I like $FRUIT"

    done

            FRUIT是一个循环变量,第一次循环$FRUIT的取值是apple,第二次取值是banana,第三次取值是pear。再比如,要将当前目录下的chap0、chap1、chap2等文件名改为chap0~、chap1~、chap2~等(按惯例,末尾有~字符的文件名表示临时文件),这个命令可以这样写:

    $ for FILENAME in chap?; do mv $FILENAME $FILENAME~; done

            也可以这样写:

    $ for FILENAME in `ls chap?`; do mv $FILENAME $FILENAME~; done

    while/do/done

           while的用法和C语言类似。比如一个验证密码的脚本:

    #! /bin/sh

     

    echo "Enter password:"

    read TRY

    while [ "$TRY" != "secret" ]; do

           echo "Sorry, try again"

           read TRY

    done

              下面的例子通过算术运算控制循环的次数:

    #! /bin/sh

     

    COUNTER=1

    while [ "$COUNTER" -lt 10 ]; do

           echo "Here we go again"

           COUNTER=$(($COUNTER+1))

    done

            另,Shell还有until循环,类似C语言的do…while。如有兴趣可在课后自行扩展学习。

    break和continue

           break[n]可以指定跳出几层循环;continue跳过本次循环,但不会跳出循环。

           即break跳出,continue跳过。

           练习:将上面验证密码的程序修改一下,如果用户输错五次密码就报错退出。

    位置参数和特殊变量

           有很多特殊变量是被Shell自动赋值的,我们已经遇到了$?和$1。其他常用的位置参数和特殊变量在这里总结一下:

    $0 相当于C语言main函数的argv[0]

    $1、$2... 这些称为位置参数(Positional Parameter),相当于C语言main函数的argv[1]、argv[2]...

    $# 相当于C语言main函数的argc - 1,注意这里的#后面不表示注释

    $@ 表示参数列表"$1" "$2" ...,例如可以用在for循环中的in后面。

    $* 表示参数列表"$1" "$2" ...,同上

    $? 上一条命令的Exit Status

    $$ 当前进程号

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

    #! /bin/sh

     

    echo "The program $0 is now running"

    echo "The first parameter is $1"

    echo "The second parameter is $2"

    echo "The parameter list is $@"

    shift

    echo "The first parameter is $1"

    echo "The second parameter is $2"

    echo "The parameter list is $@"

    输入输出

    echo

           显示文本行或变量,或者把字符串输入到文件。

    echo [option] string

    -e 解析转义字符

    -n 不回车换行。默认情况echo回显的内容后面跟一个回车换行。

    echo "hello\n\n"

    echo -e "hello\n\n"

    echo "hello"

    echo -n "hello"

    管道

              可以通过 | 把一个命令的输出传递给另一个命令做输入。

    cat myfile | more

    ls -l | grep "myfile"

    df -k | awk '{print $1}' | grep -v "文件系统"

    df -k 查看磁盘空间,找到第一列,去除“文件系统”,并输出

    tee

             tee命令把结果输出到标准输出,另一个副本输出到相应文件。

    df -k | awk '{print $1}' | grep -v "文件系统" | tee a.txt

            tee -a a.txt表示追加操作。

    df -k | awk '{print $1}' | grep -v "文件系统" | tee -a a.txt

    文件重定向

    cmd > file 把标准输出重定向到新文件中

    cmd >> file 追加

    cmd > file 2>&1 标准出错也重定向到1所指向的file里

    cmd >> file 2>&1

    cmd < file1 > file2 输入输出都定向到文件里

    cmd < &fd 把文件描述符fd作为标准输入

    cmd > &fd 把文件描述符fd作为标准输出

    cmd < &- 关闭标准输入

    函数

            和C语言类似,Shell中也有函数的概念,但是函数定义中没有返回值也没有参数列表。例如:

    #! /bin/sh

     

    foo(){ echo "Function foo is called";}

    echo "-=start=-"

    foo

    echo "-=end=-"

           注意函数体的左花括号 { 和后面的命令之间必须有空格或换行,如果将最后一条命令和右花括号 } 写在同一行,命令末尾必须有分号;。但,不建议将函数定义写至一行上,不利于脚本阅读。

            在定义foo()函数时并不执行函数体中的命令,就像定义变量一样,只是给foo这个名一个定义,到后面调用foo函数的时候(注意Shell中的函数调用不写括号)才执行函数体中的命令。Shell脚本中的函数必须先定义后调用,一般把函数定义语句写在脚本的前面,把函数调用和其它命令写在脚本的最后(类似C语言中的main函数,这才是整个脚本实际开始执行命令的地方)。

           Shell函数没有参数列表并不表示不能传参数,事实上,函数就像是迷你脚本,调用函数时可以传任意个参数,在函数内同样是用$0、$1、$2等变量来提取参数,函数中的位置参数相当于函数的局部变量,改变这些变量并不会影响函数外面的$0、$1、$2等变量。函数中可以用return命令返回,如果return后面跟一个数字则表示函数的Exit Status。

          下面这个脚本可以一次创建多个目录,各目录名通过命令行参数传入,脚本逐个测试各目录是否存在,如果目录不存在,首先打印信息然后试着创建该目录。

    #! /bin/sh

     

    is_directory()

    {

          DIR_NAME=$1

           if [ ! -d $DIR_NAME ]; then

                    return 1

            else

                    return 0

             fi

    }

    for DIR in "$@"; do

          if is_directory "$DIR"

          then :

          else

                 echo "$DIR doesn't exist. Creating it now..."

                  mkdir $DIR > /dev/null 2>&1

                  if [ $? -ne 0 ]; then

                       echo "Cannot create directory $DIR"

                       exit 1

                  fi

          fi

    done

           注意:is_directory()返回0表示真返回1表示假。

    Shell脚本调试方法

    Shell提供了一些用于调试脚本的选项,如:

          -n 读一遍脚本中的命令但不执行,用于检查脚本中的语法错误。

          -v 一边执行脚本,一边将执行过的脚本命令打印到标准错误输出。

          -x 提供跟踪执行信息,将执行的每一条命令和结果依次打印出来。

    这些选项有三种常见的使用方法:

    1、在命令行提供参数。如:

    $ sh -x ./script.sh

    2、在脚本开头提供参数。如:

    #! /bin/sh -x

    3、在脚本中用set命令启用或禁用参数。如:

    #! /bin/sh

     

    if [ -z "$1" ]; then

        set -x

        echo "ERROR: Insufficient Args."

        exit 1

        set +x

    fi

           set -x和set +x分别表示启用和禁用-x参数,这样可以只对脚本中的某一段进行跟踪调试。

    正则表达式

            以前我们用grep在一个文件中找出包含某些字符串的行,比如在头文件中找出一个宏定义。其实grep还可以找出符合某个模式(Pattern)的一类字符串。例如找出所有符合xxxxx@xxxx.xxx模式的字符串(也就是email地址),要求x字符可以是字母、数字、下划线、小数点或减号,email地址的每一部分可以有一个或多个x字符,例如abc.d@ef.com、1_2@987-6.54,当然符合这个模式的不全是合法的email地址,但至少可以做一次初步筛选,筛掉a.b、c@d等肯定不是email地址的字符串。再比如,找出所有符合yyy.yyy.yyy.yyy模式的字符串(也就是IP地址),要求y是0-9的数字,IP地址的每一部分可以有1-3个y字符。

           如果要用grep查找一个模式,如何表示这个模式,这一类字符串,而不是一个特定的字符串呢?从这两个简单的例子可以看出,要表示一个模式至少应该包含以下信息:

            字符类(Character Class):如上例的x和y,它们在模式中表示一个字符,但是取值范围是一类字符中的任意一个。

           数量限定符(Quantifier): 邮件地址的每一部分可以有一个或多个x字符,IP地址的每一部分可以有1-3个y字符。

    各种字符类以及普通字符之间的位置关系:例如邮件地址分三部分,用普通字符@和.隔开,IP地址分四部分,用.隔开,每一部分都可以用字符类和数量限定符描述。为了表示位置关系,还有位置限定符(Anchor)的概念,将在下面介绍。

           规定一些特殊语法表示字符类、数量限定符和位置关系,然后用这些特殊语法和普通字符一起表示一个模式,这就是正则表达式(Regular Expression)。例如email地址的正则表达式可以写成[a-zA-Z0-9.-]+@[a-zA-Z0-9.-]+.[a-zA-Z0-9_.-]+,IP地址的正则表达式可以写成[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}。下一节介绍正则表达式的语法,我们先看看正则表达式在grep中怎么用。例如有这样一个文本文件testfile:

    192.168.1.1

    1234.234.04.5678

    123.4234.045.678

    abcde

    $ egrep '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' testfile

    192.168.1.1

    1234.234.04.5678

            egrep相当于grep -E,表示采用Extended正则表达式语法。grep的正则表达式有Basic和Extended两种规范,它们之间的区别下一节再解释。另外还有fgrep命令,相当于grep -F,表示只搜索固定字符串而不搜索正则表达式模式,不会按正则表达式的语法解释后面的参数。

            注意正则表达式参数用单引号括起来了,因为正则表达式中用到的很多特殊字符在Shell中也有特殊含义(例如),只有用单引号括起来才能保证这些字符原封不动地传给grep命令,而不会被Shell解释掉。

          192.168.1.1符合上述模式,由三个.隔开的四段组成,每段都是1到3个数字,所以这一行被找出来了,可为什么1234.234.04.5678也被找出来了呢?因为grep找的是包含某一模式的行,这一行包含一个符合模式的字符串234.234.04.567。相反,123.4234.045.678这一行不包含符合模式的字符串,所以不会被找出来。

            grep是一种查找过滤工具,正则表达式在grep中用来查找符合模式的字符串。其实正则表达式还有一个重要的应用是验证用户输入是否合法,例如用户通过网页表单提交自己的email地址,就需要用程序验证一下是不是合法的email地址,这个工作可以在网页的Javascript中做,也可以在网站后台的程序中做,例如PHP、Perl、Python、Ruby、Java或C,所有这些语言都支持正则表达式,可以说,目前不支持正则表达式的编程语言实在很少见。除了编程语言之外,很多UNIX命令和工具也都支持正则表达式,例如grep、vi、sed、awk、emacs等等。“正则表达式”就像“变量”一样,它是一个广泛的概念,而不是某一种工具或编程语言的特性。

    基本语法

           我们知道C的变量和Shell脚本变量的定义和使用方法很不相同,表达能力也不相同,C的变量有各种类型,而Shell脚本变量都是字符串。同样道理,各种工具和编程语言所使用的正则表达式规范的语法并不相同,表达能力也各不相同,有的正则表达式规范引入很多扩展,能表达更复杂的模式,但各种正则表达式规范的基本概念都是相通的。本节介绍egrep(1)所使用的正则表达式,它大致上符合POSIX正则表达式规范,详见regex(7)(看这个man page对你的英文绝对是很好的锻炼)。希望读者仿照上一节的例子,一边学习语法,一边用egrep命令做实验。

    字符类

    数量限定符

              再次注意grep找的是包含某一模式的行,而不是完全匹配某一模式的行。

              例如有如下文本:

    aaabc

    aad

    efg

           查找a*这个模式的结果。会发现,三行都被找了出来。

    $ egrep 'a*' testfile

    aaabc

    aad

    efg

            a匹配0个或多个a,而第三行包含0个a,所以也包含了这一模式。单独用a这样的正则表达式做查找没什么意义,一般是把a*作为正则表达式的一部分来用。

    位置限定符

          位置限定符可以帮助grep更准确地查找。

          例如上一节我们用[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}查找IP地址,找到这两行

    192.168.1.1

    1234.234.04.5678

           如果用^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$查找,就可以把1234.234.04.5678这一行过滤掉了。

    其它特殊字符

    Basic正则和Extended正则区别

            以上介绍的是grep正则表达式的Extended规范,Basic规范也有这些语法,只是字符?+{}|()应解释为普通字符,要表示上述特殊含义则需要加\转义。如果用grep而不是egrep,并且不加-E参数,则应该遵照Basic规范来写正则表达式。

    grep

    1、作用

               Linux系统中grep命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹 配的行打印出来。grep全称是Global Regular Expression Print,表示全局正则表达式版本,它的使用权限是所有用户。

              grep家族包括grep、egrep和fgrep。egrep和fgrep的命令只跟grep有很小不同。egrep是grep的扩展,支持更多的re元字符, fgrep就是fixed grep或fast grep,它们把所有的字母都看作单词,也就是说,正则表达式中的元字符表示回其自身的字面意义,不再特殊。linux使用GNU版本的grep。它功能更强,可以通过-G、-E、-F命令行选项来使用egrep和fgrep的功能。

    2、格式及主要参数

    grep [options]

    主要参数:  grep --help可查看

                     -c:只输出匹配行的计数。

                      -i:不区分大小写。

                      -h:查询多文件时不显示文件名。

                       -l:查询多文件时只输出包含匹配字符的文件名。

                      -n:显示匹配行及 行号。

                      -s:不显示不存在或无匹配文本的错误信息。

                     -v:显示不包含匹配文本的所有行。

                     --color=auto :可以将找到的关键词部分加上颜色的显示。

            pattern正则表达式主要参数:

    \: 忽略正则表达式中特殊字符的原有含义。

    ^:匹配正则表达式的开始行。

    $: 匹配正则表达式的结束行。

    \<:从匹配正则表达 式的行开始。

    \>:到匹配正则表达式的行结束。

    [ ]:单个字符,如[A]即A符合要求 。

    [ - ]:范围,如[A-Z],即A、B、C一直到Z都符合要求 。

    .:所有的单个字符。

    *:所有字符,长度可以为0。

    3、grep命令使用简单实例

    itcast$ grep ‘test’ d*

    显示所有以d开头的文件中包含 test的行

     

    itcast $ grep ‘test’ aa bb cc

    显示在aa,bb,cc文件中匹配test的行。

     

    itcast $ grep ‘[a-z]\{5\}’ aa

    显示所有包含每个字符串至少有5个连续小写字符的字符串的行。

     

    itcast $ grep ‘w\(es\)t.*\1′ aa

    如果west被匹配,则es就被存储到内存中,并标记为1,然后搜索任意个字符(.*),这些字符后面紧跟着 另外一个es(\1),找到就显示该行。如果用egrep或grep -E,就不用”\”号进行转义,直接写成’w(es)t.*\1′就可以了。

    4、grep命令使用复杂实例

    明确要求搜索子目录:

    grep -r

    或忽略子目录

    grep -d skip

    如果有很多输出时,您可以通过管道将其转到’less’上阅读:

    itcast$ grep magic /usr/src/Linux/Documentation/* | less

    这样,您就可以更方便地阅读。

    有一点要注意,您必需提供一个文件过滤方式(搜索全部文件的话用 *)。如果您忘了,’grep’会一直等着,直到该程序被中断。如果您遇到了这样的情况,按 ,然后再试。

    下面还有一些有意思的命令行参数:

    grep -i pattern files :不区分大小写地搜索。默认情况区分大小写,

    grep -l pattern files :只列出匹配的文件名,

    grep -L pattern files :列出不匹配的文件名,

    grep -w pattern files :只匹配整个单词,而不是字符串的一部分(如匹配’magic’,而不是’magical’),

    grep -C number pattern files :匹配的上下文分别显示[number]行,

    grep pattern1 | pattern2 files :显示匹配 pattern1 或 pattern2 的行,

    例如:grep "abc\|xyz" testfile 表示过滤包含abc或xyz的行

    grep pattern1 files | grep pattern2 :显示既匹配 pattern1 又匹配 pattern2 的行。

    grep -n pattern files 即可显示行号信息

    grep -c pattern files 即可查找总行数

    还有些用于搜索的特殊符号:\< 和 \> 分别标注单词的开始与结尾。

    例如:

    grep man * 会匹配 ‘Batman’、’manic’、’man’等,

    grep ‘\<man’ * 匹配’manic’和’man’,但不是’Batman’,

    grep ‘\<man\>’ 只匹配’man’,而不是’Batman’或’manic’等其他的字符串。

    ‘^’: 指匹配的字符串在行首,

    ‘$’: 指匹配的字符串在行 尾,

    find

    由于find具有强大的功能,所以它的选项也很多,其中大部分选项都值得我们花时间来了解一下。即使系统中含有网络文件系统( NFS),find命令在该文件系统中同样有效,只要你具有相应的权限。

    在运行一个非常消耗资源的find命令时,很多人都倾向于把它放在后台执行,因为遍历一个大的文件系统可能会花费很长的时间(这里是指30G字节以上的文件系统)。

    一、find 命令格式

    1、find命令的一般形式为

    find pathname -options [-print -exec -ok ...]

    2、find命令的参数;

    pathname: find命令所查找的目录路径。例如用.来表示当前目录,用/来表示系统根目录,递归查找。

    -print: find命令将匹配的文件输出到标准输出。

    -exec: find命令对匹配的文件执行该参数所给出的shell命令。相应命令的形式为'command' {} \;,注意{}内部无空格,和\;之间含有一个空格分隔符。

    -ok: 和-exec的作用相同,只不过以一种更为安全的模式来执行该参数所给出的shell命令,在执行每一个命令之前,都会给出提示,让用户来确定是否执行。

    3、find命令选项

    -name 按照文件名查找文件。

    -perm 按照文件权限来查找文件。

    -prune 使用这一选项可以使find命令不在当前指定的目录中查找,如果同时使用-depth选项,那么-prune将被

    find命令忽略。

    -user 按照文件属主来查找文件。

    -group 按照文件所属的组来查找文件。

    -mtime -n +n 按照文件的更改时间来查找文件,-n表示文件更改时间距现在n天以内,+n表示文件更改时间距现在

    n天以前。find命令还有-atime和-ctime 选项,但它们都和-m time选项。

    -nogroup 查找无有效所属组的文件,即该文件所属的组在/etc/groups中不存在。

    -nouser 查找无有效属主的文件,即该文件的属主在/etc/passwd中不存在。

    -newer file1 ! file2 查找更改时间比文件file1新但比文件file2旧的文件。

    -type 查找某一类型的文件,诸如:

    b - 块设备文件。

    d - 目录。

    c - 字符设备文件。

    p - 管道文件。

    l - 符号链接文件。

    f - 普通文件。

    -size n:[c] 查找文件长度为n块的文件,带有c时表示文件长度以字节计。

    -depth 在查找文件时,首先查找当前目录中的文件,然后再在其子目录中查找。

    -fstype 查找位于某一类型文件系统中的文件,这些文件系统类型通常可以在配置文件/etc/fstab中找到,该配

    置文件中包含了本系统中有关文件系统的信息。

    -mount 在查找文件时不跨越文件系统mount点。

    -follow 如果find命令遇到符号链接文件,就跟踪至链接所指向的文件。

    另外,下面三个的区别:

    -amin n 查找系统中最后N分钟访问的文件

    -atime n 查找系统中最后n*24小时访问的文件

    -cmin n 查找系统中最后N分钟被改变文件状态的文件

    -ctime n 查找系统中最后n*24小时被改变文件状态的文件

    -mmin n 查找系统中最后N分钟被改变文件数据的文件

    -mtime n 查找系统中最后n*24小时被改变文件数据的文件

    4、使用exec或ok来执行shell命令

            使用find时,只要把想要的操作写在一个文件里,就可以用exec来配合find查找,很方便的。

             在有些操作系统中只允许-exec选项执行诸如ls或ls -l这样的命令。大多数用户使用这一选项是为了查找旧文件并删除它们。建议在真正执行rm命令删除文件之前,最好先用ls命令看一下,确认它们是所要删除的文件。

             exec选项后面跟随着所要执行的命令或脚本,然后是一对儿{},一个空格和一个\,最后是一个分号。为了使用exec选项,必须要同时使用print选项。如果验证一下find命令,会发现该命令只输出从当前路径起的相对路径及文件名。

             例如:为了用ls -l命令列出所匹配到的文件,可以把ls -l命令放在find命令的-exec选项中

    # find . -type f -exec ls -l {} \;

            上面的例子中,find命令匹配到了当前目录下的所有普通文件,并在-exec选项中使用ls -l命令将它们列出。

           在/logs目录中查找更改时间在5日以前的文件并删除它们:

    $ find logs -type f -mtime +5 -exec rm {} \;

           记住:在shell中用任何方式删除文件之前,应当先查看相应的文件,一定要小心!当使用诸如mv或rm命令时,可以使用-exec选项的安全模式。它将在对每个匹配到的文件进行操作之前提示你。

           在下面的例子中, find命令在当前目录中查找所有文件名以.LOG结尾、更改时间在5日以上的文件,并删除它们,只不过在删除之前先给出提示。

    $ find . -name "*.conf" -mtime +5 -ok rm {} \;

    < rm ... ./conf/httpd.conf > ? n

            按y键删除文件,按n键不删除。

           任何形式的命令都可以在-exec选项中使用。

          在下面的例子中我们使用grep命令。find命令首先匹配所有文件名为“ passwd*”的文件,例如passwd、passwd.old、passwd.bak,然后执行grep命令看看在这些文件中是否存在一个itcast用户。

    # find /etc -name "passwd*" -exec grep "itcast" {} \;

    itcast:x:1000:1000::/home/itcast:/bin/bash

     

    二、find命令的例子;

    1、查找当前用户主目录下的所有文件:

    下面两种方法都可以使用

    $ find $HOME -print

    $ find ~ -print

    2、让当前目录中文件属主具有读、写权限,并且文件所属组的用户和其他用户具有读权限的文件;

    $ find . -type f -perm 644 -exec ls -l {} \;

    3、为了查找系统中所有文件长度为0的普通文件,并列出它们的完整路径;

    $ find / -type f -size 0 -exec ls -l {} \;

    4、查找/var/logs目录中更改时间在7日以前的普通文件,并在删除之前询问它们;

    $ find /var/logs -type f -mtime +7 -ok rm {} \;

    5、为了查找系统中所有属于root组的文件;

    $find . -group root -exec ls -l {} \;

    6、find命令将删除当目录中访问时间在7日以来、含有数字后缀的admin.log文件。

    该命令只检查三位数字,所以相应文件的后缀不要超过999。先建几个admin.log*的文件 ,才能使用下面这个命令

    $ find . -name "admin.log[0-9][0-9][0-9]" -atime -7 -ok rm {} \;

    7、为了查找当前文件系统中的所有目录并排序;

    $ find . -type d | sort

    三、xargs

    xargs - build and execute command lines from standard input

           在使用find命令的-exec选项处理匹配到的文件时, find命令将所有匹配到的文件一起传递给exec执行。但有些系统对能够传递给exec的命令长度有限制,这样在find命令运行几分钟之后,就会出现 溢出错误。错误信息通常是“参数列太长”或“参数列溢出”。这就是xargs命令的用处所在,特别是与find命令一起使用。

           find命令把匹配到的文件传递给xargs命令,而xargs命令每次只获取一部分文件而不是全部,不像-exec选项那样。这样它可以先处理最先获取的一部分文件,然后是下一批,并如此继续下去。

            在有些系统中,使用-exec选项会为处理每一个匹配到的文件而发起一个相应的进程,并非将匹配到的文件全部作为参数一次执行;这样在有些情况下就会出现进程过多,系统性能下降的问题,因而效率不高;

            而使用xargs命令则只有一个进程。另外,在使用xargs命令时,究竟是一次获取所有的参数,还是分批取得参数,以及每一次获取参数的数目都会根据该命令的选项及系统内核中相应的可调参数来确定。

            来看看xargs命令是如何同find命令一起使用的,并给出一些例子。

            下面的例子查找系统中的每一个普通文件,然后使用xargs命令来测试它们分别属于哪类文件

    #find . -type f -print | xargs file

    在当前目录下查找所有用户具有读、写和执行权限的文件,并收回相应的写权限:

    # ls -l

    # find . -perm -7 -print | xargs chmod o-w

    # ls -l

    用grep命令在所有的普通文件中搜索hello这个词:

    # find . -type f -print | xargs grep "hello"

    用grep命令在当前目录下的所有普通文件中搜索hello这个词:

    # find . -name \* -type f -print | xargs grep "hello"

             注意,在上面的例子中, \用来取消find命令中的*在shell中的特殊含义。

             find命令配合使用exec和xargs可以使用户对所匹配到的文件执行几乎所有的命令。

    四、find 命令的参数

               下面是find一些常用参数的例子,有用到的时候查查就行了,也可以用man。

    1、使用name选项

                文件名选项是find命令最常用的选项,要么单独使用该选项,要么和其他选项一起使用。

                可以使用某种文件名模式来匹配文件,记住要用引号将文件名模式引起来。

                不管当前路径是什么,如果想要在自己的根目录HOME中查找文件名符合∗.txt的文件,使用‘~’作为 ‘pathname’ 的参数,波浪号代表了你的HOME目录。

    $ find ~ -name "*.txt" -print

    想要在当前目录及子目录中查找所有的‘ *.txt’文件,可以用:

    $ find . -name "*.txt" -print

    想要的当前目录及子目录中查找文件名以一个大写字母开头的文件,可以用:

    $ find . -name "[A-Z]*" -print

    想要在/etc目录中查找文件名以host开头的文件,可以用:

    $ find /etc -name "host*" -print

    想要查找$HOME目录中的文件,可以用:

    $ find ~ -name "*" -print 或find . –print

    要想让系统高负荷运行,就从根目录开始查找所有的文件:

    $ find / -name "*" -print

    如果想在当前目录查找文件名以两个小写字母开头,跟着是两个数字,最后是.txt的文件,下面的命令就能够返回例如名为ax37.txt的文件:

    $find . -name "[a-z][a-z][0-9][0-9].txt" -print

    2、用perm选项

             按照文件权限模式用-perm选项,按文件权限模式来查找文件的话。最好使用八进制的权限表示法。

            如在当前目录下查找文件权限位为755的文件,即文件属主可以读、写、执行,其他用户可以读、执行的文件,可以用:

    $ find . -perm 755 -print

    还有一种表达方法:在八进制数字前面要加一个横杠-,表示都匹配,如-007就相当于777,-006相当于666

    # ls -l

    # find . -perm 006

    # find . -perm -006

    -perm mode:文件许可正好符合mode

    -perm +mode:文件许可部分符合mode

    -perm -mode: 文件许可完全符合mode

    3、忽略某个目录

           如果在查找文件时希望忽略某个目录,因为你知道那个目录中没有你所要查找的文件,那么可以使用-prune选项来指出需要忽略的目录。在使用-prune选项时要当心,因为如果你同时使用了-depth选项,那么-prune选项就会被find命令忽略。

            如果希望在/apps目录下查找文件,但不希望在/apps/bin目录下查找,可以用:

    $ find /apps -path "/apps/bin" -prune -o -print

    4、使用find查找文件的时候怎么避开某个文件目录

    比如要在/home/itcast目录下查找不在dir1子目录之内的所有文件

    find /home/itcast -path "/home/itcast/dir1" -prune -o -print

    避开多个文件夹

    find /home \( -path /home/itcast/f1 -o -path /home/itcast/f2 \) -prune -o -print

    注意(前的\,注意(后的空格。

    5、使用user和nouser选项

    按文件属主查找文件,如在$HOME目录中查找文件属主为itcast的文件,可以用:

    $ find ~ -user itcast -print

    在/etc目录下查找文件属主为uucp的文件:

    $ find /etc -user uucp -print

           为了查找属主帐户已经被删除的文件,可以使用-nouser选项。这样就能够找到那些属主在/etc/passwd文件中没有有效帐户的文件。在使用-nouser选项时,不必给出用户名;find命令能够为你完成相应的工作。

          例如,希望在/home目录下查找所有的这类文件,可以用:

    $ find /home -nouser -print

    6、使用group和nogroup选项

    就像user和nouser选项一样,针对文件所属于的用户组, find命令也具有同样的选项,为了在/apps目录下查找属于itcast用户组的文件,可以用:

    $ find /apps -group itcast -print

    要查找没有有效所属用户组的所有文件,可以使用nogroup选项。下面的find命令从文件系统的根目录处查找这样的文件

    $ find / -nogroup -print

    7、按照更改时间或访问时间等查找文件

            如果希望按照更改时间来查找文件,可以使用mtime,atime或ctime选项。如果系统突然没有可用空间了,很有可能某一个文件的长度在此期间增长迅速,这时就可以用mtime选项来查找这样的文件。

           用减号-来限定更改时间在距今n日以内的文件,而用加号+来限定更改时间在距今n日以前的文件。

          希望在系统根目录下查找更改时间在5日以内的文件,可以用:

    $ find / -mtime -5 -print

    为了在/var/adm目录下查找更改时间在3日以前的文件,可以用:

    $ find /var/adm -mtime +3 -print

    8、查找比某个文件新或旧的文件

    如果希望查找更改时间比某个文件新但比另一个文件旧的所有文件,可以使用-newer选项。它的一般形式为:

    newest_file_name ! oldest_file_name

    其中,!是逻辑非符号。

    9、使用type选项

    在/etc目录下查找所有的目录,可以用:

    $ find /etc -type d -print

    在当前目录下查找除目录以外的所有类型的文件,可以用:

    $ find . ! -type d -print

    在/etc目录下查找所有的符号链接文件,可以用

    $ find /etc -type l -print

    10、使用size选项

           可以按照文件长度来查找文件,这里所指的文件长度既可以用块(block)来计量,也可以用字节来计量。以字节计量文件长度的表达形式为N c;以块计量文件长度只用数字表示即可。

          在按照文件长度查找文件时,一般使用这种以字节表示的文件长度,在查看文件系统的大小,因为这时使用块来计量更容易转换。 在当前目录下查找文件长度大于1 M字节的文件:

    $ find . -size +1000000c -print

    在/home/apache目录下查找文件长度恰好为100字节的文件:

    $ find /home/apache -size 100c -print

    在当前目录下查找长度超过10块的文件(一块等于512字节):

    $ find . -size +10 -print

    11、使用depth选项

           在使用find命令时,可能希望先匹配所有的文件,再在子目录中查找。使用depth选项就可以使find命令这样做。这样做的一个原因就是,当在使用find命令向磁带上备份文件系统时,希望首先备份所有的文件,其次再备份子目录中的文件。

            在下面的例子中, find命令从文件系统的根目录开始,查找一个名为CON.FILE的文件。

            它将首先匹配所有的文件然后再进入子目录中查找。

    $ find / -name "CON.FILE" -depth -print

    12、使用mount选项

             在当前的文件系统中查找文件(不进入其他文件系统),可以使用find命令的mount选项。

             从当前目录开始查找位于本文件系统中文件名以XC结尾的文件:

    $ find . -name "*.XC" -mount -print

    练习:请找出你10天内所访问或修改过的.c和.cpp文件。

    sed

             sed意为流编辑器(Stream Editor),在Shell脚本和Makefile中作为过滤器使用非常普遍,也就是把前一个程序的输出引入sed的输入,经过一系列编辑命令转换为另一种格式输出。sed和vi都源于早期UNIX的ed工具,所以很多sed命令和vi的末行命令是相同的。

    sed命令行的基本格式为

    sed option 'script' file1 file2 ...

    sed option -f scriptfile file1 file2 ...

    选项含义:

    --version 显示sed版本。

    --help 显示帮助文档。

    -n,--quiet,--silent 静默输出,默认情况下,sed程序在所有的脚本指令执行完毕后,将自动打印模式空间中的内容,这些选项可以屏蔽自动打印。

    -e script 允许多个脚本指令被执行。

    -f script-file,

    --file=script-file 从文件中读取脚本指令,对编写自动脚本程序来说很棒!

    -i,--in-place 直接修改源文件,经过脚本指令处理后的内容将被输出至源文件(源文件被修改)慎用!

    -l N, --line-length=N 该选项指定l指令可以输出的行长度,l指令用于输出非打印字符。

    --posix 禁用GNU sed扩展功能。

    -r, --regexp-extended 在脚本指令中使用扩展正则表达式

    -s, --separate 默认情况下,sed将把命令行指定的多个文件名作为一个长的连续的输入流。而GNU sed则允许把他们当作单独的文件,这样如正则表达式则不进行跨文件匹配。

    -u, --unbuffered 最低限度的缓存输入与输出。

    以上仅是sed程序本身的选项功能说明,至于具体的脚本指令(即对文件内容做的操作)后面我们会详细描述,这里就简单介绍几个脚本指令操作作为sed程序的例子。

    a, append 追加

    i, insert 插入

    d, delete 删除

    s, substitution 替换

    如:$ sed "2a itcast" ./testfile 在输出testfile内容的第二行后添加"itcast"。

    $ sed "2,5d" testfile

           sed处理的文件既可以由标准输入重定向得到,也可以当命令行参数传入,命令行参数可以一次传入多个文件,sed会依次处理。sed的编辑命令可以直接当命令行参数传入,也可以写成一个脚本文件然后用-f参数指定,编辑命令的格式为:

    /pattern/action

              其中pattern是正则表达式,action是编辑操作。sed程序一行一行读出待处理文件,如果某一行与pattern匹配,则执行相应的action,如果一条命令没有pattern而只有action,这个action将作用于待处理文件的每一行。

    常用sed命令

    /pattern/p 打印匹配pattern的行

    /pattern/d 删除匹配pattern的行

    /pattern/s/pattern1/pattern2/ 查找符合pattern的行,将该行第一个匹配pattern1的字符串替换为pattern2

    /pattern/s/pattern1/pattern2/g 查找符合pattern的行,将该行所有匹配pattern1的字符串替换为pattern2

            使用p命令需要注意,sed是把待处理文件的内容连同处理结果一起输出到标准输出的,因此p命令表示除了把文件内容打印出来之外还额外打印一遍匹配pattern的行。比如一个文件testfile的内容是

    123

    abc

    456

    打印其中包含abc的行

    $ sed '/abc/p' testfile

    123

    abc

    abc

    456

    要想只输出处理结果,应加上-n选项,这种用法相当于grep命令

    $ sed -n '/abc/p' testfile

    abc

    使用d命令就不需要-n参数了,比如删除含有abc的行

    $ sed '/abc/d' testfile

    123

    456

    注意,sed命令不会修改原文件,删除命令只表示某些行不打印输出,而不是从原文件中删去。

    使用查找替换命令时,可以把匹配pattern1的字符串复制到pattern2中,比如:

    $ sed 's/bc/-&-/' testfile

    123

    a-bc-

    456

    pattern2中的&表示原文件的当前行中与pattern1相匹配的字符串

    再比如:

    $ sed 's/\([0-9]\)\([0-9]\)/-\1-~\2~/' testfile

    -1-~2~3

    abc

    -4-~5~6

    pattern2中的\1表示与pattern1的第一个()括号相匹配的内容,\2表示与pattern1的第二个()括号相匹配的内容。sed默认使用Basic正则表达式规范,如果指定了-r选项则使用Extended规范,那么()括号就不必转义了。如:

    sed -r 's/([0-9])([0-9])/-\1-~\2~/' out.sh

    替换结束后,所有行,含有连续数字的第一个数字前后都添加了“-”号;第二个数字前后都添加了“~”号。

    可以一次指定多条不同的替换命令,用“;”隔开:

    $ sed 's/yes/no/;s/static/dhcp/' ./testfile

    注:使用分号隔开指令。

    也可以使用 -e 参数来指定不同的替换命令,有几个替换命令需添加几个 -e 参数:

    $ sed -e 's/yes/no/' -e 's/static/dhcp/' testfile

    注:使用-e选项。

    如果testfile的内容是

    <html><head><title>Hello World</title></head>

    <body>Welcome to the world of regexp!</body></html>

    现在要去掉所有的HTML标签,使输出结果为:

    Hello World

    Welcome to the world of regexp!

    怎么做呢?如果用下面的命令

    $ sed 's/<.*>//g' testfile

    结果是两个空行,把所有字符都过滤掉了。这是因为,正则表达式中的数量限定符会匹配尽可能长的字符串,这称为贪心的(Greedy)。比如sed在处理第一行时,<.*>匹配的并不是<html>或<head>这样的标签,而是

    <html><head><title>Hello World</title>

    这样一整行,因为这一行开头是<,中间是若干个任意字符,末尾是>。那么这条命令怎么改才对呢?留给同学们思考练习。

    awk

           sed以行为单位处理文件,awk比sed强的地方在于不仅能以行为单位还能以列为单位处理文件。awk缺省的行分隔符是换行,缺省的列分隔符是连续的空格和Tab,但是行分隔符和列分隔符都可以自定义,比如/etc/passwd文件的每一行有若干个字段,字段之间以:分隔,就可以重新定义awk的列分隔符为:并以列为单位处理这个文件。awk实际上是一门很复杂的脚本语言,还有像C语言一样的分支和循环结构,但是基本用法和sed类似,awk命令行的基本形式为:

    awk option 'script' file1 file2 ...

    awk option -f scriptfile file1 file2 ...

             和sed一样,awk处理的文件既可以由标准输入重定向得到,也可以当命令行参数传入,编辑命令可以直接当命令行参数传入,也可以用-f参数指定一个脚本文件,编辑命令的格式为:

    /pattern/{actions}

    condition{actions}

          和sed类似,pattern是正则表达式,actions是一系列操作。awk程序一行一行读出待处理文件,如果某一行与pattern匹配,或者满足condition条件,则执行相应的actions,如果一条awk命令只有actions部分,则actions作用于待处理文件的每一行。比如文件testfile的内容表示某商店的库存量:

    ProductA 30

    ProductB 76

    ProductC 55

    打印每一行的第二列:

    $ awk '{print $2;}' testfile

    30

    76

    55

           自动变量$1、$2分别表示第一列、第二列等,类似于Shell脚本的位置参数,而$0表示整个当前行。再比如,如果某种产品的库存量低于75则在行末标注需要订货:

    $ awk '$2<75 {printf "%s\t%s\n", $0, "REORDER";} $2>=75 {print $0;}' testfile

    ProductA 30 REORDER

    ProductB 76

    ProductC 55 REORDER

           可见awk也有和C语言非常相似的printf函数。awk命令的condition部分还可以是两个特殊的condition-BEGIN和END,对于每个待处理文件,BEGIN后面的actions在处理整个文件之前执行一次,END后面的actions在整个文件处理完之后执行一次。

    awk命令可以像C语言一样使用变量(但不需要定义变量),比如统计一个文件中的空行数

    $ awk '/^ *$/ {x=x+1;} END {print x;}' testfile

    就像Shell的环境变量一样,有些awk变量是预定义的有特殊含义的:

    awk常用的内建变量

    FILENAME 当前输入文件的文件名,该变量是只读的

    NR 当前行的行号,该变量是只读的,R代表record

    NF 当前行所拥有的列数,该变量是只读的,F代表field

    OFS 输出格式的列分隔符,缺省是空格

    FS 输入文件的列分融符,缺省是连续的空格和Tab

    ORS 输出格式的行分隔符,缺省是换行符

    RS 输入文件的行分隔符,缺省是换行符

    例如打印系统中的用户帐号列表

    $ awk 'BEGIN {FS=":"} {print $1;}' /etc/passwd

    awk也可以像C语言一样使用if/else、while、for控制结构。可自行扩展学习。

    C程序中使用正则

            POSIX规定了正则表达式的C语言库函数,详见regex(3)。我们已经学习了很多C语言库函数的用法,读者应该具备自己看懂man手册的能力了。本章介绍了正则表达式在grep、sed、awk中的用法,学习要能够举一反三,请读者根据regex(3)自己总结正则表达式在C语言中的用法,写一些简单的程序,例如验证用户输入的IP地址或email地址格式是否正确。

            C语言处理正则表达式常用的函数有regcomp()、regexec()、regfree()和regerror(),一般分为三个步骤,如下所示:

            C语言中使用正则表达式一般分为三步:

    编译正则表达式 regcomp()

    匹配正则表达式 regexec()

    释放正则表达式 regfree()

           下边是对三个函数的详细解释

            这个函数把指定的正则表达式pattern编译成一种特定的数据格式compiled,这样可以使匹配更有效。函数regexec 会使用这个数据在目标文本串中进行模式匹配。执行成功返回0。

    int regcomp (regex_t *compiled, const char *pattern, int cflags)

     

             regex_t 是一个结构体数据类型,用来存放编译后的正则表达式,它的成员re_nsub 用来存储正则表达式中的子正则表达式的个数,子正则表达式就是用圆括号包起来的部分表达式。

             pattern 是指向我们写好的正则表达式的指针。

            cflags 有如下4个值或者是它们或运算(|)后的值:

                      REG_EXTENDED 以功能更加强大的扩展正则表达式的方式进行匹配。

                      REG_ICASE 匹配字母时忽略大小写。

                      REG_NOSUB 不用存储匹配后的结果,只返回是否成功匹配。如果设置该标志位,那么在regexec将忽略nmatch和pmatch两个参数。

                       REG_NEWLINE 识别换行符,这样'$'就可以从行尾开始匹配,'^'就可以从行的开头开始匹配。

           当我们编译好正则表达式后,就可以用regexec 匹配我们的目标文本串了,如果在编译正则表达式的时候没有指定cflags的参数为REG_NEWLINE,则默认情况下是忽略换行符的,也就是把整个文本串当作一个字符串处理。

           执行成功返回0。

           regmatch_t 是一个结构体数据类型,在regex.h中定义:

    typedef struct {

            regoff_t rm_so;

            regoff_t rm_eo;

    } regmatch_t;

           成员rm_so 存放匹配文本串在目标串中的开始位置,rm_eo 存放结束位置。通常我们以数组的形式定义一组这样的结构。因为往往我们的正则表达式中还包含子正则表达式。数组0单元存放主正则表达式位置,后边的单元依次存放子正则表达式位置。

    int regexec (regex_t *compiled, char *string, size_t nmatch, regmatch_t matchptr[], int eflags)

    compiled 是已经用regcomp函数编译好的正则表达式。

    string 是目标文本串。

    nmatch 是regmatch_t结构体数组的长度。

    matchptr regmatch_t类型的结构体数组,存放匹配文本串的位置信息。

    eflags 有两个值:

             REG_NOTBOL 让特殊字符^无作用

             REG_NOTEOL 让特殊字符$无作用

           当我们使用完编译好的正则表达式后,或者要重新编译其他正则表达式的时候,我们可以用这个函数清空compiled指向的regex_t结构体的内容,请记住,如果是重新编译的话,一定要先清空regex_t结构体。

    void regfree (regex_t *compiled)

            当执行regcomp 或者regexec 产生错误的时候,就可以调用这个函数而返回一个包含错误信息的字符串。

    size_t regerror (int errcode, regex_t *compiled, char *buffer, size_t length)

     

    errcode 是由regcomp 和 regexec 函数返回的错误代号。

    compiled 是已经用regcomp函数编译好的正则表达式,这个值可以为NULL。

    buffer 指向用来存放错误信息的字符串的内存空间。

    length 指明buffer的长度,如果这个错误信息的长度大于这个值,则regerror 函数会自动截断超出的字符串,但他仍然会返回完整的字符串的长度。所以我们可以用如下的方法先得到错误字符串的长度。

    例如: size_t length = regerror (errcode, compiled, NULL, 0);

    测试用例:

    #include <sys/types.h>
    #include <regex.h>
    #include <stdio.h>
    
    int main(int argc, char ** argv)
    {
    	if (argc != 3) {
    		printf("Usage: %s RegexString Text\n", argv[0]);
    		return 1;
    	}
    	const char * pregexstr = argv[1];
    	const char * ptext = argv[2];
    	regex_t oregex;
    	int nerrcode = 0;
    	char szerrmsg[1024] = {0};
    	size_t unerrmsglen = 0;
    	if ((nerrcode = regcomp(&oregex, pregexstr, REG_EXTENDED|REG_NOSUB)) == 0) {
    		if ((nerrcode = regexec(&oregex, ptext, 0, NULL, 0)) == 0)	{
    			printf("%s matches %s\n", ptext, pregexstr);
    			regfree(&oregex);
    			return 0;
    		}
    	}
    	unerrmsglen = regerror(nerrcode, &oregex, szerrmsg, sizeof(szerrmsg));
    	unerrmsglen = unerrmsglen < sizeof(szerrmsg) ? unerrmsglen : sizeof(szerrmsg) - 1;
    	szerrmsg[unerrmsglen] = '\0';
    	printf("ErrMsg: %s\n", szerrmsg);
    	regfree(&oregex);
    
    	return 1;
    }

    匹配网址:

    ./a.out "http:\/\/www\..*\.com" "http://www.taobao.com"

    匹配邮箱:

    ./a.out "^[a-zA-Z0-9]+@[a-zA-Z0-9]+.[a-zA-Z0-9]+" "itcast123@itcast.com"

    ./a.out "\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*" "itcast@qq.com"

    注:\w匹配一个字符,包含下划线

            匹配固话号码:请同学们自己编写。

            除了gnu提供的函数外,还常用PCRE处理正则,全称是Perl Compatible Regular Ex-pressions。从名字我们可以看出PCRE库是与Perl中正则表达式相兼容的一个正则表达式库。PCRE是免费开源的库,它是由C语言实现的,这里是它的官方主页:http://www.pcre.org/,感兴趣的朋友可以在这里了解更多的内容。 要得到PCRE库,可以从这里下载:http://sourceforge.net/projects/pcre/files/

         PCRE++是一个对PCRE库的C++封装,它提供了更加方便、易用的C++接口。这里是它的官方主页:http://www.daemon.de/PCRE,感兴趣的朋友可以在这里了解更多的内容。 要得到PCRE++库,可以从这里下载:http://www.daemon.de/PcreDownload

              另外c++中常用 boost regex。

    展开全文
  • 什么是Linux Shell

    2018-04-25 19:41:10
    什么是Linux Shell?概念:Shell是系统的用户界面,提供了用户与内核进行交互操作的一种接口。它接收用户输入的命令并把它送入内核去执行。是在Linux内核与用户之间的解释器程序,现在Linux通常指/bin/bash解释器来...

    什么是Linux Shell?

    概念:Shell是系统的用户界面,提供了用户与内核进行交互操作的一种接口。它接收用户输入的命令并把它送入内核去执行。是在Linux内核与用户之间的解释器程序,现在Linux通常指/bin/bash解释器来负责向内核翻译以及传达用户/程序指令,shell相当于操作系统的“外壳”

    Shell的使用方式

    交互式---命令行
    --人工干预。智能化程度高
    --逐条解释执行,效率底
    非交互式---脚本
    需要提前设计,智能化难度大
    批量执行,效率高

    方便在后台静悄悄地运行


    常见的Shell环境
    通过usermod,chsh更改Shell
    手动执行目标Shell程序


    shell与bash区别
    shell(解释器)
    bash(解释器的具体型号,版本)
    在linux中可以使用的shell解释器的版本
    [root@server0 ~]# cat /etc/shells
    /bin/sh
    /bin/bash
    /sbin/nologin
    /usr/bin/sh
    /usr/bin/bash
    /usr/sbin/nologin
    /bin/tcsh
    /bin/csh


    切换用户的Shell环境

    若需要临时使用另一种Shell环境,可以直接执行对应的Shell解释器程序,比如只要执行Ksh可以切换Ksh命令行环境。


    为什么lincx默认的shell的类型是bash?

    bash基本特性的一些优点
    快捷键,Tab键补齐
    命令历史
    命令别名
    标准输入输出
    重定向
    管道操作
    永久的更改解释器 usermod -s /bin/ksh zhangsan

     查看对应用户使用的解释器:grep ‘zhangsan’ /etc/passwd

    代码解释:

    [root@server0 ~]# useradd -s /bin/ksh zhangsan(创建zhangsan用户并为它设置默认解释器ksh)

    [root@server0 ~]# grep 'zhangsan' /etc/passwd
    zhangsan:x:1001:1001::/home/zhangsan:/bin/ksh

    2.历史命令-history
    默认记录了1000条
    保存位置:/.bash_history
    控制历史命令的数量:/etc/profile
    内容中:HISTSIZE=1000是控制历史记录的条数,在工作中需要更改。避免信息泄露。



    操作历史命令

    history工具
    history:查看历史命令列表
    history -c 清空历史命令
    ls ~/.bash_history


    调用历史命令

    !78 : 执行历史记录中的第78条命令
    !str 执行最近一次以str开头的历史命令




    I/O设备与重定向
    交互式硬件设备
    标准输入:从该设备接收用户输入的数据 
    标准输出:通过该设备向用户输出数据
    标准错误:通过该设备报告执行中的错误信息

    错误的与正确的信息在屏幕上输出上是不一样的


    重定向输入   < 将文本来源由键盘改为指定的文件
    重定向输出 > 将命令行的正常执行输出保存到文件,而不是直接显示在屏幕上
                     >> 与>类似,但操作是追加而不是覆盖
          重定向错误           2>将命令行的执行错误信息保存到文件
                                    2>> 与2>类似,但操作是追加而不是覆盖
    混合重定向 &> 相当于>和2>,覆盖到同一个文件


    展开全文
  • 这几天博主刻苦研究了下 Shell 编程,感觉 Shell 编程是一门非常强大的语言。 下面就Shell 编程的几个点进行下讲解: 0.Shell 编程的基本含义 (一个最简单的Shell 程序 / Shell 编程的注意点) 1.Shell 的内置...

    这几天博主刻苦研究了下 Shell 编程,感觉 Shell 编程是一门非常强大的语言。


    下面就Shell 编程的几个点进行下讲解:


    一.Shell 编程的基本含义 (一个最简单的Shell 程序 /   Shell 编程的注意点)

    二.Shell 的变量 与 变量的赋值 /  变量的作用域

    三.Shell 的内置变量 与 参数 (Shell脚本 参数、Shell函数 参数)

    四.Shell 的数组

    五.Shell 的函数

    六. Shell 的 条件控制 if /  if elif / if  elif  else / case in casc

    七. Shell 的 循环控制  while do / until / for / for in / 

    八. Shell 的数字运算


    一.Shell 编程的基本含义:

    我理解的Shell 编程可以理解为一堆Shell 命令的集合


    No.0 一个最简单的Shell 程序

    #!/bin/bash
    a="123456789123456789"
    b=$((a+13))
    echo $b
    

    上面就是一个简单的Shell 程序,主要功能是进行大数加法。

    我知道如果有一些编程的经验的程序猿肯定对这个程序不屑一顾,下面给出一个稍微复杂一点的程序。


    No.1 稍微复杂一点的Shell程序

    #!/bin/bash
    
    sour="data.txt"
    out="data_new.txt"
    
    ct=$(wc -l $sour | awk '{print $1}')
    echo "source file lines : $ct"
    lines=$(( ($RANDOM%10+90)*ct/100 ))
    echo "should be change lines : $lines"
    
    
    awk -v num=$lines  -F'&' '
    BEGIN{
    	srand();
    } 
    {
    	if(NR<=num)
    	{	
    		split($2,a,"=");
    		ct=int(rand()*100%9);
    		a[2]=a[2]+ct;
    		gsub(/cid=[0-9]*/,"cid="a[2],$0);
    		#$2=a[1]"="a[2];
    	}
    	print $0;
    }
    ' $sour > $out
    
    old=$(wc -l $sour | awk '{print $1}')
    new=$(wc -l $out | awk '{print $1}')
    
    echo "old file has $old lines"
    echo "new file has $new lines"
    





    通过这个Shell 程序,我们可以看到Shell 程序的一些基本特征, 也就是一些值得注意到的地方


    值得注意的点  (如果没有写过Shell,大家一定要阅读以下几点)

    1.bash 程序的典型特征

     第一行 #! /bin/bash 表示该程序由 /bin/bash 进行执行解析。 


    2.bash 程序的注释

    Shell 程序的注释用 # 进行表示, # 表示单行注释,


    3.Shell 的数字运算

     Shell 程序如果要进行数学运算,需要把计算表达式用 $(( )) 括起来, 如 lines=$(( ($RANDOM%10+90)*ct/100 ))






    二.Shell 的变量 与 变量的赋值 /  变量的作用域


    一. 基本的变量的赋值

    Shell 中的变量赋值不需要预先定义再使用,可以直接使用。(默认为空)

    如 

    a=3

    b="A String"   

    这种方式即可。


    不过有些需要注意的地方:


    1.变量与变量内容以一个等号“=”来连接


    2.等号两边不能接空格符    

    正确 a=3   错误 a = "3"

    3.变量名称只能是英文字母与数字,但是开头字符不能是数字。


    4.变量内容若有空格符可用双引号“”  或者 单引号 ‘’ 将变量内容结合起来

    若 var=lang is $LANG, 则 echo $var 可得 lang is en_us

    5.单引号内的特殊字符仅为一般字符(纯文本)

    若 ar='lang is $LANG'    则 echo $ar,  可得 lang is $LANG

    6.可用转义字符“ \ ”(反引号) 将特殊符号变成一般字符


    7.如果需要其他命令的执行结果再进行赋值

    可以使用 反引号 `命令`    或  $(命令)

    8.若该变量为了增加变量内容时,可用 $变量名 或 ${变量名} 累加内容

    如 PATH="$PATH":/home/bin

    9.若该变量需要在其他子进程执行,需要 export 使变量变成环境变量

    export PATH

    10.通常大写字符为系统默认变量。自行设置变量可以使用小写字符,方便判断


    11.取消变量的方法为使用 unset 变量名称,例如取消 myname 的设置






    二. 对于获取命令执行后的结果 的 赋值

    对于变量,经常我们要赋予摸一个命令的返回值,比如 a=(某文件的行数)   。。。。


    这时候该怎么办呢?

    有两种方法


    方法一 ` 命令` (注意该符号为反引号,即 TAB 上面的那个按键 )

    方法二   $(命令)



    三.将变量内容交-》命令处理


    主要是通过管道符进行接收


    示例

    echo sssss | grep (grep的正常处理)





    三.Shell 的内置变量 与 参数 (Shell 参数、Shell函数 参数)


    1) Shell 的内置变量

    Shell 脚本中有一些内置变量,举个例子


    $RANDOM : $RANDOM用来获取随机数

    $? :返回上一个命令执行的结果



    2)Shell 参数  、 Shell 函数参数

    Shell 脚本的参数 、Shell  函数的参数

    我们编写Shell 脚本的时候,在运行shell 脚本经常使用各种各样的参数,

    举个例子:

     tar -zxv -f filename -C target    //解压 tar.gz / .tgz 到指定文件夹


    那大家一定好奇,Shell 是如何得 filename target 这些参数的呢?

    Shell 脚本 参数可以通过以下几个变量进行获取,

    ./script  opt1 opt2 opt3

    $0 $1 $2 $3

    即 

    $0 为脚本本身的名字 ./script

    $1 为脚本后面的第一个参数

    ...


    $#  :(常用)代表后面接的参数“个数”   

    $@ :(常用)代表 “$1” "$2" "$3" "$4"

    $* : 代表 “$1c$2c$3c$4” 其中c为分割字符,默认为空格键,所以本例中代表 “$1 $2 $3 $4”



    对于Shell 里面的函数也是一样的

    $0 :Shell 脚本的名字 **这点注意***

    $1 : Shell 函数的第一个参数

    $2 : Shell 函数的第二个参数


    下面以一个比较典型的例子进行举例

    #!/bin/sh
    echo "The first parameter is $0"
    echo "The second parameter is $1"
    echo "The whole parameters is $@"
    echo "The parameters num is $#"
    arr=("$@")
    echo ${arr[0]}
    echo ${arr[1]}
    
    function a(){
    	echo "What's  this? $0;"
    	echo "What's  this? $1;"
    	echo "The whole function parameters are $@ "
    	echo "The whole function parameters are $# "
    	echo "The whole function parameters are $*"
    }
    
    
    
    a 10 11;
    






    上面的例子综合使用了变量,数组,函数,变量的作用域等知识点

    通过以上例子,我们可以看出来几点


    我总结一下,不对的地方希望读者指出

    1.$@, $#, $* 变量可以用于提取参数,这三个变量是有作用域的,

    对于整个脚本层次,

    $@, $#, $*  可以得到脚本的一些运行参数 

    对于函数层次,

    $@, $#, $* 可以的到函数的一些运行参数


    2. $0, $1, $2 ... 基本也遵循以上的规律

    唯一要注意的一点就是:$0 无论是在脚本层次  或者 函数层次 都是执行的脚本 的脚本名称。





    四.Shell 的数组

    shell 脚本中的数组使用十分简便:


    1.定义数组

    示例:

    a=(1 2 3) /        arr=("$@")

    即可定义一个数组,数组中有三个元素 1 ,2 ,3


    2. 获取数组的元素  (数组的下标是从1开始的)

    echo ${a[0]} //打印数组的第一个元素
    echo ${a[1]} //打印数组的第二个元素


    3.获取数组长度等

    数组元素个数/长度     ${#array[@]}//即得到所有元素,利用 # 得到长度。 
    同理:

    字符串的长度:  ${#str}



    五.Shell 的函数


    函数的定义只要用 function 即可 ,


    不过有一些值得注意的地方

    1.Shell 函数的返回值一定要是整数,否则会报错

    2.要得到函数的返回值,可以用 $?  来获取上一条命令执行的结果进行获取


    示例如下:


    function a(){
    	echo "What's  this? $0;"
    	echo "What's  this? $1;"
    	echo "The whole function parameters are $@ "
    	echo "The whole function parameters are $# "
    	echo "The whole function parameters are $*"
    	return 3;	#Shell 函数执行的结果必须为整数
    }
    
    
    
    # re=`a 10 11`; #re 接收不到函数执行的结果
    a 10 11 
    echo "result is $?	"



    五.Shell 的条件控制


    首先讲一个东西,判别式 [ 判断内容 ]  外面为中括号[]

    就是相当于 c/c++里面的 if ()

    注意 Linux的判断式,两端需要有空格


    一.if

    (1)

    if [ 条件判断式 ]; then

    程序段

    fi


    (2)

    if [[ 条件判断式 ]]; then

    程序段

    fi 

    尤其是在判断空字符串的时候,参考以下代码

    if [[ ${1} == "" ]];then
            echo "Please indicate the cassandra Address."
            echo "Use this cmd like this:"
            echo "./clear_data.sh 192.102.1.1"
            exit
    fi;
    
    stack-overflow 上的tips

    http://stackoverflow.com/questions/19691082/why-am-i-getting-a-unary-operator-expected-error


    二.if else

    if [ 条件判断式 ]; then

    程序段

    else

    程序段

    fi


    三.if elif else

    if [条件判断式一]; then

    程序段

    elif [条件判断式二]; then

    程序段

    else 

    程序段


    四.case esac

    case $变量 in

    "A变量")

    程序段

    ;;

    "B变量")

    程序段

    ;;

    *)

    程序段

    ;;

    esac


    ======================================================================



    示例代码


    if elif if


    #!/bin/sh
    
    read -p "Please input (Y/N) : " yn
    
    if [ "$yn" == "Y" ] || [ "$yn" == "y" ];then
    	echo "OK, contiue"
    elif [ "$yn" == "N" ] || [ "$yn" == "n" ];then
    	echo "You choose no"
    else
    	echo "Unknown choose"
    fi
    




    case esac

    #!/bin/bash
    
    #read -p "Input your choice: " choice
    case $1 in
    	"one")
    	echo "Your choice is ONE"
    	;;
    	"two")
    	echo "Your choice is TWO"
    	;;
    	"three")
    	echo "Your choice is THREE"
    	;;
    	*)
    	echo "Usage $0 (one|two|three)"
    	;;
    esac
    







    七. Shell 的 循环控制  while do / until / for / for in / 


    一. while

    while [ condition ]

    do

    程序段落

    done


    二.until

    until [ condition ]

    do

    程序段

    done


    三.for in

    for var in con1 con2 con3 ...

    do 

    程序段

    done


    四.for 

    for (( 初始值; 限制值; 执行步长))

    do

    程序段

    done


    示例代码

    until

    #!/bin/bash
    
    until [ "$yn" == "yes" ] || [ "$yn" == "YES" ];do
    	read -p "Please input yes/YES to stop this program: " yn
    done
    echo "OK! you input right selection"
    


    while 

    #!/bin/bash
    
    
    s=0
    i=0
    while (($i != 100))
    do
    	i=$(($i+1))
    	s=$(($s+$i))		
    done
    echo "The sum of 1-100 is $s"
    


    for in

    #!/bin/bash
    
    anis=(dog cat ant)
    
    for ani in ${anis[@]}
    do
    	echo "There are ${ani}s"
    done
    


    Shell使用 break 和 continue 来跳出循环。

    break命令

    break命令允许跳出所有循环(终止执行后面的所有循环)。

    下面的例子中,脚本进入死循环直至用户输入数字大于5。要跳出这个循环,返回到shell提示符下,就要使用break命令。
    1. #!/bin/bash
    2. while :
    3. do
    4. echo -n "Input a number between 1 to 5: "
    5. read aNum
    6. case $aNum in
    7. 1|2|3|4|5) echo "Your number is $aNum!"
    8. ;;
    9. *) echo "You do not select a number between 1 to 5, game is over!"
    10. break
    11. ;;
    12. esac
    13. done
    在嵌套循环中,break 命令后面还可以跟一个整数,表示跳出第几层循环。例如:
    1. break n
    表示跳出第 n 层循环。

    下面是一个嵌套循环的例子,如果 var1 等于 2,并且 var2 等于 0,就跳出循环:
    1. #!/bin/bash
    2. for var1 in 1 2 3
    3. do
    4. for var2 in 0 5
    5. do
    6. if [ $var1 -eq 2 -a $var2 -eq 0 ]
    7. then
    8. break 2
    9. else
    10. echo "$var1 $var2"
    11. fi
    12. done
    13. done
    如上,break 2 表示直接跳出外层循环。运行结果:
    1 0
    1 5

    continue命令

    continue命令与break命令类似,只有一点差别,它不会跳出所有循环,仅仅跳出当前循环。

    对上面的例子进行修改:
    1. #!/bin/bash
    2. while :
    3. do
    4. echo -n "Input a number between 1 to 5: "
    5. read aNum
    6. case $aNum in
    7. 1|2|3|4|5) echo "Your number is $aNum!"
    8. ;;
    9. *) echo "You do not select a number between 1 to 5!"
    10. continue
    11. echo "Game is over!"
    12. ;;
    13. esac
    14. done
    运行代码发现,当输入大于5的数字时,该例中的循环不会结束,语句
    1. echo "Game is over!"
    永远不会被执行。

    同样,continue 后面也可以跟一个数字,表示跳出第几层循环。

    再看一个 continue 的例子:
    1. #!/bin/bash
    2. NUMS="1 2 3 4 5 6 7"
    3. for NUM in $NUMS
    4. do
    5. Q=`expr $NUM % 2`
    6. if [ $Q -eq 0 ]
    7. then
    8. echo "Number is an even number!!"
    9. continue
    10. fi
    11. echo "Found odd number"
    12. done
    运行结果:
    Found odd number
    Number is an even number!!
    Found odd number
    Number is an even number!!
    Found odd number
    Number is an even number!!
    Found odd number




    八. Shell 的数字运算


    Shell 程序如果要进行数学运算,需要把计算表达式用 $(( )) 括起来, 如 lines=$(( ($RANDOM%10+90)*ct/100 ))




    九.一些额外的参考资料


    Shell 变量

    http://www.runoob.com/linux/linux-shell-variable.html

    Shell 中变量比较

    http://blog.csdn.net/weiyuweizhi/article/details/7488388

    http://blog.sina.com.cn/s/blog_4b2117ea0100x1i1.html




    Shell 数组

    http://www.poluoluo.com/server/201004/82896.html





    Shell函数

    http://blog.csdn.net/ljianhui/article/details/9333443

    http://www.jb51.net/article/33899.htm

    函数定义,变量作用域范围

    http://www.jb51.net/article/33899.htm




    Bash 条件判断

    http://codingstandards.iteye.com/blog/780156





    Bash循环

    http://blog.sina.com.cn/s/blog_9d074aae01012ytf.html







    字符串

    Bash字符串分割

    http://www.cnblogs.com/FlyFive/p/3640243.html








    展开全文
  • 1.$() 在 bash shell 中,$( ) 与 ` (反引号) 都是用来做命令替换用(commandsubstitution)的。 例如 version=$(uname -r)和...1. \ ` 基本上可用在全部的 unix shell 中使用,若写成 shell script ,其移植性...
  • Linux中编写Shell脚本

    2020-02-04 17:02:12
    Shell Shell脚本的执行 Shell脚本编写规范 Shell 中的变量 变量的算术运算 双小括号 (()) 数值运算命令的用法 let 运算命令的用法 expr 命令的用法 br 命令的用法 $[]符号的运算示例 Shell脚本的条件测试 ...
  • shell介绍_01shell是一个命令解析器,提供用户和机器之间的交互支持特定语法,比如逻辑判读、循环每个用户都可以有自己特定的shellCentOS7默认shell为bash(Bourne Agin Shell)还有zsh、ksh等命令历史_02history...
  • && || test
  • 说明:在进行一些性能测试的时候,有时候我们希望能计算一...1. Linux shell获取时间的相关命令 time命令:获取一个程序的执行时间,可以获取到实际运行时间以及程序在用户态和内核态分别的时间,大部分的性能测试,可
  • 本系列适合Linux初学者,属于Linux入门级教程,主要介绍了Shell的分类...Linux Shell系列教程之(一)Shell简介Linux Shell系列教程之(二)第一个Shell脚本Linux Shell系列教程之(三)Shell变量Linux Shell系列教程之
  • LINUX Shell脚本编程实例详解(一)上 ping命令测试网段内主机的2例详解 使用linux已经有10来年了但是一直止于各种服务器的搭建和环境的调优,能力不能得到提升,今天突然下定决心学习Shell脚本编程,因此以网上...
  • Linux shell终端打开方式
  • linux shell 数组 遍历
  • Linux shell脚本基础学习这里我们先来第一讲,介绍shell的语法基础,开头、注释、变量和 环境变量,向大家做一个基础的介绍,虽然不涉及具体东西,但是打好基础是以后学习轻松地前提。 1. Linux 脚本编写基础 ◆...
  • linux shell 下各种进制数据转换。 离不了echo 输出显示命令 **************************************** shell 内置各种进制表示方法非常简单。记得 base#number 即可。 base 为 2-64 赋值语句 let 或(()) 不能直接...
  • linux shell 条件判断if else, if elif else....
  • 本文是Linux Shell系列教程的第(十一)篇,更多Linux Shell教程请看:Linux Shell系列教程 在上一篇Linux Shell系列教程之(十)Shell for循环中,我们已经对Shell 循环语句的for循环进行了介绍,本篇给大家介绍...
  • Linux Shell中的空格

    2018-08-16 17:28:01
    今天在学Linux Shell脚本,语法中的空格实在是搞不清楚。 1.定义变量时,=号两边不可以留空格 2.使用反引号的时候,运算符两边必须留空格;使用$()的时候,运算符两边不可以留空格 3.if条件测试语句的时候,[ ]...
  • linux shell 中判断文件、目录是否存在
1 2 3 4 5 ... 20
收藏数 568,424
精华内容 227,369
关键字:

linuxshell