• Linux之shell编程

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

    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学习资源整理:https://zhuanlan.zhihu.com/p/22654634 ...知乎 - 你是如何学习 Linux 编程的?:https://www.zhihu.com/question/20730157 如果让你推荐一本 Linux 书籍,你会推荐什么?:https...

    linux学习资源整理:https://zhuanlan.zhihu.com/p/22654634

    Linux初学者(学习资料):https://zhuanlan.zhihu.com/p/21723250

    知乎 - 你是如何学习 Linux 编程的?:https://www.zhihu.com/question/20730157

    如果让你推荐一本 Linux 书籍,你会推荐什么?:https://www.zhihu.com/question/20341603/answer/246718241

    Linux+开发+运维->推荐书籍与学习路线:http://www.cnhonkerarmy.com/thread-220235-1-1.html

    《Linux云计算从入门到精通》系列实战笔记:http://www.178linux.com/87104

     

     

    Linux 学习路线

     

    1. 《鸟哥的linux私房菜》

     

    鸟哥官网:http://linux.vbird.org

    鸟哥的linux私房菜分为《鸟哥的linux私房菜 基础学习篇》《鸟哥的linux私房菜 服务器架设篇》。先看基础篇,再看服务器架设篇。读完这两本书后可以再看下《Linxu 就该这么学》和 《构建高可用 LInux 服务器》这两本书是关于Linux运维的。这些书读完之后你就对 linux 系统有整体的了解和感知,也可以感受linux与windows的不同。网上有 pdf 版本的电子书,可以下载看下。如果想学Linux运维的话,可以找一些培训机构的视频看下,例如:马哥Linux运维、等。也可以向培训机构的客服要下他们的课程大纲然后自学,或者直接去培训机构系统全面的学习。培训是一个入门捷径。但是能学到多少就看自己了。师傅领进门,修行靠个人。

     

    2. 《apue》 unix环境高级编程

     

    APUE说明及源码下载地址 :http://www.apuebook.com/

    apue(Advanced Programming in the UNIX Environment) 被誉为 UNIX编程 “圣经”。
    目前最新的是第三版,可以网上下载第二版或者第三版的pdf

     

     

    3. Unix 网络编程 (即 UNP) 分 卷一 和 卷二

     

    Unix 网络编程源码下载:http://unpbook.com/

    Unix 的网络编程必看的书籍(网上也可以下载电子版)

     

    4. 《TCP/IP详解》 卷一 和 卷二 

     

    《TCP/IP详解》也是经典巨著。总共 三卷(卷1是协议。卷2是实现。卷三是TCP事务协议、HTTP、NNTP和UNIX域协议)

     

    5. Linux 服务器方向

     

    《高性能服务器编程》《Linux多线程服务器端编程》

    Linux 服务器方面书籍

    Linux书籍:https://www.zhihu.com/search?type=content&q=linux%E4%B9%A6%E7%B1%8D

     

    6. 阅读第三方库源码

     

    例如: libevent、nginx 等。

     

    Libevent 是一个用C语言编写的、轻量级的开源高性能事件通知库,主要有以下几个亮点:事件驱动( event-driven),高性能;轻量级,专注于网络,不如 ACE 那么臃肿庞大;源代码相当精炼、易读;跨平台,支持 Windows、 Linux、 *BSD 和 Mac Os;支持多种 I/O 多路复用技术, epoll、 poll、 dev/poll、 select 和 kqueue 等;支持 I/O,定时器和信号等事件;注册事件优先级。
    Libevent 已经被广泛的应用,作为底层的网络库;比如 memcached、 Vomit、 Nylon、 Netchat等等。

     

    Nginx (engine x) 是一个高性能的HTTP和反向代理服务器,也是一个IMAP/POP3/SMTP服务器。其将源代码以类BSD许可证的形式发布,因它的稳定性、丰富的功能集、示例配置文件和低系统资源的消耗而闻名。Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,并在一个BSD-like 协议下发行。其特点是占有内存少,并发能力强,事实上nginx的并发能力确实在同类型的网页服务器中表现较好,中国大陆使用nginx网站用户有:百度、京东、新浪、网易、腾讯、淘宝等。

     

    7. linux学习的方向及深度

     

    https://www.zhihu.com/question/21492447

     

     

     

    网络编程经典书籍

     

     

    首先要说讲述TCP/IP的书很多,其中有3本书很全。

    分别是《TCP/IP详解》三卷本,《用TCP/IP进行网际互连》三卷本,《TCP/IP指南》+《IPv6》四卷本

    其中TCP/IP详解的作者还写了另外2本经典著作,《Unix环境高级编程》,《Unix网络编程》

    作者W.Richard Stevens个人网站  http://www.kohala.com/

    大师作品部部经典,可惜1999年去另一个世界维护Unix了。。。。。。。。。

     

    说明:搞Linux网络编程的,想学TCP/IP的一定要看大师W.Richard Stevens这六本书,基本上六本书看完基础也就搭好了。

     

     

            W.Richard Stevens(1951-1999),国际知名的UNIX和网络专家,受人人尊敬的作家。他的著作有《UNIX网络编程》(两卷本),《UNIX网络高级编程》,《TCP/IP详解》(三卷本)等,同时他还是广受欢迎的教师和顾问。 
      Stevens先生1951年生于赞比亚,早年,他就读于美国弗吉尼亚州的费什本军事学校,后获得密歇根大学学士、亚利桑那大学系统工程硕士和博士学位。他曾就职于基特峰国家天文台,从事计算机编程。 
      Stevens先生不幸病逝于1999年9月1日,他的离去是计算机界的巨大损失。 
      W. Richard Stevens对我们这些学习Unix/Linux的程序员的影响是巨大的,每每捧读老先生的书都会被感动,不仅被他那丰富的知识所折服,更是被他那一丝不苟,严谨治学的态度所倾倒。“他不清楚的,他下决心要弄明白。他知道的,他要努力传授给所有感兴趣的人们!”这就是我们的Stevens!一生能有其一部力作可谓无憾矣!而况七部乎? 
      一位朋友说:“Stevens的书一定要全部认真地去读的。”我想他是对的。最后,对Stevens在天英灵说声:谢谢您!想必这时他正坐在上帝身边,继续从事他的写作。 

    W. Richard Stevens Biography :http://www.kohala.com/start/bio1.html 
    W. Richard Stevens' Home Page :http://www.kohala.com/start/ 
    W. Richard Stevens on Wikipedia :http://en.wikipedia.org/wiki/W._Richard_Stevens 
    Guru of the Unix gurus :http://archive.salon.com/tech/feature/2000/09/01/rich_stevens/index.html

    ----------------------------------------------------------------------------------------------------------------------------------------------------------------

    《用TCP/IP进行网际互连第一卷:原理、协议与结构(第五版)》

    《用TCP/IP进行网际互连第二卷——设计、实现与内核》

    《用TCP/IP进行网际互连第三卷:客户-服务器编程与应用(Linux/POSIX套接字版)》

    说明:这个系列也是三卷本,和《TCP/IP详解》三卷本一样也是经典,区别是  网际互联 偏重理论 而 详解偏重实际。

    ----------------------------------------------------------------------------------------------------------------------------------------------------------------

    如果你看了上面2套TCP/IP的书6本还不尽兴,还有一套书可以作为提高或者查缺补漏

    虽然与W.Richard Stevens TCP/IP详解 有一定的差距,但仍不失为下一代IPv6网络的参考工具书,值得收藏学习

    ----------------------------------------------------------------------------------------------------------------------------------------------------------------

    《TCP/IP协议原理与应用(第3版)》

    写的非常好,建议一定要买!可作入门,但即使是高手,也应看看,学习作者分析问题的方法和思想。其实是无数次印刷了,经典不能错过。

    ----------------------------------------------------------------------------------------------------------------------------------------------------------------

    《TCP/IP高效编程 : 改善网络程序的44个技巧》

    很明显属于技巧类的书,同样这本书很老,但是还是很有参考价值,这本书中大量出现Unix网络编程的提示,可以作为其补充。

    ----------------------------------------------------------------------------------------------------------------------------------------------------------------

    《TCP/IP架构、设计及应用(Linux版)》

    谈不上经典,可以补充阅读

    ----------------------------------------------------------------------------------------------------------------------------------------------------------------

    《linux网络体系结构:linux内核中网络协议的设计与实现》

    又名: The Linux Network Architecture: Design and Implementation of Network Protocols in the Linux Kernel
    书名: Linux网络体系结构
    说明:翻译的很不好,建议看原版,可以下电子版。

    一本在kernel实现网络功能的基础书籍,最好还是看英文版的,翻译的实在太烂了。有条件的还是看原版吧,没办法啊,中国的此类书籍基本上落后老美3-4年。结合linux源代码分析一书收获更多。一并把内核结构和各功能在内核中的关系也搞清楚了。

    ----------------------------------------------------------------------------------------------------------------------------------------------------------------

    本人写的另外系列,配合一起看效果更好,因为C语言是语言,还需要到实际环境Linux中去用,而Linux环境下网络编程自然需要TCP/IP的知识:

     

    C语言经典著作导读
    Linux内核学习四库全书

    https://blog.csdn.net/freeking101/article/details/71473369

    展开全文
  • 在 「关于我 」那篇博文里,朋友们应该知道了我不是科班出身,是由机械强行转行到Linux应用开发方向。下面我就详细向大家介绍自己这一路上的转行历程,希望对大家有所启发。   我是学机械专业的,对于机械专业我...

     

    前言

     

     

     

     

    在 「关于我 」那篇博文里,朋友们应该知道了我不是科班出身,是由机械强行转行到Linux应用开发方向。下面我就详细向大家介绍自己这一路上的转行历程,希望对大家有所启发。

     

    我是学机械专业的,对于机械专业我还是很感兴趣,而且当年这个专业也是我自己选择的。本科时成绩没排第一,但也排专业前列。硕士时成绩一直是专业第一,还拿过国家奖学金。由此可见,我还是挺热爱机械的,考研时甚至还想去中科院深造(可惜落榜了),想成为一名机械砖家。

     

     

    转折点

     

     

     

    但转机在我毕业之后。本来我就想一直踏踏实实的做机械,但阴差阳错的,第一份工作由机械工程师调剂到电子工程师。虽然是电子工程师,但做了很多代码工作,那时候学了一些Verilog,以及C#,并且用C#开发了一个测试工具及一个生产用的上位机,据说那个测试工具现在还在电子部门使用。也就是那时,我真正接触到了编程,并深深的被编程世界所着迷。

     

    可是,我在学校学的是机械啊,跟软件相关的东西顶多是C语言及汇编语言,但C语言是十年前(那时候)学的,早就还给老师了。因此,为了工作的需要,我硬着头皮开始学习编程。我每天早上6点起床,学习一个小时后7点去赶班车,晚上下班回来再继续学习。在这样的条件下我学会了Verilog及C#,同时对STM32有个大概的了解。

     

    但那个岗位毕竟不是完全做软件工作,还需要做电子方面的工作。做软件的都知道,写代码的时候需要一个不被打扰的环境,而我那个岗位,一天可以接到十几个电话,严重干扰我的软件开发进度,因此,我决定离开公司,去做真正的软件工程师。

     

     

    入门

     

     

     

    第二份工作,我是嵌入式工程师。在去公司之前,我系统的学了一遍51,看的是 「郭天祥十天学会51单片机」,对单片机有个初步的入门。真正入职后,再利用一个月的时间系统学习一遍C语言,看的是C语言之父写的 「C程序设计语言」。这本书虽然写于80年代,但现在看来依然不过时。而且这本书比较薄,容易看完,比较有成就感。

     

    看完 「C程序设计语言」之后,最好再看 「C和指针」 ,「C专家编程」,「C陷阱与缺陷」。这三本书号称「C语言三剑客」 ,学完它们你的C语言水平绝对是非常之高了。做我们这行,C语言是基础语言,一定要把C语言完全拿下。

     

    之后为了工作的需要,开始接触了Marvell 88MC200及88MW300。这两款是WIFI芯片,但本质是单片机,采用的是ARM M3内核。底层驱动已经由厂家写好了,我们要做的工作就是在SDK基础上做应用做二次开发。在这个过程中,我对敏捷开发有了初步的认识,看了 「高效程序员的45个习惯:敏捷开发修炼之道」这本书。

     

    在第二家公司里,我知道了物联网的整体解决方案,对于物联网的整体框架有了更深入的了解。同时对嵌入式开发所需的技能有了全面的掌握,达到能够独立做一个物联网项目的水平。当然,期间还学会了版本控制工具git,看的是 「Pro Git」及 「git权威指南」。同时再进一步系统学习了STM32,看的书是野火的 「STM32库开发实战指南」,但看的视频却是正点原子的。这里强烈推荐正点原子的视频,真的是做的非常好。

     

    由于我是转行,我自知底子薄,起点比别人落后很多,因此我很刻苦学习。我就住在公司附近,公司8点上班,我依然6点起床,12点睡觉。平时没事的时候就在公司学习,看代码,写代码,调代码,甚至好几个周末也是在公司度过。为了转行我一心扑在了学习上,放弃了一切娱乐活动,只为能赶上同事一点半点。这个状态一直持续了我在这个公司的整个过程。

     

     

    新挑战

     

     

     

    可惜,一年之后我不得不离开了,因为我老婆在广州,我去广州追随我老婆了。由于我面试时的不错表现,以及我不错的英语口语,我顺利的被广州一家外资世界500强录用。我的新岗位是Linux应用开发工程师,使用C++开发。而在那时,我既不会Linux,也不会C++,所以公司敢录用我真的是勇气可嘉。

     

    从单片机到Linux又是一个比较大的跨度。Linux主要有三个方向:运维、应用、底层,而我选择的是应用方向。为了工作的需要,我必须先拿下C++。好在公司是外企,几乎不加班,于是我有充足的时间学习。可是,每天坐班车的时间加起来有三个小时,这么好的时间我不能浪费啊,于是,我买了个iPad,下载一套视频,在班车上也进行学习。当然,我也注意保护眼睛,在班车上绝不看书,看视频的时候也经常往窗外看看。

     

    这里讲讲自学过程中是采用看书还是看视频的方式。我采用的是先看视频再看书的方式。为什么要这样?因为我个人觉得,很多技术书都是很厚的一本,自己看下来要花费很多时间精力,而且很多知识点在工作中很少会用到,可以暂时先略过的。视频一般会比较短,一般是几天就能看完,而且老师一般只挑重点的讲。我们时间都很宝贵,我建议先把老师讲的知识完全掌握了以后,再利用书籍进一步扩展其它知识。但是,不管是看书还是看视频,一定要写代码,一定要写代码,一定要写代码,否则非常容易造成眼高手低!!

     

    我这家公司里我依然保持着早上6点起床,晚上11点睡觉的习惯。这样早上有1个小时,晚上3个小时,班车上3个小时,一天总共有7个小时可以用于学习。而周末我会睡个懒觉,7点起床,依然11点睡觉,周末几乎不出门,所以周末又有大量的时间可以用来学习。

     

    C++实在是太难了,我用了一个多月才将视频里的知识完全拿下来。之后,在工作中,陆续开始看「C++ Primer」 进一步巩固。其它书籍推荐:「Effective C++」,「C++语言的设计与演化」,「C++ 标准程序库」。在stackoverflow上有个C++必读书单,可以去看看。

     

    学完C++我再学习了Linux系统编程,同样采用的是先视频后书籍的方式进行。不管是视频还是书籍,一定要看最经典的。市面上有太多良莠不齐的视频和书籍,如果选择了一个质量不好的材料来看,不仅会浪费很多时间,而且有可能还会被误导。对于视频的选择,可以去各大培训机构,里面通常有一些免费视频资料供我们下载。

     

    对于Linux应用的学习,主要有六部分:1. 环境搭建;2. 基本操作;3. 系统编程;4. 网络编程;5. 数据库编程,6. Shell编程。下面一一详细介绍。

     

    1. 环境搭建

    作为Linux工程师,毋庸置疑一定需要Linux环境。对于Linux环境的获取,我们通常有两种方式:

    • 将电脑整体安装为Linux系统;

    • 在电脑里安装一个虚拟机,跑Linux电脑;

    • Window+Linux双系统。

    得到Linux环境后还不够,还要知道如何配置、如何远程连接Linux电脑、如何与Linux电脑互传文件、如何在主机上阅读Linux电脑中的代码,等等。

     

    2. 基本操作

    众所周知,Linux很少或几乎没有界面,所有的操作几乎都可以通过命令行来完成。对于运维人员来说,需要掌握相当大量的Linux命令。而对于应用、驱动方向的人员来说,只需掌握一些基本的常用的命令即可。对于这部分很多人建议看 「鸟哥的私房菜」 ,但我觉得这个更适合运维人员,我们无需掌握那么多命令。

     

    3. 系统编程

    在学系统编程之前,一定要先学习Makefile,这会为后续的学习提高很大效率。之后的系统编程,主要有几大块:IO编程、进程、线程、进程间通讯(包括管道、信号、信号量、共享内存等)。这几部分学完了,基本也就差不多了。

     

    4. 网络编程

    网络编程主要就是socket,poll,epoll,以及对TCP/IP的理解,同时要学会高并发式服务器的编写。

     

    5. 数据库编程

    数据库的内容其实并不属于Linux,但在项目中经常要用到。这部分主要要学会数据库的基本操作,以及如何写一套接口去操作数据库。

     

    6. Shell编程

    Shell是Linux下的脚本语言,功能虽然不如高级语言强大,但它可能做很多事,在某些场合甚至比高级语言要方便得多。当然除了Shell脚本,还有Python脚本。

     

    Linux应用编程书籍推荐:

    • UNIX环境高级编程。简称APUE,号称程序员的圣经。它不是一本API字典,它还讲述了很多操作系统的细节,内存,文件系统等方面,是一本难得的好书。但是它起点有点高,不适合初学者。

    • Linux程序设计。如果觉得APUE有点难入门的话,可以选择此书进行入门。

    • Unix/Linux系统编程手册。这本书号称是一本超越APUE的书,它是一本比较新的书,里面新增了APUE所没有的Linux/Unix新特性。而且对于一些概念性的东西讲的确实比APUE好。但至于能否超载APUE,还有待历史的考验。

    • UNIX 网络编程。也是一本非常经典的书,主要是网络编程方向的。

    • MySQL必知必会。本书在Amazon上长期排在数据库销售榜首,建议想快速了解数据库原理和MySQL的新手阅读。快餐性质,简洁明快,小开本,而且很薄,比较好阅读。

    • Linux Shell脚本攻略。这本书很薄很精华,它追求的不是全,而是精,所以用它来入门再适合不过了。

     

    学完以上六部分,基本就有能力完成Linux环境下的应用编程了。当然,在有些场合我们可能还需要用到Python脚本。像我公司的项目部分脚本就是用Python完成的。对于Python的入门,可以参考 「简明Python教程」。但如果想进一步提高的话,那就需要阅读大量书籍了。对于Linux层级的脚本应用,掌握一些基础的足够了。

     

    以上就是我的Linux应用开发自学之路,虽然我离高手还有很长的距离,但我还是将自己的这段自学经历分享出来,希望能够帮助朋友们少走一些弯路,同时也期待和高手一起学习、提高。好了,今天的分享就至此为止。

    ---------------

    我是良许,世界500强外企 Linux 开发工程师,专业生产 Linux 干货。欢迎关注我的公众号「良许Linux」,回复「1024」获取最新最全的技术资料,回复「入群」进入高手如云技术交流群。

     

     

     

     

    展开全文
  • Linux C网络编程基础

    2019-03-21 09:30:32
    Linux套接字结构定义 Linux C的网络基础操作函数 字节顺序转换函数族 IP地址转换函数族 域名转换函数 Linux网络套接字操作函数 创建套接字描述符函数 绑定套接字函数 建立连接函数 倾听套接字切换函数 ...

    目录

    Linux套接字结构定义

    Linux C的网络基础操作函数

    字节顺序转换函数族

    IP地址转换函数族

    域名转换函数

    Linux网络套接字操作函数

    创建套接字描述符函数

    绑定套接字函数

    建立连接函数

    倾听套接字切换函数

    接收连接函数

    关闭连接函数

    套接字读写函数

    套接字地址获取函数

    发送和接收函数


    Linux套接字结构定义

    Linux在头文件<sys/socket.h>中定义了一种通用的套接字结构类型,以供不同的协议调用:

    struct sockaddr
    {
        unsigned short int sa_family;
        unsigned char sa_data[14];
    };

    其中sa_family是套接字协议族地址类型,sa_data则是具体的协议地址(不同的协议族对应不同的地址结构)。

    常见协议对应的sa_family值
    可选值 说明
    AF_INET IPv4协议
    AF_INET6 IPv6协议
    AF_LOCAL UNIX协议
    AF_LINK 链路地址协议
    AF_KEY 密钥套接字

    除了sockaddr以外,Linux还在头文件<netinet/in.h>中定义了另外一种结构类型sockaddr_in,它和sockaddr等效且可以互相转换(需要显式转换),通常在涉及TCP/IP的编程协议中使用。

    struct sockaddr_in
    {
        int sa_len;                     //长度单位,不必设置,通常情况下固定长度为16字节
        short int sa_family;            //地址族
        unsigned short int sin_port;    //端口号
        struct in_addr sin_addr;        //IP地址
        unsigned char sin_zero[8];      //填充0以保持与struct sockaddr同样大小
    };
    
    struct sin_addr
    {
        in_addr_t s_addr;    //32位IPv4地址,网络字节序
    };

    使用sockaddr_in的时候需要注意以下几点:

    ·结构sockaddr_in中的TCP或UDP端口号sin_port和IP地址sin_addr都是以网络字节序存储的,因此要注意格式统一

    ·32位的IPv4地址可以利用两种不同的方法使用,如sockaddr_in类型的结构体addr,要使用其IP地址可以是addr.sin_addr(此时引用的是结构体类型),也可以是addr.sin_addr.s_addr(此时引用的是整型)

    ·sin_zero不被使用,它是为了和通用套接字地址sockaddr保持一致而引入的,通常会被填充为0

    Linux C的网络基础操作函数

    字节顺序转换函数族

    计算机的数据存储有两种形式:大端格式和小端格式

    ·大端格式:字数据的高位存储在低地址中

    ·小端格式:字数据的高位存储在高地址中

    网络中的数据传输采用的是大端格式,而计算机操作系统既有大端格式(大型机)也有小端格式(一般PC机)。Linux在头文件<arpa/inet.h>提供了4个函数用于处理大端格式和小端格式的数据转换。

    /*
        函数名中,h代表host,n代表network,s代表short,l代表long
        32位的long数据通常存放IP地址,16位的short数据通常存放端口号
        这些函数调用成功后返回处理后的值,调用失败则返回-1
    */
    uint32_t htonl(uint32_t hostlong);    //将32位PC机数据(小端格式)转换为32位网络传输数据(大端格式)
    uint16_t htons(uint16_t hostshort);   //将16位PC机数据(小端格式)转换为16位网络传输数据(大端格式)
    uint32_t ntohl(uint32_t netlong);     //将32位网络传输数据转换为32位PC机数据
    uint16_t ntohs(uint16_t netshort);    //将16位网络传输数据转换为16位PC机数据

     

    IP地址转换函数族

    Linux在头文件<arpa/inet.h>提供了用于将“点分十进制”表示的IP地址与二进制表示的IP地址相互转换的函数族,这些函数包括inet_aton、inet_addr和inet_ntoa等。

    ·inet_aton函数用于将点分十进制数的IP地址转换成网络字节序的32位二进制数值。输入的点分十进制IP地址存放在参数straddr中,作为返回结果的二进制数值存放在addrptr中。

    int inet_aton(const char *straddr, struct in_addr *addrptr);

    ·inet_ntoa函数调用的结果将作为函数的返回值返回给调用它的函数。

    char *inet_ntoa(struct in_addr inaddr);

    ·int_addr函数的功能与inet_aton函数相同,但是结果传递的方式不同。结果以返回值的形式返回。

    in_addr_t inet_addr(const char *straddr);

    域名转换函数

    Linux在头文件<netdb.h>中定义了一个结构体,用于描述一个主机的相关参数:

    #define h_addr h_addr_list[0];    //主机的第一个IP地址
    
    struct hostent
    {
        char *h_name;          //主机的正式名称
        char *h_aliases;       //主机的别名
        int h_addrtype;        //主机的地址类型,IPv4为AF_INET
        int h_length;          //主机的地址长度,对于IPv4是4字节,即32位
        char **h_addr_list;    //主机的IP地址列表
    };

    头文件<netdb.h>提供了gethostbyname和gethostbyaddr函数用于处理域名和地址的转换:

    extern int h_errno;
    struct hostent *gethostbyname(const char *name);
    struct hostent *gethostbyaddr(const void *addr, socklen_t len, int type);

    gethostbyname用于实现域名或主机名到IP地址的转换,参数name指向存放域名或主机名的字符串。gethostbyaddr用于实现IP地址到域名或主机名的转换,参数addr是一个指向含有地址结构(in_addr)的指针;参数len是此结构的大小,对于IPv4而言其值为4,对于IPv6则是16;参数type为协议类型。

    若调用这两个函数成功,则返回一个指向hostent结构的指针,若调用失败则返回空指针NULL,同时设置全局变量h_errno为相应的值。

    h_errno可能的值
    h_errno

    说明

    HOST_NO_FOUND 找不到对应的主机
    TRY_AGAIN 出错重试
    NO_RECOVERY 出现了不可修复的错误
    NO_DATA 该名字有效,但没有找到该记录

    Linux网络套接字操作函数

    创建套接字描述符函数

    #include <sys/types.h>
    #include <sys/socket.h>
    
    int socket(int domain, int type, int protocol);    //创建成功的返回值为整型的套接字描述符,创建失败则返回-1

    ·domain:套接字的协议类型,socket函数支持的协议类型如下

    协议名称 描述
    AF_UNIX, AF_LOCAL 本地交互协议
    AF_INET IPv4协议
    AF_INET6 IPv6协议
    AF_NETLINK 内核接口设备协议
    AF_IPX IPX-Novell协议
    AF_X25 ITU-T X.25/ISO-8208协议
    AF_AX25

    业余无线电AX.25协议

    AF_ATMPVC 原始ATM接入协议
    AF_APPLETALK 苹果公司的Appletalk协议
    AF_PACKET 底层数据包接口

    ·type:用于指定当前套接字类型,socket函数支持的套接字类型包括SOCK_STREAM(数据流)、SOCK_DGRAM(数据报)、SOCK_SEQPACKET(顺序数据报)、SOCK_RAW(原始套接字)、SOCK_RDM(可靠传递消息)、SOCK_PACKET(数据包)。

    ·除了在使用原始套接字以外,通常情况下设置为0,以表示使用默认的协议。

    在Linux系统中创建一个套接字时会在内核中创建一个套接字数据结构,然后返回一个套接字描述符标识这个套接字数据结构。这个套接字数据结构包含连接的各种信息,如目的地址、TCP状态以及发送接收缓冲区等。TCP协议这个套接字数据机构的内容来控制这条连接。

    绑定套接字函数

    在创建了套接字之后需要将本地地址和套接字绑定在一起,Linux提供了bind函数进行这个操作:

    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);    //若绑定成功则返回0,否则返回-1

    其中参数sockfd是创建套接字时得到的套接字描述符,addr是本地地址,addrlen是套接字对应的地址结构长度。在C/S模式下服务器和客户端都可以使用bind函数来设置套接字地址,通常来说有以下5种模式:

    ·服务器指定套接字地址的公认端口号,不指定IP地址。这时addr的值设为INADDR_ANY,表示它愿意接收来自任何网络设备接口的客户端连接,这是服务器最经常使用的绑定方式。

    ·服务器指定套接字地址的公认端口号和IP地址,表示服务器只接收来自对应于这个IP地址的特定网络设备端口的客户端连接

    ·客户端指定套接字地址的连接端口号,在一般情况下,客户端不用指定自己的套接字地址的端口号,当客户调用函数connect进行TCP连接时,系统会自动为它选择一个未用的端口号,当客户端调用函数connect进行TCP连接时,系统会自动为它选择一个未使用的端口号,并且用本地的IP地址来填充套接字地址中的相应项。

    ·指定客户端的IP地址和连接端口号,表示客户端使用指定的网络设备接口和端口号进行通信

    ·指定客户端的IP地址,表示客户端使用指定的网络设备接口进行通信,系统自动为客户端选择一个未使用的端口号。一般情况下,只有在主机有多个网络设备接口时使用。

    bind函数对应的参数组合方式
    C/S IP port 说明
    服务器 INADDR_ANY 非0值 指定服务器的公认端口号
    服务器 本地IP地址 非0值 指定服务器的IP地址和公认端口号
    客户端 INADDR_ANY 非0值 指定客户端的连接端口号
    客户端 本地IP地址 非0值 指定客户端的IP地址和连接端口号
    客户端 本地IP地址 0 指定客户端的IP地址

    在编写客户端程序时,通常不适用固定的端口号,除非是在必须使用特定端口的情况下,例如:

    ·服务器执行主动关闭操作:服务器最后进入TIME_WAIT状态。当客户机再次与这个服务器进行连接时,仍使用相同的客户机端口号,于是这个连接与前一次连接的套接字对完全相同,这是因为前一次连接处于TIME_WAIT状态,并未完全消失,所以这次连接请求被拒绝,函数connect以错误返回。

    ·客户端执行手动关闭操作:客户端最后进入TIME_WAIT状态,当立刻再次执行这个客户端程序时,客户机将继续与这个固定客户机端口号绑定,但因为前一次连接处于TIME_WAIT状态,并未消失,系统会发现这个端口号仍被占用,所以这次绑定操作失败,函数bind以错误返回。

    建立连接函数

    当使用socket函数建立一个套接字并且绑定了地址之后,即可使用connect函数来和服务器建立连接:

    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);    //若连接成功则返回0,否则返回-1

    connect函数的参数含义与bind函数相同。

    在调用connect函数建立连接之前,客户端应用程序需要指定服务器端进行的套接字地址,而客户端不会指定自己的套接字地址,这时Linux会自动从1024~5000的端口范围中为客户端分配一个未被使用的套接字地址,然后将该端口号和本机IP地址结合在一起放入套接字地址中。

    当客户端调用函数connect函数来主动建立连接时,这个函数将启动TCP3次握手过程,在连接建立之后或发生错误时,函数返回。连接过程中可能有以下几种错误情况:

    ·如果客户端TCP协议没有接收到对它的SYN确认信息,则函数以错误返回,错误类型为ETIMEOUT。

    ·如果远程TCP协议返回一个RST数据报,则函数立即以错误返回,错误类型为ECONNREFUSED。当在SYN数据报指定的目的端口号没有服务器进程在等待连接时,会发送RST数据报,向客户机报告这个错误。

    ·如果客户机的SYN数据报导致某个路由器产生“目的地不可达”类型的ICMP消息,则函数以错误返回,错误类型为EHOSTUNREACH或ENETUNREACH。通常情况下,TCP协议在接收到ICMP消息后回记录下这个消息并继续发送几次SYN数据报,如果都宣告失败,函数才会以错误返回。

           如果调用函数connect失败,应该用函数close关闭这个套接字,不能再次用这个套接字来调用函数connect。

    倾听套接字切换函数

    对于服务器端的应用程序而言,在创建了套接字之后通常需要等待客户端的连接,此时可以使用listen函数将该套接字转换为倾听套接字。    

    int listen(int sockfd, int backlog);    //成功则返回0,否则返回-1

    参数sockfd为套接字描述符,backlog为设置请求队列的最大长度。该函数的功能有以下两个:

    ·socket函数创建的套接字是主动套接字,可以用它来进行主动连接(调用connect函数),但不能接受连接请求,而用于服务器的套接字必须能够接收客户端请求。listen函数将一个尚未连接的主动套接字转换成为一个被动套接字,被动套接字可以接收请求。

    总结起来就是,若要创建一个倾听套接字,必须首先调用socket函数创建一个主动套接字,然后调用bind函数将套接字与服务器套接字地址绑定在一起,最后调用listen函数进行转换。这3个操作是所有TCP服务器必须执行的操作。

    而对于参数backlog的作用,TCP协议为每个倾听套接字维护两个队列:

    ·未完成连接队列:每个尚未完成3次握手操作的TCP连接在这个队列中占有一项。TCP协议在接收到一个客户机的SYN数据报后,在这个队列中创建一个新条目,然后发送确认消息以及自己的SYN数据报(ACK+SYN),等待客户端对自己的SYN进行确认。此时,套接字处于SYN_RCVD状态,这个条目保存在队列中,直到客户端返回对自己SYN消息的确认,或者连接超时。

    ·已完成连接队列:每个已经完成3次握手操作,但尚未被应用程序接收(调用accept函数)的TCP连接在这个队列中占有一项。

    参数backlog指定倾听套接字的完成连接对了的最大长度,表示这个套接字能够接收的最大数目的未接收(unaccepted)连接。如果当一个客户端的SYN消息到达,但这个队列已满,TCP协议将会忽略这个SYN,并且不会发送RST数据报。

    接收连接函数

    当服务器倾听到一个连接之后,可以使用函数accept从倾听套接字的完成连接队列中接收一个连接,如果这个完成连接队列为空,则会使得这个进程进入睡眠状态。

    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

    当这个函数执行成功时,函数返回值为一个新的套接字描述符,标识这个接收的连接。而且函数还把参数addr指向的结构变量中的地址修改为客户机地址,更新参数addrlen指向的整型变量中存储客户机地址的长度。如果对客户机的地址和长度都不感兴趣,可以将参数addr和addrlen设置为NULL。若函数执行失败,返回-1。

    accept函数从倾听套接字的完成连接队列中接收一个已经建立的TCP连接,因为倾听套接字时专为接收客户机请求,完成3次握手的连接操作而用的,所以TCP协议不能使用倾听套接字来标识这个连接,于是TCP协议创建一个新的套接字来标识这个要接收的连接,并将它返回给应用程序。现在有两个套接字:一个是调用accept函数时使用的倾听套接字,另一个是accept函数执行成功返回的连接套接字(connected socket)。这两个套接字的作用是完全不同的:一个服务器进程通常只需要创建一个倾听套接字,在服务器进程的整个活动期间,用它来接收所有客户机连接请求,在服务器进程终止前关闭这个倾听套接字;而对于每个接收的连接,TCP都要创建一个新的连接套接字来标识这个连接,服务器使用这个连接套接字与客户机进行通信,当服务器处理完这个客户机请求时,关闭这个套接字。

    关闭连接函数

    当操作完成之后,可以使用close函数来关闭当前建立的连接,这函数定义在头文件<unistd.h>中。

    int close(int fd);

           close函数将套接字描述符的引用计数减1。如果描述符的引用计数大于0,则表示还有进程引用这个描述符,close函数正常返回;如果描述符的引用计数变为0,则表示再没有进程引用这个描述符,于是启动清除套接字描述符的操作,函数正常返回。清除操作是将这个套接字描述符标记为关闭状态,进程将不再能访问这个套接字,但并不表示套接字已经被删除(联想一下TCP的4次挥手的过程)。TCP将继续使用这个套接字,将尚未发送的数据传递给对方,然后发送FIN数据报,执行关闭操作,直到这个TCP连接完全关闭后,套接字才会被删除。

    套接字读写函数

           函数read和write分别用于从套接字读/写数据。

    int read(int fd, char *buf, int len);
    int write(int fd, char *buf, int len);

           fd为套接字描述符;buf指定数据缓冲区;len指定接收或发送的数据量大小(以字节为单位)。函数成功执行时,返回读/写成功的数据量大小,失败则返回-1。

    套接字地址获取函数

    当需要获取套接字的地址时,可以使用头文件<sys/socket.h>中的getsockname和getpeername函数,前者用于返回本地的套接字地址,后者用于返回与本地套接字建立了连接的对等套接字地址。

    //函数调用成功则返回0,否则返回-1
    int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

    需要注意的是,如果sockfd标记的套接字中的地址长度大于addr指向的对象的存储空间,则存储的地址将被截断,如果socket还没有捆绑地址,则结果是未知的。

    存储在addr参数所指对象中的地址格式依赖于该套接字的通信域。对于给定的通信域,套接字地址的长度通常是固定的,如果需要确切知道空间大小,并提供实际需要的存储空间,通常的做法是利用与套接字通信域相匹配的数据类型为addr所指对象分配空间,然后强制其地址转换为struct socket *并传送给getsockname。

    getsockname函数通常会应用于以下情况:

    ·对于没有使用bind捆绑地址至套接字的客户进程,在它成功调用connect之后,getsockname可以返回内核指定给该套接字的本地地址(如IP地址和端口号等)。

    ·当利用0端口号(系统自动分配端口号)调用bind之后,getsockname可以返回系统指定给该套接字的本地端口号

    ·getsockname可以获得一个套接字的地址族

    ·服务进程在接收了客户的连接请求之后(成功调用accept之后),以accept返回的描述符调用getsockname可以获得指定给该连接的套接字地址,这个套接字是连接套接字而不是倾听套接字。

    getpeername函数的行为与getsockname函数类似。

    发送和接收函数

    除了之前说的read和write之外,还可以使用recv和send函数来在套接字中实现数据的发送和接送。这两个函数相比read/write多了一个参数用以指明控制套接字特殊传输方式的各种标志。

    ssize_t send(int sockfd, const void *buf, size_t len, int flags);
    ssize_t recv(int sockfd, void *buf, size_t len, int flags);

    与read/write函数相同,如果调用成功,返回值是代表接收/发送成功的字节数(函数发送的实际长度可能小于其指定的长度),失败则返回-1。flags参数用于指定消息的传送类型,当该值为0时send函数和write函数完全相同,同样recv函数与read函数也相同。

    send函数的flags参数除0外还有如下两种取值:

    send函数的flags参数取值
    MSG_OOB send函数发送的数据成为带外数据,带外数据是流套接字特有的。在流套接字上传送数据时,数据按它们写出的顺序传送。因为接收进程必须依次读套接字上的当前数据,因此当出现一个紧急情况时,没有办法立即通知接收进程。带外数据正是用于解决这个问题。带外数据在正常的数据流之外发送,其效果相当于越过套接字上所有等待的数据。当它到达接收进程时,接收进程会收到一个信号,从而进程可以立即处理这个数据。
    MSG_DONTROUTE 不在消息中包含路由信息,通常来说普通应用不会关系相应的信息。

    recv函数的flags参数除0外还有如下三种取值:

    recv函数的flags参数取值
    MSG_OOB 读带外数据
    MSG_PEEK 窥视套接字上的数据而不实际读出它们,即尽管buffer所指对象中填入了所请求的数据,但随后的read或recv将读到相同的数据。
    MSG_WAITALL 请求函数阻塞直至所请求的全部数据都已接收到。

    write函数、read函数、send函数和recv函数都是用于TCP下面向连接的套接字数据发送和接收。而在UDP下面向无连接的套接字数据发送和接收则需要使用sendto和recvfrom函数。

    ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
    ssize_t recvfrom(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *src_addr, socklen_t addrlen);

     

                                                                                                       本文内容摘自《Linux C编程从基础到实践》,有改动

    展开全文
  • 采用的是虚拟机模拟的Linux环境。   启动虚拟机中的cenos,登陆并进入Linux环境。 打开MobaXterm Personal Edition,在里面也有一个centos。在MobaXterm Personal Edition中编辑较为方便。 2、开始编程 ...

    1、准备gonz工作。

    采用的是虚拟机模拟的Linux环境。

     

    启动虚拟机中的cenos,登陆并进入Linux环境。

    打开MobaXterm Personal Edition,在里面也有一个centos。在MobaXterm Personal Edition中编辑较为方便。

    2、开始编程

    键入“vim test.c”,按回车键,进入“test.c”的编程环境。

    按一下“a”键,进入可编程模式,键入一个简单的命令,输出“hello,world!”

    按“esc”键退出编辑模式。按住“ctrl+:”,如果想baoc保存文件,则在此基础上jian键入“wq”,不想保存,则在此基础上键入“q!”。

    保存后的文件可以用“ls”来查看,存在test.c。

    展开全文
  • linux中的for循环有很多用处:比如跑历史数据,参数为日期,那么我们可以把日期放入for循环中,循环把日期作为参数执行下面看一个我生产中用到的例子[zhangshk@fonova-app02 ~]$ cat nohup_run0_100.sh for i in {0...
  • Linux系统编程视频课程为《Linux系统编程》入门篇,主要针对零基础的Linux开发学员科普Linux系统编程的概念以及需要掌握的各种技能,掌握Linux命令编写、Linux学习路线并熟悉嵌入式设备编程的方法。为后续的Linux...
  • 嵌入式Linux C编程基础

    2019-07-01 10:36:46
    本课程是全套课程的第0.2.3课(预科第三课程),主题linux系统下C语言开发学习,总共25小时左右的课程。该视频是我在联嵌科技代课期间随堂真实录制,学生均为根本没接触过C语言的应届毕业生(现在全部毕业,从事...
  • 本课程讲解Linux编程下C语言的基础编程知识,包括环境构建,Linux shell命令 ,Linux VI编辑器,Linux GCC编译器使用,C语言保留字,变量,表达式、语句、函数、程序的结构、数据结构与算法、链表与栈。
  • 作者:方春根 ... 来源:知乎 ...题主的意思不是问“Linux好处都有啥,谁说对了不给他”(escape根据题主描述题主应该压根不清楚计算机的体系结构那么有点基本开发经验的我可以简单讲讲关于题主的疑惑了:
  • 如何才能成为一个Linux编程高手?在这里笔者建议大家多读经典书籍,多读优秀的开源代码,多写代码,除了这些别无它法。接下来本文就向大家推荐一些公认的Linux编程经典图书。想要成为真正的Linux编程高手,这些书籍...
  • 【 声明:版权所有,欢迎转载,请勿用于商业用途。...  在Linux下面关于C语言的内容其实挺多的。...而我在这里所关注的只是linux应用层方面的内容,属于最基础的内容,当然也是最重要的内容。实际上对应于
  • 先要学习网络知识才谈得上编程讲述计算机网络的最经典的当属Andrew S.Tanenbaum的《计算机网络》第五版,这本书难易适中。《计算机网络(第5版)》是国内外使用最广泛、最权威的计算机网络经典教材。目前已经是第五...
  • 一、Linux常用命令 1、常用工具 2、目录和文件命令 3、安装和卸载软件命令 4、压缩包管理 5、文件属性、用户用户组及查找与检索命令 6、网络管理命令 7、其他命令 二、Linux vi/vim常用命令 三、Linux下...
  • 本课程主要讲解Linux环境进程与线程的概述,进程创建,进程间通信编程,多线程编程。 学习条件: 1.C编程基础 2.Linux编程基础
  • 参考书当然时《Unix/Linux编程实践教程》现写下心得笔记,有什么不对的,请大家指正哈。-------------------------------正文直接上代码:/*more01.c - version 0.1 of more *read and print 24 lines then pau...
  • linux 编程入门 Linux/UNIX系统编程手册 【德】Michael Kerrisk著 着眼于Linux 2.6.x和GNU C语言库(glibc)版本2 Web站点 http://man7.org/tlpi 勘误 http://man7.org/tlpi/errata 为调试程序,或是研究程序的运作...
  • Linux编程命名规则

    2015-11-26 20:49:55
    如果只是编写一些小程序,程序只有几十或几百行,编程风格可能并不重要。然而,如果是和许多人一起进行一定规模的项目开发,或者希望过一段时间之后,还能够快速而准确地理解自己的程序,就必须养成良好的编程习惯。...
  • 本文记录Linux Shell编程中常用基本知识,方便快速入门以及查询使用
  • linux C编程

    2020-07-10 23:31:46
     C语言是国际上广泛使用的计算机高级语言,作为Linux系统的开发语言,C语言在Linux编程开发中扮演着重要的角色。它们很快形成了完美的结合,为用户提供了一个强大的编程环境。  国内Linux的发展方兴未艾,...
1 2 3 4 5 ... 20
收藏数 491,960
精华内容 196,784
热门标签