• [Linux系统] (4)脚本编程


    一、bash shell

    可以理解为一种解释器和启动器,解释命令文本,并执行命令。

    命令来源:

    • 用户交互输入
    • 文本文件输入

    1.示例,写一个最简单的文本

    vi test.txt

    写入以下内容:

    echo "Hello World"
    ls -l /
    echo $$

    这样就写好了一个最简单的脚本,其中$$表示运行当前脚本的bash的进程号。

    2.如何运行这个文本中的命令

    # 使用bash内建命令source来运行
    source test.txt
    # 使用"."来运行,注意点后面是空格
    . test.txt

    实际上source和"."都是bash的biuldin命令:

    [root@centos-clone1 ~]# type source
    source is a shell builtin
    [root@centos-clone1 ~]# type '.'
    . is a shell builtin

    source和"."是等效的,意思是解释文本中的命令,并执行。我们经常使用的source /etc/profile就是让系统重新执行一下/etc/profile配置脚本。

    3.使用bash子进程运行

    上面使用source和"."来执行脚本,echo $$显示的是当前bash的进程号。

    先使用pstree工具查看进程树:

    [root@centos-clone1 ~]# pstree
    -bash: pstree: command not found

    发现Mini版的CentOS默认没有安装pstree,使用yum安装:

    yum install psmisc -y

    安装完毕后执行pstree:

    [root@centos-clone1 ~]# pstree
    systemd鈹€鈹�攢agetty
            鈹溾攢auditd鈹€鈹€鈹€{auditd}
            鈹溾攢chronyd
            鈹溾攢crond
            鈹溾攢dbus-daemon
            鈹溾攢irqbalance
            鈹溾攢lvmetad
            鈹溾攢master鈹€鈹�攢pickup
            鈹       鈹斺攢qmgr
            鈹溾攢nginx鈹€鈹€鈹€nginx
            鈹溾攢polkitd鈹€鈹€鈹€6*[{polkitd}]
            鈹溾攢rsyslogd鈹€鈹€鈹€2*[{rsyslogd}]
            鈹溾攢sshd鈹€鈹€鈹€sshd鈹€鈹�攢bash鈹€鈹€鈹€pstree
            鈹            鈹斺攢bash
            鈹溾攢systemd-journal
            鈹溾攢systemd-logind
            鈹溾攢systemd-udevd
            鈹斺攢tuned鈹€鈹€鈹€4*[{tuned}]

    发现显示乱码,解决他:

    vi /etc/sysconfig/i18n

    写入下面内容:

    LANG="en_US"
    SUPPORTED="en_US:en"
    SYSFONT="latarcyrheb-sun16"

    使其生效(临时生效,重新登录会失效):

    source /etc/sysconfig/i18n

    然后再次执行pstree:

    [root@centos-clone1 ~]# pstree 
    systemd-+-agetty
            |-auditd---{auditd}
            |-chronyd
            |-crond
            |-dbus-daemon
            |-irqbalance
            |-lvmetad
            |-master-+-pickup
            |        `-qmgr
            |-nginx---nginx
            |-polkitd---6*[{polkitd}]
            |-rsyslogd---2*[{rsyslogd}]
            |-sshd-+-sshd---bash
            |      `-sshd---bash---pstree
            |-systemd-journal
            |-systemd-logind
            |-systemd-udevd
            `-tuned---4*[{tuned}]

    红色部分显示我们的pstree运行在一个bash下。

    我们使用命令创建一个子程序bash,并再次使用pstree查看:

    [root@centos-clone1 ~]# /bin/bash
    [root@centos-clone1 ~]# pstree
    systemd-+-agetty
            |-auditd---{auditd}
            |-chronyd
            |-crond
            |-dbus-daemon
            |-irqbalance
            |-lvmetad
            |-master-+-pickup
            |        `-qmgr
            |-nginx---nginx
            |-polkitd---6*[{polkitd}]
            |-rsyslogd---2*[{rsyslogd}]
            |-sshd-+-sshd---bash
            |      `-sshd---bash---bash---pstree
            |-systemd-journal
            |-systemd-logind
            |-systemd-udevd
            `-tuned---4*[{tuned}]

    我们可以看到,这次的pstree实在新创建的bash子进程上运行的。

    此时,我们有两层bash,我们可以使用exit退出一层:

    [root@centos-clone1 ~]# exit
    exit

    如果本身是最底层的bash,如果使用exit,则会logout。

    我们就可以在创建bash子进程的时候就运行脚本:

    [root@centos-clone1 ~]# /bin/bash test.txt
    hello World
    total 34
    lrwxrwxrwx    1 root root        7 Oct 14 20:48 bin -> usr/bin
    dr-xr-xr-x.   5 root root     4096 Oct 15 12:59 boot
    drwxr-xr-x   19 root root     3080 Oct 15 13:49 dev
    drwxr-xr-x.  76 root root     8192 Oct 22 15:21 etc
    drwxr-xr-x.   5 root root       40 Oct 20 15:57 home
    lrwxrwxrwx    1 root root        7 Oct 14 20:48 lib -> usr/lib
    lrwxrwxrwx    1 root root        9 Oct 14 20:48 lib64 -> usr/lib64
    drwxr-xr-x.   2 root root        6 Apr 11  2018 media116980

    我们发现子进程的PID号是116980,这是我们刚刚创建并运行test.txt的子进程号。

    但是我们运行echo $$看看:

    [root@centos-clone1 ~]# echo $$
    116262

    发现当前的PID并不是116980,说明一个问题,使用/bin/bash来创建子进程运行脚本,在运行完毕后,会结束子进程,回到父进程。

    而且每次使用bash来运行脚本,运行时的子进程PID都可能是不一样的。

    4.bash脚本

    我们在前面看到,使用/bin/bash运行一个命令文本,我们每次运行时都需要使用/bin/bash test.txt来运行。

    我们可以采用以下形式,将该文本默认使用/bin/bash执行,将文本内容修改为:

    #!/bin/bash
    echo "hello world"
    ls -l /
    echo $$

    我们在前面加了一行"#!/bin/bash",指定该文本运行的解释器。这样直接运行文件就可以执行了:

    # 将文本的权限修改为可执行
    chmod u+x test.txt
    #运行
    ./test.txt
    [root@centos-clone1 ~]# ./test.txt 
    hello World
    total 34
    lrwxrwxrwx    1 root root        7 Oct 14 20:48 bin -> usr/bin
    dr-xr-xr-x.   5 root root     4096 Oct 15 12:59 boot
    drwxr-xr-x   19 root root     3080 Oct 15 13:49 dev
    drwxr-xr-x.  76 root root     8192 Oct 22 15:21 etc
    drwxr-xr-x.   5 root root       40 Oct 20 15:57 home
    lrwxrwxrwx    1 root root        7 Oct 14 20:48 lib -> usr/lib
    lrwxrwxrwx    1 root root        9 Oct 14 20:48 lib64 -> usr/lib64
    ...
    119996

    同理,python脚本也是一样的,在开头写上"#!/usr/bin/python"等python命令的path。

    在这种脚本的执行方式下,实际上和/bin/bash test.txt一样,是创建了一个子进程来执行,执行完毕后,退出子进程回到父进程。

    5.一个重要的问题:为什么要在子进程中去执行脚本(重要)

    如果我们的执行脚本有问题,可能导致进程崩溃,那么如果在当前进程执行的话,可能导致bash崩溃。

    而我们如果每次执行脚本都fork出一个子进程执行,那么就算脚本有问题,崩溃的也是子进程,退出后回到父进程,还可以正常运行。

    二、脚本中的函数

    shell脚本也支持函数。定义好的函数就相当于一个命令。

    一个命令只有以下三种情况:

    • buildin
    • 一个可执行文件
    • 一个定义的function(可能包含多个命令)

    1.在交互命令行下定义一个函数

    [root@centos-clone1 ~]# ooxx(){
    > echo "Hello World"
    > ls -l /
    > echo $$
    > }

    ooxx是函数名,后面跟一个小括号,不用写参数,只是一个形式。然后接大括号,中间每一行都是一个命令。

    运行函数:

    [root@centos-clone1 ~]# ooxx
    Hello World
    total 34
    lrwxrwxrwx    1 root root        7 Oct 14 20:48 bin -> usr/bin
    dr-xr-xr-x.   5 root root     4096 Oct 15 12:59 boot
    drwxr-xr-x   19 root root     3080 Oct 15 13:49 dev
    drwxr-xr-x.  76 root root     8192 Oct 22 15:21 etc
    drwxr-xr-x.   5 root root       40 Oct 20 15:57 home
    lrwxrwxrwx    1 root root        7 Oct 14 20:48 lib -> usr/lib
    lrwxrwxrwx    1 root root        9 Oct 14 20:48 lib64 -> usr/lib64
    ......
    116262

    直接使用函数名运行,定义好的函数相当于一个命令。

    查看该函数的信息:

    [root@centos-clone1 ~]# type ooxx
    ooxx is a function
    ooxx () 
    { 
        echo "Hello World";
        ls --color=auto -l /;
        echo $$
    }

    可以看到ooxx是一个方法,并给出具体方法体。

    三、重定向

    首先重定向不是命令!!

    每个程序都有I/O:

    • 0:标准输入
    • 1:标准输出
    • 2:错误输出

    1.示例,查看程序I/O位置

    Linux一切皆文件,I/O也是抽象成文件的。

    首先,我们来查看一下bash的I/O。

    查看当前bash的进程号:

    [root@centos-clone1 ~]#  echo $$
    1594

    查看bash进程的I/O文件:

    [root@centos-clone1 fd]# cd /proc/$$/fd
    [root@centos-clone1 fd]# ll
    total 0
    lrwx------ 1 root root 64 Oct 24 01:05 0 -> /dev/pts/0
    lrwx------ 1 root root 64 Oct 24 01:05 1 -> /dev/pts/0
    lrwx------ 1 root root 64 Oct 24 01:05 2 -> /dev/pts/0
    lrwx------ 1 root root 64 Oct 24 22:25 255 -> /dev/pts/0

    $$表示当前bash的进程PID,为1594。在系统运行的时候,所有程序都会在/proc中抽象出一个文件夹,里面存放的是程序的运行数据。

    在/proc中找到名为1594的文件夹,这个文件夹就是bash的运行数据,里面有一个叫fd的文件夹(fd代表文件描述符),其中的0、1、2就是他的标准输入、标准输出】错误输出。

    这两个I/O都是默认指向/dev/pts/0的,也就是命令行终端。也就是为什么我们使用命令的时候,结果会显示在当前终端屏幕上的原因。

    2.开启第2个终端

    当我们使用ssh链接第二个终端(多用户登录)时,新的用户也会启动一个新的bash程序:

    [root@centos-clone1 ~]# echo $$
    63926

    查看新终端bash的I/O文件:

    [root@centos-clone1 ~]# cd /proc/63926/fd
    [root@centos-clone1 fd]# ll
    total 0
    lrwx------ 1 root root 64 Oct 24 22:29 0 -> /dev/pts/1
    lrwx------ 1 root root 64 Oct 24 22:29 1 -> /dev/pts/1
    lrwx------ 1 root root 64 Oct 24 22:29 2 -> /dev/pts/1
    lrwx------ 1 root root 64 Oct 24 22:30 255 -> /dev/pts/1

    我们发现他的标准输入输出都是指向/dev/pts/1的,说明每个终端都会对应一个输入输出文件(对应终端)。

    我们在设备信息中可以看到这两个终端的I/O设备:

    [root@centos-clone1 pts]# cd /dev/pts/
    [root@centos-clone1 pts]# ll
    total 0
    crw--w---- 1 root tty  136, 0 Oct 24 22:25 0
    crw--w---- 1 root tty  136, 1 Oct 24  2019 1
    c--------- 1 root root   5, 2 Oct 24 01:02 ptmx

    在/dev/pts中,我们看到0、1两个I/O设备,对应的就是我们启动的两个终端,对应两个bash进程。

    3.初试重定向

    我们可以将1号终端的标准输出重定向到2号终端,达到的效果是,1号终端运行"ls -l /"命令,返回的结果在2号终端显示。

    首先,我们备份1号终端的标准输出,以便于恢复:

    [root@centos-clone1 fd]# cd /proc/$$/fd
    [root@centos-clone1 fd]# ll
    total 0
    lrwx------ 1 root root 64 Oct 24 01:05 0 -> /dev/pts/0
    lrwx------ 1 root root 64 Oct 24 01:05 1 -> /dev/pts/0
    lrwx------ 1 root root 64 Oct 24 01:05 2 -> /dev/pts/0
    lrwx------ 1 root root 64 Oct 24 22:25 255 -> /dev/pts/0
    [root@centos-clone1 fd]# exec 6>&1
    [root@centos-clone1 fd]# ll
    total 0
    lrwx------ 1 root root 64 Oct 24 01:05 0 -> /dev/pts/0
    lrwx------ 1 root root 64 Oct 24 01:05 1 -> /dev/pts/0
    lrwx------ 1 root root 64 Oct 24 01:05 2 -> /dev/pts/0
    lrwx------ 1 root root 64 Oct 24 22:25 255 -> /dev/pts/0
    lrwx------ 1 root root 64 Oct 24 01:05 6 -> /dev/pts/0

    然后,将标准输出1,指向/dev/pts/1文件:

    [root@centos-clone1 fd]# exec 1> /dev/pts/1

    此时,终端1的标准输出已经重定向到终端2的输入设备:

    # 终端1中执行ls -l
    [root@centos-clone1 fd]# ls -l /usr
    [root@centos-clone1 fd]#

    终端1中什么都没有显示,观察终端2:

    # 终端2中显示了/usr中所有的内容列表
    total 128 dr-xr-xr-x. 2 root root 20480 Oct 23 21:20 bin drwxr-xr-x. 2 root root 6 Apr 11 2018 etc drwxr-xr-x. 2 root root 6 Apr 11 2018 games drwxr-xr-x. 42 root root 8192 Oct 21 16:03 include drwxr-xr-x 3 root root 51 Oct 22 14:56 java dr-xr-xr-x. 27 root root 4096 Oct 21 16:00 lib dr-xr-xr-x. 38 root root 20480 Oct 21 16:03 lib64 drwxr-xr-x. 22 root root 4096 Oct 21 16:00 libexec drwxr-xr-x. 13 root root 4096 Oct 21 23:35 local dr-xr-xr-x. 2 root root 16384 Oct 23 21:20 sbin drwxr-xr-x. 76 root root 4096 Oct 21 16:03 share drwxr-xr-x. 4 root root 32 Apr 11 2018 src lrwxrwxrwx 1 root root 10 Oct 14 20:48 tmp -> ../var/tmp

    实现完毕后,将终端1的标准输出流恢复回去:

    [root@centos-clone1 fd]# exec 1>&6
    [root@centos-clone1 fd]# ll
    total 0
    lrwx------ 1 root root 64 Oct 24 01:05 0 -> /dev/pts/0
    lrwx------ 1 root root 64 Oct 24 01:05 1 -> /dev/pts/0
    lrwx------ 1 root root 64 Oct 24 01:05 2 -> /dev/pts/0
    lrwx------ 1 root root 64 Oct 24 22:25 255 -> /dev/pts/0
    lrwx------ 1 root root 64 Oct 24 01:05 6 -> /dev/pts/0

    4.重定向输出到文件

    将ls -l /usr的输出重定向到文件中:

    [root@centos-clone1 ~]# ls -l /usr 1> ~/usr_list
    [root@centos-clone1 ~]# cat usr_list
    total
    128 dr-xr-xr-x. 2 root root 20480 Oct 23 21:20 bin drwxr-xr-x. 2 root root 6 Apr 11 2018 etc drwxr-xr-x. 2 root root 6 Apr 11 2018 games drwxr-xr-x. 42 root root 8192 Oct 21 16:03 include drwxr-xr-x 3 root root 51 Oct 22 14:56 java dr-xr-xr-x. 27 root root 4096 Oct 21 16:00 lib dr-xr-xr-x. 38 root root 20480 Oct 21 16:03 lib64 drwxr-xr-x. 22 root root 4096 Oct 21 16:00 libexec drwxr-xr-x. 13 root root 4096 Oct 21 23:35 local dr-xr-xr-x. 2 root root 16384 Oct 23 21:20 sbin drwxr-xr-x. 76 root root 4096 Oct 21 16:03 share drwxr-xr-x. 4 root root 32 Apr 11 2018 src lrwxrwxrwx 1 root root 10 Oct 14 20:48 tmp -> ../var/tmp

    如果进行第2次命令,也重定向到该文件,则内容会被覆盖:

    [root@centos-clone1 ~]# ls -l / 1> ~/usr_list
    [root@centos-clone1 ~]# cat usr_list 
    
    total 32
    lrwxrwxrwx    1 root root        7 Oct 14 20:48 bin -> usr/bin
    dr-xr-xr-x.   5 root root     4096 Oct 15 12:59 boot
    drwxr-xr-x   19 root root     3080 Oct 24 01:02 dev
    drwxr-xr-x.  76 root root     8192 Oct 24 01:02 etc
    drwxr-xr-x.   5 root root       40 Oct 20 15:57 home
    lrwxrwxrwx    1 root root        7 Oct 14 20:48 lib -> usr/lib
    lrwxrwxrwx    1 root root        9 Oct 14 20:48 lib64 -> usr/lib64
    drwxr-xr-x.   2 root root        6 Apr 11  2018 media
    drwxr-xr-x.   2 root root        6 Apr 11  2018 mnt
    drwxr-xr-x.   2 root root        6 Apr 11  2018 opt
    dr-xr-xr-x  115 root root        0 Oct 24 01:02 proc
    dr-xr-x---.   8 root root     4096 Oct 24 22:42 root
    drwxr-xr-x   23 root root      640 Oct 24 01:02 run
    lrwxrwxrwx    1 root root        8 Oct 14 20:48 sbin -> usr/sbin
    drwxrwx---    2 root leoshare   18 Oct 20 16:20 share
    drwxr-xr-x.   2 root root        6 Apr 11  2018 srv
    dr-xr-xr-x   13 root root        0 Oct 24 01:02 sys
    drwxrwxrwt.   9 root root     4096 Oct 24 03:44 tmp
    drwxr-xr-x.  14 root root     4096 Oct 22 14:55 usr
    drwxr-xr-x.  19 root root     4096 Oct 14 20:48 var

    所以">"符号叫做覆盖重定向

    如果想要追加,则使用">>"符号

    [root@centos-clone1 ~]# ls -l / 1>> ~/usr_list

    5.错误输出

    每个程序除了有标准输入和标准输出,还有错误输出,也就是2指向的文件。

    在Bash的错误输出默认是输出到终端的,也就是/dev/pts/下的0、1等设备。

    #god目录不存在的情况
    [root@centos-clone1 ~]# ls -l /god
    ls: cannot access /god: No such file or directory

    此时返回的错误信息就是错误输出。

    将错误输出重定向到文件(使用"2>"符号):

    [root@centos-clone1 ~]# ls -l /god 2> err.log
    [root@centos-clone1 ~]# cat err.log 
    ls: cannot access /god: No such file or directory

    6.重定向的绑定顺序

    示例:

    # /god目录不存在,/usr目录存在
    ls -l /god /usr 2>&1 1> a.out
    [root@centos-clone1 ~]# ls -l /god /usr 2>&1 1> a.out
    ls: cannot access /god: No such file or directory
    [root@centos-clone1 ~]# cat a.out 
    /usr:
    total 128
    40 dr-xr-xr-x.  2 root root 20480 Oct 23 21:20 bin
     0 drwxr-xr-x.  2 root root     6 Apr 11  2018 etc
     0 drwxr-xr-x.  2 root root     6 Apr 11  2018 games
    12 drwxr-xr-x. 42 root root  8192 Oct 21 16:03 include
     0 drwxr-xr-x   3 root root    51 Oct 22 14:56 java
     4 dr-xr-xr-x. 27 root root  4096 Oct 21 16:00 lib
    40 dr-xr-xr-x. 38 root root 20480 Oct 21 16:03 lib64
     4 drwxr-xr-x. 22 root root  4096 Oct 21 16:00 libexec
     4 drwxr-xr-x. 13 root root  4096 Oct 21 23:35 local
    20 dr-xr-xr-x.  2 root root 16384 Oct 23 21:20 sbin
     4 drwxr-xr-x. 76 root root  4096 Oct 21 16:03 share
     0 drwxr-xr-x.  4 root root    32 Apr 11  2018 src
     0 lrwxrwxrwx   1 root root    10 Oct 14 20:48 tmp -> ../var/tmp

    上述运行结果中,/usr的内容列表被保存在了a.out中,而ls -l /god的错误信息打印在了屏幕上。

    说明错误输出流没有绑定生效,我们将2个绑定换一下位置:

    ls -l /god /usr 1> a.out 2>&1
    [root@centos-clone1 ~]# ls -l /god /usr  1> a.out 2>&1
    [root@centos-clone1 ~]# cat a.out 
    ls: cannot access /god: No such file or directory
    /usr:
    total 128
    40 dr-xr-xr-x.  2 root root 20480 Oct 23 21:20 bin
     0 drwxr-xr-x.  2 root root     6 Apr 11  2018 etc
     0 drwxr-xr-x.  2 root root     6 Apr 11  2018 games
    12 drwxr-xr-x. 42 root root  8192 Oct 21 16:03 include
     0 drwxr-xr-x   3 root root    51 Oct 22 14:56 java
     4 dr-xr-xr-x. 27 root root  4096 Oct 21 16:00 lib
    40 dr-xr-xr-x. 38 root root 20480 Oct 21 16:03 lib64
     4 drwxr-xr-x. 22 root root  4096 Oct 21 16:00 libexec
     4 drwxr-xr-x. 13 root root  4096 Oct 21 23:35 local
    20 dr-xr-xr-x.  2 root root 16384 Oct 23 21:20 sbin
     4 drwxr-xr-x. 76 root root  4096 Oct 21 16:03 share
     0 drwxr-xr-x.  4 root root    32 Apr 11  2018 src
     0 lrwxrwxrwx   1 root root    10 Oct 14 20:48 tmp -> ../var/tmp

    此时,我们发现标准输出和错误输出都输出到了a.out。

    以上实验说明重定向绑定的顺序是从左到右的。

    标准输出和错误输出定位到一个文件的特殊写法

    # 两者一样的效果,都是将1、2都定向到a.out
    [root@centos-clone1 ~]# ls -l /god /usr >& a.out
    [root@centos-clone1 ~]# ls -l /god /usr &> a.out

    7.输入重定向

    输入重定向有三种方式:

    • 输入一行字符串,使用"<<<"
    • 输入一大段文本,使用"<<"
    • 输入一个文件,使用"<"

    read命令:接收标准输入的字符串,以换行符结果,类似python的input:

    [root@centos-clone1 ~]# read var1
    123abcd
    [root@centos-clone1 ~]# echo $var1
    123abcd

    我们可以用以下形式重定向输入:

    [root@centos-clone1 ~]# read var1 0<<<"abcd123" 
    [root@centos-clone1 ~]# echo $var1
    abcd123

    使用三个"<"表示将一行字符串重定向到输入。

    重定向一大段文本到输入:

    [root@centos-clone1 ~]# read var2 0<<ooxx
    > abc
    > 123
    > uuiery
    > sdfj
    > ooxx
    [root@centos-clone1 ~]# echo $var2
    abc

    使用两个"<"可以将多行文本重定向到输入,以ooxx作为整段文本的结束字符。

    但是我们在打印$var2时,发现只有abc三个字符,这是因为read命令对换行符敏感,在输入流中确实有我们输入的所有字符,但是read只读到了第一行。

    此时,我们将read换为cat,就正确了:

    [root@centos-clone1 ~]# cat 0<<ooxx
    > abc
    > 123
    > skdfjksf
    > ooxx
    abc
    123
    skdfjksf

    我们可以看到,cat正确读到了除了ooxx(我们定义的结束符)以外的所有行内容。

    将文件重定向到标准输入:

    [root@centos-clone1 ~]# cat 0< myname.txt 
    My name is Leo...

    这里只是一个示例,没有实际应用价值,因为上述命令等效于cat myname.txt。

    8.一个综合的重定向示例(通过重定向请求www.baidu.com首页)

    我们使用exec定义一个TCP socket:

    # 将描述符8指向tcp socket,与baidu建立连接
    [root@centos-clone1 fd]# exec 8<> /dev/tcp/www.baidu.com/80
    # 向8发送http请求头
    [root@centos-clone1 fd]# echo -e "GET / HTTP/1.0
    " 1>&8
    # 从8接收http响应
    [root@centos-clone1 fd]# cat 0<&8

    我们发现,www.baidu.com返回的响应打印在屏幕上:

    [root@centos-clone1 fd]# cat 0<&8
    HTTP/1.0 200 OK
    Accept-Ranges: bytes
    Cache-Control: no-cache
    Content-Length: 14615
    Content-Type: text/html
    Date: Fri, 25 Oct 2019 06:38:48 GMT
    P3p: CP=" OTI DSP COR IVA OUR IND COM "
    P3p: CP=" OTI DSP COR IVA OUR IND COM "
    Pragma: no-cache
    Server: BWS/1.1
    Set-Cookie: BAIDUID=160ACF42CC151F0ED2B6D3241C0FE02A:FG=1; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
    Set-Cookie: BIDUPSID=160ACF42CC151F0ED2B6D3241C0FE02A; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
    Set-Cookie: PSTM=1571985528; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
    Set-Cookie: BAIDUID=160ACF42CC151F0EA49EFE62D0FE9DB0:FG=1; max-age=31536000; expires=Sat, 24-Oct-20 06:38:48 GMT; domain=.baidu.com; path=/; version=1; comment=bd
    Traceid: 157198552802713482341501073886667258581
    Vary: Accept-Encoding
    X-Ua-Compatible: IE=Edge,chrome=1
    ......
    ......
    ......
    http://.+.baidu.com'))[0];}}name && ns_c({'fm': 'behs','tab': name,'query': encodeURIComponent(key),'un': encodeURIComponent(bds.comm.user || '') });};}})();};if(window.pageState==0){initIndex();}})();document.cookie = 'IS_STATIC=1;expires=' + new Date(new Date().getTime() + 10*60*1000).toGMTString();</script>
    </body></html>

    如果我们想存入文件,在使用cat读取响应的时候,将输出重定向到文件就可以了:

    [root@centos-clone1 fd]# exec 8<> /dev/tcp/www.baidu.com/80
    [root@centos-clone1 fd]# echo -e "GET / HTTP/1.0
    " 1>&8
    [root@centos-clone1 fd]# cat 0<&8 1> ~/baidu.txt
    [root@centos-clone1 fd]# cat ~/baidu.txt 

    注意:如果在创建了socket后,我们过一段时间才发送请求,可能因为socket链接超时而收不到响应,此时应该重新绑定8>/dev/tcp/www..baidu.com/80。

    四、变量

    1.变量生命周期

    变量的生命周期是在当前运行bash的进程中的,当前bash退出后,变量也就消亡了。

    [root@centos-clone1 ~]# myname=Leo
    [root@centos-clone1 ~]# echo $myname 
    Leo
    [root@centos-clone1 ~]# /bin/bash
    [root@centos-clone1 ~]# echo $myname
    
    [root@centos-clone1 ~]# exit
    exit
    [root@centos-clone1 ~]# echo $myname
    Leo

    我们可以看到,定义一个变量myname为Leo,当前进程中打印$myname输出Leo正确。

    我们启动一个bash子进程,并在子进程中打印$myname,输出空,表示没有这个变量。

    退出子进程回到父进程,再次打印$myname,正确显示Leo。

    2.局部变量

    在函数内部,我们可以定义局部变量:

    [root@centos-clone1 ~]# ooxx(){
    > local age=32
    > echo $age
    > }
    [root@centos-clone1 ~]# ooxx
    32
    [root@centos-clone1 ~]# echo $age
    #输出空

    可以看到在函数内部定义的局部变量的生命周期只是在函数内部,函数执行完后,局部变量消亡。

    3.函数内部访问普通变量

    [root@centos-clone1 ~]# ooxx(){
    > echo $myname
    > myname=John
    > echo $myname
    > }
    [root@centos-clone1 ~]# ooxx
    Leo
    John
    [root@centos-clone1 ~]# echo $myname
    John

    可以看到,函数内部可以访问普通变量,并可以修改其值。

    4.参数

    对于shell脚本和shell函数,我们都需要传入参数。

    和JAVA,C++等高级语言不同,shell的参数不需要形参,而是通过一些符号来取值:

    [root@centos-clone1 ~]# ooxx(){
    > echo $1
    > echo $2
    > echo $#
    > }
    [root@centos-clone1 ~]# ooxx 1 2 3 4 5
    1
    2
    5

    我们可以看到,shell中的参数是使用$1、$2、$#、$*等来取值的,如下表所示:

    # 参数个数
    $#
    # 参数列表
    $*
    # 参数列表
    $@
    # 第一个参数
    $1
    # 第二个参数
    $2
    # 第11个参数
    ${11}

    使用示例:

    # 创建一个脚本
    vi test.sh

    # 输入内容 echo $1 echo $2 echo $11 echo ${11} echo $# echo $* echo $@

    运行脚本:

    [root@centos-clone1 ~]# . test.sh 1 2 3 4 5 6 7 8 9 0 a b c
    1
    2
    11
    a
    13
    1 2 3 4 5 6 7 8 9 0 a b c
    1 2 3 4 5 6 7 8 9 0 a b c

    我们输入了13个参数

    $1为1正确。$2为2正确。$11为11,不正确,是先取了$1然后拼接上1,得到11。${11}取第11个参数为a是正确的。

    $#输出13正确,一共有13个参数。$*和$@输出参数列表。

    5.管道

    重要:管道符号"|"实际上会开启多个子进程来完成前后的命令。

    [root@centos-clone1 ~]# ls -l / | more

    也就是"|"左边的ls命令和右边的more命令,会分别新建一个子进程来分别执行。管道打通两个子进程之间的通道,将ls的输出传递给more命令。

    我们验证一下:

    [root@centos-clone1 ~]# echo $$
    1594
    [root@centos-clone1 ~]# echo $$ | more
    1594

    第一个echo $$打印的是当前进程的PID,而根据上述的说法,第二个命令打印的PID不应该是1594。这里出现了问题:

    我们将echo $$换一个命令:

    [root@centos-clone1 ~]# echo $BASHPID
    1594
    [root@centos-clone1 ~]# echo $BASHPID | more
    113173

    使用echo $BASHPID同样获取的是进程的PID。但是第二个命令的结果却真正输出了子进程的PID。

    这是因为,echo $$的优先级高于管道符号"|":

    # bash先看到echo $$并将其执行,结果替换为了1594,然后再启动子进程
    echo $$ | more
    # 相当于
    echo 1594 | more

    而echo $BASHPID只是一个普通取值,优先级低于管道符号"|":

    # 先启动了子进程,然后在子进程中执行echo $BASHPID
    echo $BASHPID | more
    # 所以$BASHPID拿到的是子进程的PID

    6.判断一个命令是否执行成功

    [root@centos-clone1 ~]# ls -l /
    total 32
    lrwxrwxrwx    1 root root        7 Oct 14 20:48 bin -> usr/bin
    dr-xr-xr-x.   5 root root     4096 Oct 15 12:59 boot
    drwxr-xr-x   19 root root     3080 Oct 24 01:02 dev
    drwxr-xr-x.  76 root root     8192 Oct 24 01:02 etc
    drwxr-xr-x.   5 root root       40 Oct 20 15:57 home
    lrwxrwxrwx    1 root root        7 Oct 14 20:48 lib -> usr/lib
    lrwxrwxrwx    1 root root        9 Oct 14 20:48 lib64 -> usr/lib64
    drwxr-xr-x.   2 root root        6 Apr 11  2018 media
    drwxr-xr-x.   2 root root        6 Apr 11  2018 mnt
    drwxr-xr-x.   2 root root        6 Apr 11  2018 opt
    dr-xr-xr-x  114 root root        0 Oct 24 01:02 proc
    dr-xr-x---.   8 root root     4096 Oct 25 15:11 root
    drwxr-xr-x   23 root root      640 Oct 24 01:02 run
    lrwxrwxrwx    1 root root        8 Oct 14 20:48 sbin -> usr/sbin
    drwxrwx---    2 root leoshare   18 Oct 20 16:20 share
    drwxr-xr-x.   2 root root        6 Apr 11  2018 srv
    dr-xr-xr-x   13 root root        0 Oct 24 22:44 sys
    drwxrwxrwt.   9 root root     4096 Oct 25 14:30 tmp
    drwxr-xr-x.  14 root root     4096 Oct 22 14:55 usr
    drwxr-xr-x.  19 root root     4096 Oct 14 20:48 var
    [root@centos-clone1 ~]# echo $?
    0
    [root@centos-clone1 ~]# ls -l /god
    ls: cannot access /god: No such file or directory
    [root@centos-clone1 ~]# echo $?
    2

    我们可以看到,当ls执行成功时,$?的值为0。

    当ls执行失败时,$?的值为非0。

    所以,我们在脚本中可以通过判断$?来判断前面的命令是否执行成功。

    五、环境变量

    前面我们提到的变量的生命周期都是限于当前进程的。

    我们的父进程要创建一个子进程,而子进程也要获取到某个变量的值(环境变量)。

    什么叫做环境变量,环境可能是包含多个进程的一个环境,则环境变量能够供多个进程访问。

    [root@centos-clone1 ~]# echo $env_var
    999
    [root@centos-clone1 ~]# /bin/bash
    [root@centos-clone1 ~]# echo $env_var
    999

    使用export将变量导出,则可以供子进程访问。

    注意:这里的export是导出而不是共享,也就是说该变量可供该父进程创建的所有子进程访问,但是不是共享的。当子进程修改该变量时,父进程中这个变量的值不发生改变。反之同理。

    重要:export导出的变量,相当于在子进程中有一个拷贝,各自拥有一个独立的副本。

    在这种原理下,我们考虑一个场景:

    当父进程中有大量的导出的环境变量,每个变量的值占用空间很大。所有环境变量加起来例如有10GB,那么父进程创建子进程时,理论上会将所有的环境变量进行拷贝。子进程创建速度会非常缓慢。

    操作系统内核设计者如何解决这个问题,答案时使用copy on write技术。

    即,创建子进程时,所有的环境变量以引用的方式传递给子进程,当父进程要修改某个环境变量时,在修改之前,将副本拷贝给子进程,这样将10G的变量分时拷贝,就可以解决这个问题。

    同样的,当子进程要修改某个变量时,则先拷贝该变量,然后再进行修改。

    六、其他一些知识点

    1.引用

    双引号引用:

    [root@centos-clone1 ~]# name=Leo
    [root@centos-clone1 ~]# echo $name
    Leo
    [root@centos-clone1 ~]# echo "$name say hello"  
    Leo say hello

    双引号可以拼接中间带空格的字符串。并且其中的$name优先级更好,bash会取到name的值。

    单引号引用:

    [root@centos-clone1 ~]# name=Leo
    [root@centos-clone1 ~]# echo $name
    Leo
    [root@centos-clone1 ~]# echo '$name say hello'
    $name say hello

    我们发现单引号中的$name无法取值,说明$取值的优先级低于单引号。

    2.将命令输出赋值给变量

    [root@centos-clone1 ~]# lines=`ls -l / | wc -l`
    [root@centos-clone1 ~]# echo $lines
    21

    整个命令用反引号引起来。

    七、表达式

    1.算术表达式

    第一种形式:let c=$a+$b

    [root@centos-clone1 ~]# a=1
    [root@centos-clone1 ~]# b=2
    [root@centos-clone1 ~]# let c=$a+$b
    [root@centos-clone1 ~]# echo $c
    3

    第二种形式:c=$((a+b))

    [root@centos-clone1 ~]# c=$((a+b))
    [root@centos-clone1 ~]# echo $c
    3

    2.条件表达式(逻辑表达式)

    使用test做条件判断,查看help test:

    [root@centos-clone1 ~]# help test
    test: test [expr]
        Evaluate conditional expression.
        
        File operators:
        
          -a FILE        True if file exists.
          -b FILE        True if file is block special.
          -c FILE        True if file is character special.
          -d FILE        True if file is a directory.
          -e FILE        True if file exists.
          -f FILE        True if file exists and is a regular file.
          -g FILE        True if file is set-group-id.
          -h FILE        True if file is a symbolic link.
          -L FILE        True if file is a symbolic link.
          -k FILE        True if file has its `sticky' bit set.
          -p FILE        True if file is a named pipe.
          -r FILE        True if file is readable by you.
          -s FILE        True if file exists and is not empty.
          -S FILE        True if file is a socket.
          -t FD          True if FD is opened on a terminal.
          -u FILE        True if the file is set-user-id.
          -w FILE        True if the file is writable by you.
          -x FILE        True if the file is executable by you.
          -O FILE        True if the file is effectively owned by you.
          -G FILE        True if the file is effectively owned by your group.
          -N FILE        True if the file has been modified since it was last read.
        
          FILE1 -nt FILE2  True if file1 is newer than file2 (according to
                           modification date).
        
          FILE1 -ot FILE2  True if file1 is older than file2.
        
          FILE1 -ef FILE2  True if file1 is a hard link to file2.
        
        String operators:
        
          -z STRING      True if string is empty.
        
          -n STRING
             STRING      True if string is not empty.
        
          STRING1 = STRING2
                         True if the strings are equal.
          STRING1 != STRING2
                         True if the strings are not equal.
          STRING1 < STRING2
                         True if STRING1 sorts before STRING2 lexicographically.
          STRING1 > STRING2
                         True if STRING1 sorts after STRING2 lexicographically.
        
        Other operators:
        
          -o OPTION      True if the shell option OPTION is enabled.
          -v VAR     True if the shell variable VAR is set
          ! EXPR         True if expr is false.
          EXPR1 -a EXPR2 True if both expr1 AND expr2 are true.
          EXPR1 -o EXPR2 True if either expr1 OR expr2 is true.
        
          arg1 OP arg2   Arithmetic tests.  OP is one of -eq, -ne,
                         -lt, -le, -gt, or -ge.

    可以从帮助中看到,test可以做文件状态的判断,文件之间的比较,以及字符串的比较,数值的比较等等。

    使用test比较两个数字:

    [root@centos-clone1 ~]# test 3 -gt 8
    [root@centos-clone1 ~]# echo $?
    1

    3大于8,结果应该是假,所以$?的值为非0。

    [root@centos-clone1 ~]# test 3 -lt 8
    [root@centos-clone1 ~]# echo $?
    0

    反之,命令的执行状态为0,表示结果为真。

    由于运行的结果是以命令成败与否来判断,则一般使用以下形式:

    [root@centos-clone1 ~]# test 3 -gt 8 && echo "It's true"

    当前面成功运行时,后面的才会执行。

    也可以写成更清晰的形式:

    [root@centos-clone1 ~]# [ 3 -gt 8 ] && echo "It's true"

    我们看一下"[]"的信息:

    [root@centos-clone1 ~]# type '['
    [ is a shell builtin

    发现[]是一个bash内建命令,是命令后面就必须跟一个空格。。。

    [root@centos-clone1 ~]# [3 -gt 8 ] && echo "It's true"  
    bash: [3: command not found
    [root@centos-clone1 ~]# [ 3 -gt 8] && echo "It's true" 
    bash: [: missing `]'

    前中括号的后面以及后中括号的前面都必要带空格,否则会报错。

    总结:只要是命令,后面必须有空格。

    如果要取反:

    [root@centos-clone1 ~]# [ ! 3 -gt 8 ] && echo "It's true"  

    取反就在前面加"!"。

    八、示例(编写一个自动创建用户的脚本)

    #!/bin/bash
    [ $# -eq 2 ] || exit 2
    adduser $1
    echo $2 | passwd --stdin $1 &> /dev/null
    echo "Add User Seccuss..."

    解释:

    1)[ $# -eq 2 ] || exit 2,表示判断参数是否是2个,如果不是2个,则退出脚本。exit后面跟数字标准脚本的执行状态,0表示正确退出,1-127表示错误

    2) 创建用户,用户名从第一个参数获取

    3) 设置用户密码,从第二个参数获取,注意这里passwd命令要接受标准输入必须使用--stdin选项。并将命令的返回信息全部扔掉,/dev/null是一个数据黑洞。

    4) 打印一个消息,表示添加成功。

    添加用户是否存在的验证:

    [root@centos-clone1 ~]# id leokale
    uid=1003(leokale) gid=1004(leokale) groups=1004(leokale)
    [root@centos-clone1 ~]# echo $?
    0

    当用户存在时id命令执行正确,返回0。

    [root@centos-clone1 ~]# id leokale
    id: leokale: no such user
    [root@centos-clone1 ~]# echo $?
    1

    当用户不存在时,id命令执行失败,返回非0数字1。

    所以,我们可以通过id命令的执行状态来判断该用户是否存在:

    #!/bin/bash
    [ ! $# -eq 2 ] && echo "number of args wrong.." && exit 2
    id $1 &> /dev/null && echo "User is exist..." && exit 5
    adduser $1
    echo $2 | passwd --stdin $1 &> /dev/null
    echo "Add User Seccuss..."

    通过id命令判断指定创建的用户是否存在,如果存在,则提示用户存在,然后错误退出,退出码为5。如果用户不存在,则执行后面的逻辑,创建指定用户。

    只有root用户才有权限添加用户,我们要考虑普通用户误运行该脚本的情况:

    #!/bin/bash
    [ ! $# -eq 2 ] && echo "number of args wrong.." && exit 2
    id $1 &> /dev/null && echo "User is exist..." && exit 5
    adduser $1 &>/dev/null && echo $2 | passwd --stdin $1 &> /dev/null && echo "Add User Seccuss..." $$ exit 0
    echo "Somewhere wrong.."
    exit 7

    我们将创建用户,创建密码都用&&串起来,这样当创建用户失败的情况下,横向命令都不会继续执行,然后错误退出,退出码为7。

    # 使用普通用户leo来执行脚本
    [leo@centos-clone1 tmp]$ ./addUser number of args wrong.. [leo@centos-clone1 tmp]$ ./addUser leokale 52myself User is exist... [leo@centos-clone1 tmp]$ ./addUser sjdjkf sdf Somewhere wrong..

    九、控制语句

    1.if判断语句

    查看if的帮助:

    [leo@centos-clone1 tmp]$ help if
    if: if COMMANDS; then COMMANDS; [ elif COMMANDS; then COMMANDS; ]... [ else COMMANDS; ] fi
        Execute commands based on conditional.
        ......
        ......
    
        Exit Status:
        Returns the status of the last command executed.

    我们可以看到if语句的基本形式为:if 命令; then 命令; else 命令; fi。。以if开始,以fi结尾。

    if语句使用示例:

    [root@centos-clone1 ~]# vi test2.sh
    if ls -l / &> /dev/null; then echo "OK!"; else echo "Not OK!"; fi
    [root@centos-clone1 ~]# vi test2.sh
    if [ 3 -gt 8 ]; then echo "OK!"; else echo "Not OK!"; fi

    分行写法:

    [root@centos-clone1 ~]# vi test2.sh 
    if ls -l / &> /dev/null; then 
    echo "OK!" else
    echo "Not OK!" fi

    2.for循环

    查看for的帮助:

    [root@centos-clone1 ~]# help for
    for: for NAME [in WORDS ... ] ; do COMMANDS; done
        Execute commands for each member in a list.
        ......
        
    for ((: for (( exp1; exp2; exp3 )); do COMMANDS; done
        Arithmetic for loop.
        
        Equivalent to
            (( EXP1 ))
            while (( EXP2 )); do
                    COMMANDS
                    (( EXP3 ))
            done
     
        ......

    for循环使用示例:

    [root@centos-clone1 ~]# vi test3.sh
    for((i=0;i<10;i++));do echo $i;done
    [root@centos-clone1 ~]# chmod +x test3.sh
    [root@centos-clone1 ~]# ./test3.sh 
    0
    1
    2
    3
    4
    5
    6
    7
    8
    9

    分行写:

    [root@centos-clone1 ~]# vi test3.sh 
    for((i=0;i<10;i++)); do
    echo $i done

    增强for循环:

    [root@centos-clone1 ~]# vi test4.sh
    for name in leo john "leo kale";do echo $name;done
    [root@centos-clone1 ~]# ./test4.sh 
    leo
    john
    leo kale

    分行写法:

    [root@centos-clone1 ~]# vi test4.sh 
    for name in leo john "leo kale";do 
        echo $name
    done

     3.while循环

    [root@centos-clone1 ~]# vi test5.sh
    while ls -l /god;do
        echo "ok"
        rm -rf /god 
    done
    [root@centos-clone1 ~]# ./test5.sh
    total 0
    ok
    ls: cannot access /god: No such file or directory

    预先创建一个/god目录,然后执行上述脚本,循环第一次发现有/god目录,然后执行删除操作,第二轮循环发现目录不存在,则退出。

    4.case多分支

    [root@centos-clone1 ~]# vi test6.sh 
    name=$1
    
    case $name in
    "john")
        echo "John";;
    "leo")
        echo "Leo";;
    *)
        echo "wrong";;
    esac
    [root@centos-clone1 ~]# ./test6.sh leo
    Leo
    [root@centos-clone1 ~]# ./test6.sh john
    John
    [root@centos-clone1 ~]# ./test6.sh leokale
    wrong

    十、示例

    增强For循环读取一个文件,打印每一行,并统计行数:

    [root@centos-clone1 ~]# vi scanfile.sh 
    
    #!/bin/bash
    oldIFS=$IFS
    IFS=$'
    '
    num=0
    content=`cat testfile.txt`
    for i in $content;do
        echo $i
        ((num++))
    done
    echo "$num lines"
    
    IFS=$oldIFS

    解释:

    1)首先我们备份IFS,IFS是一个环境变量,其中定义了做文本分割的符号,例如空格、换行等。

    2)将IFS赋值为只有换行符,否则文件中的每一行如果存在空格,则会被自动切分成多行。

    3)完成后,需要将IFS回复,不影响其他程序的运行。

    逐步For循环方式:

    [root@centos-clone1 ~]# vi scanfile.sh 
    #!/bin/bash
    
    num=0
    lines=`cat testfile.txt | wc -l`
    for((i=1;i<=lines;i++));do
            head -$i testfile.txt | tail -1
            ((num++))
    done
    echo num:$num

    解释:

    1)使用cat testfile.txt | wc -l获取文件行数

    2) 从第1行开始for循环遍历,获取每一行内容,并打印

    while循环方式:

    [root@centos-clone1 ~]# vi scanfile.sh 
    #!/bin/bash
    
    exec 8<$0
    exec 0< testfile.txt
    
    num=0
    while read line;do
            echo $line
            ((num++))
    done 
    echo num:$num
    exec 0<&8

    解释:

    1)先备份准备输入

    2)重定向标准输入为testfile.txt文件

    3)使用read从输入流中读取一行(read对换行符敏感),循环读取

    4)回复标准输入

    保持学习,否则迟早要被淘汰*(^ 。 ^ )***
  • 相关阅读:
    ural 1519 fomular 1 既插头DP学习笔记
    2016集训测试赛(十九)Problem C: 无聊的字符串
    2016集训测试赛(十九)Problem A: 24点大师
    2016集训测试赛(二十)Problem B: 字典树
    写一个addEventListener以及removeEventListener
    关于props的注意事项!
    swiper轮播始终居中active图片
    vue中登录模块的插件封装
    v-show
    v-if
  • 原文地址:https://www.cnblogs.com/leokale-zz/p/11726952.html
Copyright © 2020-2023  润新知