Linux——(4)腳本編程

1、bash shellhtml

能夠理解爲一種解釋器和啓動器,解釋命令文本,並執行命令。java

命令來源:python

  • 用戶交互輸入
  • 文本文件輸入

 

1.示例,寫一個最簡單的文本nginx

vi test.txt

寫入如下內容:chrome

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

這樣就寫好了一個最簡單的腳本,其中$$表示運行當前腳本的bash的進程號。shell

 

2.如何運行這個文本中的命令express

# 使用bash內建命令source來運行
source test.txt
# 使用"."來運行,注意點後面是空格
. test.txt

實際上source和"."都是bash的biuldin命令:centos

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

source和"."是等效的,意思是解釋文本中的命令,並執行。咱們常用的source /etc/profile就是讓系統從新執行一下/etc/profile配置腳本。bash

3.使用bash子進程運行cookie

上面使用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出一個子進程執行,那麼就算腳本有問題,崩潰的也是子進程,退出後回到父進程,還能夠正常運行。

 

2、腳本中的函數

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是一個方法,並給出具體方法體。

 

3、重定向

首先重定向不是命令!!

每一個程序都有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、一、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。

以上實驗說明重定向綁定的順序是從左到右的。

標準輸出和錯誤輸出定位到一個文件的特殊寫法

# 二者同樣的效果,都是將一、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\n" 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\n" 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。

 

2、變量

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
# 第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。

因此,咱們在腳本中能夠經過判斷$?來判斷前面的命令是否執行成功。

 

3、環境變量

前面咱們提到的變量的生命週期都是限於當前進程的。

咱們的父進程要建立一個子進程,而子進程也要獲取到某個變量的值(環境變量)。

什麼叫作環境變量,環境多是包含多個進程的一個環境,則環境變量可以供多個進程訪問。

[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的變量分時拷貝,就能夠解決這個問題。

一樣的,當子進程要修改某個變量時,則先拷貝該變量,而後再進行修改。

 

4、其餘一些知識點

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

整個命令用反引號引發來。

 

5、表達式

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"  

取反就在前面加"!"。

 

6、示例(編寫一個自動建立用戶的腳本)

#!/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..

 

7、控制語句

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

 

8、示例

加強For循環讀取一個文件,打印每一行,並統計行數:

[root@centos-clone1 ~]# vi scanfile.sh 

#!/bin/bash
oldIFS=$IFS
IFS=$'\n'
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)回覆標準輸入

相關文章
相關標籤/搜索