<em id="pn7p8"><acronym id="pn7p8"><u id="pn7p8"></u></acronym></em>

    <th id="pn7p8"></th>

    <button id="pn7p8"></button>

      <dd id="pn7p8"></dd>
      <progress id="pn7p8"><track id="pn7p8"></track></progress>

      Linux培訓
      達內IT學院

      400-111-8989

      Linux Shell編程之常用技巧

      • 發布:Linux培訓
      • 來源:Linux基礎
      • 時間:2016-09-30 10:23

      前言

      本文集中介紹了bash編程中部分高級編程方法和技巧。通過學習本文內容,可以幫你解決以下問題:

      1、bash可以網絡編程么?

      2、.(){ .|.& };. 據說執行這些符號可以死機,那么它們是啥意思?

      3、你是什么保證crond中的任務不重復執行的?grep一下然后wc算一下個數么?

      4、受限模式執行bash可以保護什么?

      5、啥時候會出現subshell?

      6、coproc協進程怎么用?

      /dev和/proc目錄

      dev目錄是系統中集中用來存放設備文件的目錄。除了設備文件以外,系統中也有不少特殊的功能通過設備的形式表現出來。設備文件是一種特殊的文件,它們實際上是驅動程序的接口。在Linux操作系統中,很多設備都是通過設備文件的方式為進程提供了輸入、輸出的調用標準,這也符合UNIX的“一切皆文件”的設計原則。所以,對于設備文件來說,文件名和路徑其實都不重要,最重要的使其主設備號和輔助設備號,就是用ls -l命令顯示出來的原本應該出現在文件大小位置上的兩個數字,比如下面命令顯示的8和0:

      [zorro@zorrozou-pc0 bash]$ ls -l /dev/sda

      brw-rw---- 1 root disk 8, 0 5月 12 10:47 /dev/sda

      設備文件的主設備號對應了這種設備所使用的驅動是哪個,而輔助設備號則表示使用同一種驅動的設備編號。我們可以使用mknod命令手動創建一個設備文件:

      [zorro@zorrozou-pc0 bash]$ sudo mknod harddisk b 8 0

      [zorro@zorrozou-pc0 bash]$ ls -l harddisk

      brw-r--r-- 1 root root 8, 0 5月 18 09:49 harddisk

      這樣我們就創建了一個設備文件叫harddisk,實際上它跟/dev/sda是同一個設備,因為它們對應的設備驅動和編號都一樣。所以這個設備實際上是跟sda相同功能的設備。

      系統還給我們提供了幾個有特殊功能的設備文件,在bash編程的時候可能會經常用到:

      /dev/null:黑洞文件。可以對它重定向如何輸出。

      /dev/zero:0發生器。可以產生二進制的0,產生多少根使用時間長度有關。我們經常用這個文件來產生大文件進行某些測試,如:

      [zorro@zorrozou-pc0 bash]$ dd if=/dev/zero of=./bigfile bs=1M count=1024

      1024+0 records in

      1024+0 records out

      1073741824 bytes (1.1 GB, 1.0 GiB) copied, 0.3501 s, 3.1 GB/s

      dd命令也是我們在bash編程中可能會經常使用到的命令。

      /dev/random:Linux下的random文件是一個根據計算機背景噪聲而產生隨機數的真隨機數發生器。所以,如果容納噪聲數據的熵池空了,那么對文件的讀取會出現阻塞。

      /dev/urandom:是一個偽隨機數發生器。實際上在Linux的視線中,urandom產生隨機數的方法根random一樣,只是它可以重復使用熵池中的數據。這兩個文件在不同的類unix系統中可能實現方法不同,請注意它們的區別。

      /dev/tcp & /dev/udp:這兩個神奇的目錄為bash編程提供了一種可以進行網絡編程的功能。在bash程序中使用/dev/tcp/ip/port的方式就可以創建一個scoket作為客戶端去連接服務端的ip:port。我們用一個檢查http協議的80端口是否打開的例子來說明它的使用方法:

      [zorro@zorrozou-pc0 bash]$ cat tcp.sh

      #!/bin/bash

      ipaddr=127.0.0.1

      port=80

      if ! exec 5<> /dev/tcp/$ipaddr/$port

      then

      exit 1

      fi

      echo -e "GET / HTTP/1.0\n" >&5

      cat <&5

      ipaddr的部分還可以寫一個主機名。大家可以用此腳本分別在本機打開web服務和不打開的情況下分別執行觀察是什么效果。

      /proc是另一個我們經常使用的目錄。這個目錄完全是內核虛擬的。內核將一些系統信息都放在/proc目錄下一文件和文本的方式顯示出來,如:/proc/cpuinfo、/proc/meminfo。我們可以使用man 5 proc來查詢這個目錄下文件的作用。

      函數和遞歸

      我們已經接觸過函數的概念了,在bash編程中,函數無非是將一串命令起了個名字,后續想要調用這一串命令就可以直接寫函數的名字了。在語法上定義一個函數的方法是:

      name () compound-command [redirection]

      function name [()] compound-command [redirection]

      我們可以加function關鍵字顯式的定義一個函數,也可以不加。函數在定義的時候可以直接在后面加上重定向的處理。這里還需要特殊說明的是函數的參數處理和局部變量,請看下面腳本:

      [zorro@zorrozou-pc0 bash]$ cat function.sh |awk '{print "\t"$0}'

      #!/bin/bash

      aaa=1000

      arg_proc () {

      echo "Function begin:"

      local aaa=2000

      echo $1

      echo $2

      echo $3

      echo $*

      echo $@

      echo $aaa

      echo "Function end!"

      }

      echo "Script bugin:"

      echo $1

      echo $2

      echo $3

      echo $*

      echo $@

      echo $aaa

      arg_proc aaa bbb ccc ddd eee fff

      echo $1

      echo $2

      echo $3

      echo $*

      echo $@

      echo $aaa

      echo "Script end!"

      我們帶-x參數執行一下:

      + aaa=1000

      + echo 'Script bugin:'

      Script bugin:

      + echo 111

      111

      + echo 222

      222

      + echo 333

      333

      + echo 111 222 333 444 555

      111 222 333 444 555

      + echo 111 222 333 444 555

      111 222 333 444 555

      + echo 1000

      1000

      + arg_proc aaa bbb ccc ddd eee fff

      + echo 'Function begin:'

      Function begin:

      + local aaa=2000

      + echo aaa

      aaa

      + echo bbb

      bbb

      + echo ccc

      ccc

      + echo aaa bbb ccc ddd eee fff

      aaa bbb ccc ddd eee fff

      + echo aaa bbb ccc ddd eee fff

      aaa bbb ccc ddd eee fff

      + echo 2000

      2000

      + echo 'Function end!'

      Function end!

      + echo 111

      111

      + echo 222

      222

      + echo 333

      333

      + echo 111 222 333 444 555

      111 222 333 444 555

      + echo 111 222 333 444 555

      111 222 333 444 555

      + echo 1000

      1000

      + echo 'Script end!'

      Script end!

      觀察整個執行過程可以發現,函數的參數適用方法跟腳本一樣,都可以使用*、$@這些符號來處理。而且函數參數跟函數內部使用local定義的局部變量效果一樣,都是只在函數內部能看到。函數外部看不到函數里定義的局部變量,當函數內部的局部變量和外部的全局變量名字相同時,函數內只能取到局部變量的值。當函數內部沒有定義跟外部同名的局部變量的時候,函數內部也可以看到全局變量。

      bash編程支持遞歸調用函數,跟其他編程語言不同的地方是,bash還可以遞歸的調用自身,這在某些編程場景下非常有用。我們先來看一個遞歸的簡單例子:

      [zorro@zorrozou-pc0 bash]$ cat recurse.sh

      #!/bin/bash

      read_dir () {

      for i in $1/*

      do

      if [ -d $i ]

      then

      read_dir $i

      else

      echo $i

      fi

      done

      }

      read_dir $1

      這個腳本可以遍歷一個目錄下所有子目錄中的非目錄文件。關于遞歸,還有一個經典的例子,fork炸彈:

      .(){ .|.& };.

      這一堆符號看上去很令人費解,我們來解釋一下每個符號的含義:根據函數的定義語法,我們知道.(){}的意思是,定義一個函數名子叫“.”。雖然系統中又個內建命令也叫.,就是source命令,但是我們也知道,當函數和內建命令名字沖突的時候,bash首先會將名字當成是函數來解釋。在{}包含的函數體中,使用了一個管道連接了兩個點,這里的第一個.就是函數的遞歸調用,我們也知道了使用管道的時候會打開一個subshell的子進程,所以在這里面就遞歸的打開了子進程。{}后面的分號只表示函數定義完畢的結束符,在之后就是調用函數名執行的.,之后函數開始遞歸的打開自己,去產生子進程,直到系統崩潰為止。

      bash并發編程和flock

      在shell編程中,需要使用并發編程的場景并不多。我們倒是經常會想要某個腳本不要同時出現多次同時執行,比如放在crond中的某個周期任務,如果執行時間較長以至于下次再調度的時間間隔,那么上一個還沒執行完就可能又打開一個,這時我們會希望本次不用執行。本質上講,無論是只保證任何時候系統中只出現一個進程還是多個進程并發,我們需要對進程進行類似的控制。因為并發的時候也會有可能產生競爭條件,導致程序出問題。

      我們先來看如何寫一個并發的bash程序。在前文講到作業控制和wait命令使用的時候,我們就已經寫了一個簡單的并發程序了,我們這次讓它變得復雜一點。我們寫一個bash腳本,創建一個計數文件,并將里面的值寫為0。然后打開100個子進程,每個進程都去讀取這個計數文件的當前值,并加1寫回去。如果程序執行正確,最后里面的值應該是100,因為每個子進程都會累加一個1寫入文件,我們來試試:

      [zorro@zorrozou-pc0 bash]$ cat racing.sh

      #!/bin/bash

      countfile=/tmp/count

      if ! [ -f $countfile ]

      then

      echo 0 > $countfile

      fi

      do_count () {

      read count < $countfile

      echo $((++count)) > $countfile

      }

      for i in `seq 1 100`

      do

      do_count &

      done

      wait

      cat $countfile

      rm $countfile

      我們再來看看這個程序的執行結果:

      [zorro@zorrozou-pc0 bash]$ ./racing.sh

      26

      [zorro@zorrozou-pc0 bash]$ ./racing.sh

      13

      [zorro@zorrozou-pc0 bash]$ ./racing.sh

      34

      [zorro@zorrozou-pc0 bash]$ ./racing.sh

      25

      [zorro@zorrozou-pc0 bash]$ ./racing.sh

      45

      [zorro@zorrozou-pc0 bash]$ ./racing.sh

      5

      多次執行之后,每次得到的結果都不一樣,也沒有一次是正確的結果。這就是典型的競爭條件引起的問題。當多個進程并發的時候,如果使用的共享的資源,就有可能會造成這樣的問題。這里的競爭調教就是:當某一個進程讀出文件值為0,并加1,還沒寫回去的時候,如果有別的進程讀了文件,讀到的還是0。于是多個進程會寫1,以及其它的數字。解決共享文件的競爭問題的辦法是使用文件鎖。每個子進程在讀取文件之前先給文件加鎖,寫入之后解鎖,這樣臨界區代碼就可以互斥執行了:

      [zorro@zorrozou-pc0 bash]$ cat flock.sh

      #!/bin/bash

      countfile=/tmp/count

      if ! [ -f $countfile ]

      then

      echo 0 > $countfile

      fi

      do_count () {

      exec 3< $countfile

      #對三號描述符加互斥鎖

      flock -x 3

      read -u 3 count

      echo $((++count)) > $countfile

      #解鎖

      flock -u 3

      #關閉描述符也會解鎖

      exec 3>&-

      }

      for i in `seq 1 100`

      do

      do_count &

      done

      wait

      cat $countfile

      rm $countfile

      [zorro@zorrozou-pc0 bash]$ ./flock.sh

      100

      對臨界區代碼進行加鎖處理之后,程序執行結果正確了。仔細思考一下程序之后就會發現,這里所謂的臨界區代碼由加鎖前的并行,變成了加鎖后的串行。flock的默認行為是,如果文件之前沒被加鎖,則加鎖成功返回,如果已經有人持有鎖,則加鎖行為會阻塞,直到成功加鎖。所以,我們也可以利用互斥鎖的這個特征,讓bash腳本不會重復執行。

      [zorro@zorrozou-pc0 bash]$ cat repeat.sh

      #!/bin/bash

      exec 3> /tmp/.lock

      if ! flock -xn 3

      then

      echo "already running!"

      exit 1

      fi

      echo "running!"

      sleep 30

      echo "ending"

      flock -u 3

      exec 3>&-

      rm /tmp/.lock

      exit 0

      -n參數可以讓flock命令以非阻塞方式探測一個文件是否已經被加鎖,所以可以使用互斥鎖的特點保證腳本運行的唯一性。腳本退出的時候鎖會被釋放,所以這里可以不用顯式的使用flock解鎖。flock除了-u參數指定文件描述符鎖文件以外,還可以作為執行命令的前綴使用。這種方式非常適合直接在crond中方式所要執行的腳本重復執行。如:

      */1 * * * * /usr/bin/flock -xn /tmp/script.lock -c '/home/bash/script.sh'

      關于flock的其它參數,可以man flock找到說明。

      受限bash

      以受限模式執行bash程序,有時候是很有必要的。這種模式可以保護我們的很多系統環境不受bash程序的誤操作影響。啟動受限模式的bash的方法是使用-r參數,或者也可以rbash的進程名方式執行bash。受限模式的bash和正常bash時間的差別是:

      1、不能使用cd命令改變當前工作目錄。

      2、不能改變SHELL、PATH、ENV和BASH_ENV環境變量。

      3、不能調用含有/的命令路徑。

      4、不能使用.執行帶有/字符的命令路徑。

      5、不能使用hash命令的-p參數指定一個帶斜杠\的參數。

      6、不能在shell環境啟動的時候加載函數的定義。

      7、不能檢查SHELLOPTS變量的內容。

      8、不能使用>, >|, <>, >&, &>和 >>重定向操作符。

      9、不能使用exec命令使用一個新程序替換當前執行的bash進程。

      10、enable內建命令不能使用-f、-d參數。

      11、不可以使用enable命令打開或者關閉內建命令。

      12、command命令不可以使用-p參數。

      13、不能使用set +r或者set +o restricted命令關閉受限模式。

      測試一個簡單的受限模式:

      [zorro@zorrozou-pc0 bash]$ cat restricted.sh

      #!/bin/bash

      set -r

      cd /tmp

      [zorro@zorrozou-pc0 bash]$ ./restricted.sh

      ./restricted.sh: line 5: cd: restricted

      subshell

      我們前面接觸過subshell的概念,我們之前說的是,當一個命令放在()中的時候,bash會打開一個子進程去執行相關命令,這個子進程實際上是另一個bash環境,叫做subshell。當然包括放在()中執行的命令,bash會在以下情況下打開一個subshell執行命令:

      1、使用&作為命令結束提交了作業控制任務時。

      2、使用|連接的命令會在subshell中打開。

      3、使用()封裝的命令。

      4、使用coproc(bash 4.0版本之后支持)作為前綴執行的命令。

      5、要執行的文件不存在或者文件存在但不具備可執行權限的時候,這個執行過程會打開一個subshell執行。

      在subshell中,有些事情需要注意。subshell中的$$取到的仍然是父進程bash的pid,如果想要取到subshell的pid,可以使用BASHPID變量:

      [zorro@zorrozou-pc0 bash]$ echo $$ ;echo $BASHPID && (echo $$;echo $BASHPID)

      5484

      5484

      5484

      24584

      可以使用BASH_SUBSHELL變量的值來檢查當前環境是不是在subshell中,這個值在非subshell中是0;每進入一層subshell就加1。

      [zorro@zorrozou-pc0 bash]$ echo $BASH_SUBSHELL;(echo $BASH_SUBSHELL;(echo $BASH_SUBSHELL))

      0

      1

      2

      在subshell中做的任何操作都不會影響父進程的bash執行環境。subshell除了PID和trap相關設置外,其他的環境都跟父進程是一樣的。subshell的trap設置跟父進程剛啟動的時候還沒做trap設置之前一樣。

      協進程coprocess

      在bash 4.0版本之后,為我們提供了一個coproc關鍵字可以支持協進程。協進程提供了一種可以上bash移步執行另一個進程的工作模式,實際上跟作業控制類似。嚴格來說,bash的協進程就是使用作業控制作為實現手段來做的。它跟作業控制的區別僅僅在于,協進程的標準輸入和標準輸出都在調用協進程的bash中可以取到文件描述符,而作業控制進程的標準輸入和輸出都是直接指向終端的。我們來看看使用協進程的語法:

      coproc [NAME] command [redirections]

      使用coproc作為前綴,后面加執行的命令,可以將命令放到作業控制里執行。并且在bash中可以通過一些方法查看到協進程的pid和使用它的輸入和輸出。例子:

      zorro@zorrozou-pc0 bash]$ cat coproc.sh

      #!/bin/bash

      #例一:簡單命令使用

      #簡單命令使用不能通過NAME指定協進程的名字,此時進程的名字統一為:COPROC。

      coproc tail -3 /etc/passwd

      echo $COPROC_PID

      exec 0<&${COPROC[0]}-

      cat

      #例二:復雜命令使用

      #此時可以使用NAME參數指定協進程名稱,并根據名稱產生的相關變量獲得協進程pid和描述符。

      coproc _cat { tail -3 /etc/passwd; }

      echo $_cat_PID

      exec 0<&${_cat[0]}-

      cat

      #例三:更復雜的命令以及輸入輸出使用

      #協進程的標準輸入描述符為:NAME[1],標準輸出描述符為:NAME[0]。

      coproc print_username {

      while read string

      do

      [ "$string" = "END" ] && break

      echo $string | awk -F: '{print $1}'

      done

      }

      echo "aaa:bbb:ccc" 1>&${print_username[1]}

      echo ok

      read -u ${print_username[0]} username

      echo $username

      cat /etc/passwd >&${print_username[1]}

      echo END >&${print_username[1]}

      while read -u ${print_username[0]} username

      do

      echo $username

      done

      執行結果:

      [zorro@zorrozou-pc0 bash]$ ./coproc.sh

      31953

      jerry:x:1001:1001::/home/jerry:/bin/bash

      systemd-coredump:x:994:994:systemd Core Dumper:/:/sbin/nologin

      netdata:x:134:134::/var/cache/netdata:/bin/nologin

      31955

      jerry:x:1001:1001::/home/jerry:/bin/bash

      systemd-coredump:x:994:994:systemd Core Dumper:/:/sbin/nologin

      netdata:x:134:134::/var/cache/netdata:/bin/nologin

      ok

      aaa

      root

      bin

      daemon

      mail

      ftp

      http

      uuidd

      dbus

      nobody

      systemd-journal-gateway

      systemd-timesync

      systemd-network

      systemd-bus-proxy

      systemd-resolve

      systemd-journal-remote

      systemd-journal-upload

      polkitd

      avahi

      colord

      rtkit

      gdm

      usbmux

      git

      gnome-initial-setup

      zorro

      nvidia-persistenced

      ntp

      jerry

      systemd-coredump

      netdata

      最后

      本文主要介紹了一些bash編程的常用技巧,主要包括的知識點為:

      1、/dev/和/proc目錄的使用。

      2、函數和遞歸。

      3、并發編程和flock。

      4、受限bash。

      5、subshell。

      6、協進程。

      至此,我們的bash編程系列就算結束了。當然,shell其實到現在才剛剛開始。畢竟我們要真正實現有用的bash程序,還需要積累大量命令的使用。本文篇幅有限,就不探討外部命令的詳細使用方法和技巧了。希望這一系列內容對大家進一步深入了解bash編程有幫助。

      預約申請免費試聽課

      填寫下面表單即可預約申請免費試聽!怕錢不夠?可就業掙錢后再付學費! 怕學不會?助教全程陪讀,隨時解惑!擔心就業?一地學習,可全國推薦就業!

      上一篇:Linux 進程狀態淺析
      下一篇:編寫可靠shell 腳本的8個建議

      Linux文件擴展名

      Linux文件類型

      Linux新手要了解的十個知識點

      linux目錄結構的相關知識

      • 掃碼領取資料

        回復關鍵字:視頻資料

        免費領取 達內課程視頻學習資料

      • 視頻學習QQ群

        添加QQ群:1143617948

        免費領取達內課程視頻學習資料

      Copyright ? 2021 Tedu.cn All Rights Reserved 京ICP備08000853號-56 京公網安備 11010802029508號 達內時代科技集團有限公司 版權所有

      選擇城市和中心
      黑龍江省

      吉林省

      河北省

      湖南省

      貴州省

      云南省

      廣西省

      海南省

      高清特黄a大片,日本真人真做爰,特级做人爱C级,免费a级毛片 百度 好搜 搜狗
      <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <文本链> <文本链> <文本链> <文本链> <文本链> <文本链>