2021-02-28

bash學習筆記

說在前頭

近期需要寫一些bash的腳本來進行一些批量的任務,但由于上次自己寫腳本還是在大概半年之前,因此有許多細節已經模糊不清了。其實這種現象之前也有過,比如在我學習使用正則表達過程中,學習過程的確不難,但是忘的也快,畢竟人腦不是電腦。如果不是經常使用的話的確是容易遺忘的(使用linux,以及寫bash腳本僅僅是個人的偶爾需求,并非工作或是相關領域的學生),因此想寫此筆記進行。對于bash的一些語法和命令進行精煉,加上自己的理解,整理成筆記。

我學習以及參考的主要書籍《Linux命令行與shell腳本編程大全》第三版Richard Blum Chrisrine Bresnahan著。如果作為第一次接觸bash的同學可以仔細閱讀相關書籍進行詳細的學習,而我的文章作為自己理解和精煉,并定有縮略甚至可能出現一些錯誤。望周知。

十一章 構建基本腳本

  1. 使用多個命令

    在bash中,如果想要在一行中使用多個命令。這個和我們熟悉的C語言是相似的,只要在各個不同命令之間使用;(分號)即可。例如

    cd /home/; mkdir Mydocument
    

  1. 創建腳本文件

    我們在創建腳本過程中第一行往往是一句較為特殊的一行代碼

    #!/bin/bash
    

    在bash中一般情況下#字符代表注釋,因此后面可以寫任何字符不影響腳本的運行,但是若是#!,則變成了特殊情況,這代表這個腳本使用/bin/bash這個程序運行。此時若要運行該腳本只需要如下命令即可.

    ./test.sh
    

    但是在我的計算機的bash中,不論是否有#!/bin/bash都可以使用上面的命令來運行。想來bash已經將其默認為一個可執行的ASCII文本默認為bash腳本語言。但是如果我們編寫其他語言的文件時一定要加上相應的解釋器,如:

    #!/bin/python3
    print ("Hello")
    

    上面創建一個名為hello.py的文件,如果你想要./hello.py這樣來執行文件,頭一行必須加上相應的解釋器。

    上述前提:不論是test.sh還是hello.py都具有可執行的權限,如果沒有需要使用chmod命令進行更改


  1. 顯示消息

    這個命令和C語言中的printf,和C++中的cout,和python中的print的最基本功能是相似的,簡而言之就是輸出一段文字,但要注意的是echo輸出的是一段標準輸出,標準輸出可作為一些參數輸入到其他命令當中

    echo Hello world!!
    

    其他更多使用方法可以查詢man手冊


  1. 使用變量

    bash腳本中的變量不像其他語言中一樣豐富,在我眼里,這其中只有一種字符串類型的變量,當然后續還有一些數字計算,以及數組等,但是數字計算需要使用expr命令或者是中括號,使用了“第三方”工具來進行計算,不夠原生。另外數組中的內容也盡是字符串類型數據,因此本質上來說都是字符串類型。

    • 環境變量

      如果對linux有些了解的話那環境變量一定不陌生。常用的環境變量名稱常用純大寫字母來表示,系統的正常運行依賴無數的環境變量,比如說最熟悉的PATH變量。

      其中PATH相當于變量名稱,而如果要查看變量內容

      echo ${PATH}
      輸出:
      /usr/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:
      

      這個過程我們是想要吧變量PATH的內容輸出到屏幕上以供我們人眼所看到,因此我們使用echo標準輸出命令,而如果我們。

       echo PATH
      

      系統會認為我們想要輸出的是PATH這四個字符,但是我們需要的是PATH變量的內容,因此需要特殊的符號來代表PATH變量的內容${PATH}

      如果我們思考為什么python或者是C中輸出變量內容為什么沒有需要特殊的符號來標記呢?這是因為C或python中有豐富的數據類型。

      int a = 3;
      cout << a;
      

      由于這個過程已經指定了a變量為int類型,計算機并不會認為你想要輸出一個a字符。總之C或者python中輸出字符串還是變量并不會產生歧義,但是在bash中單純地echo 變量名是會產生歧義的。

      最后說一點變量中的大括號是可以省略的,echo $PATH,也是能夠正確輸出變量內容的,不過要小心,這其中還是有些坑我們后面會說明。Anyway,${PATH}這種寫法準沒錯,不嫌棄麻煩還是這種寫法。

    • 用戶變量

      簡單來說用戶自己創建的臨時變量

      #!/bin/bash
      var=hello
      echo ${var}
      var="${var} World!!"
      echo ${var}
      echo "${var}"
      echo '${var}'
      輸出:
      hello
      hello World!!
      hello World!!
      ${var}
      

      在這里只是想要說明一些bash中的變量修改比較簡單粗暴,由于沒有數據類型的困擾,只需要在使用=來賦予或者添加變量內容。另外記住${變量名}代表的變量內容,不論是在雙引號里還是在方括號(方括號我們后續會使用到)里,唯一例外就是在單引號內,單引號像是照妖鏡,讓一切現真身!!

    • 變量替換

      非常有用 ,常用有兩種形式,反引號或者是$(命令),以date,顯示日期命令為例子。

      var1=$(date +%y%m%d)
      var2=`date +%y%m%d`
      echo ${var1}
      echo ${var2}
      輸出:
      210221
      210221
      

      兩種輸出無區別,僅僅是個人習慣問題。


  1. 重定向輸入和輸出

    • 輸出重定向

      輸出重定向有兩個符號>和>>。但箭頭>可以將標準輸出定向到一個文件中,并且覆蓋源文件內容。雙箭頭>>可以將標準輸出定向到一個文件中,不覆蓋源文件內容,或是說在源文件內容基礎上添加內容

      dmesg > test
      ls -l >> test
      date > test
      
    • 輸入重定向

      與輸出重定向相對應,輸出重定向是將命令的內容輸出到一個文件中,而輸入重定向一般是將文件的內容輸入到命令當中。

      比如一般情況下,我們查看該目錄下文件和文件夾的總數常用ls -l | wc -l,這其中將ls -l命令的輸出內容輸入到wc -l中來計數一共有多少行文字。但是我們也可以使用輸入重定向將一個文本內容輸入到wc -l中來計算一共有多少行文字

      ls -l > test
      wc -l < test
      

      上面輸出的結果與ls -l | wc -l完全一致

    • 管道命令

      管道命令也是使用的相當多的命令,命令形式是command1 | command2,管道命令就是中間的豎線,將命令1的標準輸出內容作為命令2的輸入內容。對于我來說最常用的形式便是查找文件,比如在一個目錄下我想要找一個文件,但是忘記了文件的具體名稱,只記得是一個rar壓縮包

      ls -l | grep -E rar$
      

  1. 執行數學運算

    在編寫腳本過程中我們難免需要一些數學運算,整數的加減法,浮點數或是說小數的加減法,書上總共列了幾種方法,但我認為最后一種方法已經涵蓋了之前的數學運算的所有能力,因此我只記住,也一直在用的是書中提到的最后一種方法。

    使用bc,linux的內置計算器,對于浮點數來說主要記住scale=n,保留n位小數的選項,比如

    echo "scale=5; 3.14 / 2.22" | bc
    輸出:
    1.41441
    

    如果沒有scale,則計算默認保留整數。

    當然比較大小也可以計算

    echo "3.14 <= 2.22" | bc
    輸出:
    0
    

  1. 退出腳本

    這里介紹的這個應該屬于地一個特殊變量$?,其中的美元號和一般變量中是同一個${變量名},只是變量名變成特殊的?。該命令表示上一個命令結束狀態。相應的編號如下。

    asda
    輸出:
    bash: asda:未找到命令
    echo $?
    輸出:
    127  
    

    [圖片上傳失敗...(image-cf107e-1614521091753)]

    在書寫腳本過程中我們可以自己制定相應的推出狀態碼,使用exit,比如想要另腳本退出的狀態碼為上圖中沒有的數字32,可以在腳本最后添加exit 32命令即可。


十二章 使用結構化命令

  1. if 判斷語句

    • if-then語句

      if commands
      then
         commands
      fi
      

      這個是判斷語句最基本的用法,then的位置無關緊要,也可以寫成

      if commands then
         commands
      fi
      

      這就是腳本中的判斷語句,但是需要注意的是if后面的內容是一個命令,<u>判斷的結果依照該命令的退出數值而決定</u>,如果退出狀態為0,則相當與判斷正確,執行嵌套在if語句內的命令。通過上面的說明可以看出如果有下面的語句

      if 0 
      then
         echo "Hello"
      fi
      

      這條語句是錯誤的,因為“0”并非是一條命令,因此該語句無法執行,但是不論在C還是python中

      if (1)
          cout << "Hello"
      

      這種語句是可以執行的,歸結原因是一般語言中if后面判斷的是數值,而bash腳本中if判斷的是后面命令的退出狀態的狀態碼。

    • if-then-else語句

      理解了if-then語句,那么這條語句就沒有什么好解釋的了

      if command
      then
         commands
      else
         commands
      fi
      
    • 嵌套if語句

      if command1
      then
         commands
      elif command2
      then
         commands
      fi
      

  1. test命令

    test命令的作用就是將想要進行判斷的東西轉換成一個command以供作為if后面的判斷命令,通常用法如下

    if test -n str1
    then
      echo "Hello"
    fi
    

    該代碼塊用于判斷str1是否為空字符,若是,則test -n str1的退出狀態碼為0,否則為1。現在對于我來說test很少使用,但是需要知道,為了代碼美觀便于閱讀,常常用方括號來代替test命令

    if [ -n str1 ] 
    then
      echo "Hello"
    fi
    

    需要注意的是想對應的寫法[空格command空格],形象地說命令和方括號之間不能夠挨著,否則語法錯誤。這種方括號的寫法使用時間長了容易造成誤解,C語言中括號往往代表這整體,但是這里的方括號代表這一個語句,你可以在你的bash中單獨輸入[ -n "Hello" ]或者[ -n "" ],絕對不會報錯,因為行面兩個就是標準的命令,接下來可以使用echo $?,來查看代碼的推出狀態。

    下面是bash中一些常用比較命令的條件參數,需要使用的時候可以查詢


  1. 復合條件測試

    表達式

    [ condition1 ] && [ condition2 ]
    [ condition1 ] || [ condition2 ]
    

    前者是與,后者是或。


  2. if-then的高級特性

    if后面的判斷之前我們一直使用[ expression ],更本質的說應該是使用test expression,來進行判斷。高級特性中會使用((expression))[[expression]],兩種類型。

    雙圓括號((expression)),擴展了數學運算,加入了自加,自減,乘方等高級的數學運算

    if ((${var} ** 2 < 90))
    then
     commands
    fi
    

    上面代表如果變量var的二次方小于90,則執行if內的命令,否則跳過。

雙方括號提供模式匹配,不過我一般都使用正則表達,幾乎沒有使用過雙方括號,需要可以自己查閱書籍257頁。


  1. case命令

    case命令應該是類似與C語言中的switch命令,用于解決多個if ... elif ... elif這種情況,但是不論是C還是bash中我基本沒有用過,我認為知道即可

    case var in 
    parttern1 | parttern2) commands1;;
    parttern3) commands2;;
    *) default commands3;;
    esac
    

    上面的語句的意思是當變量var與模式parttern1或者模式parttern2匹配時候,執行commands1,當與parttern3匹配時候執行commands2,其他情況執行commands3


十三章 更多結構化命令

  1. for命令

    • 如果會C或python,對于for命令應該相當熟悉

      for i in list
      do
         commands
      done
      

      不看do和done的話這個命令和python中的for用法很相似。

      for命令中的重點就在于命令中的list,在python中存在數組數據類型list。雖然bash中也存在數組,但是和python中的數組并不完全相同,后續會有相關內容,不過這里的list不是bash中的數組,而是一串使用空格作為分割的字符

      for i in one two three four five
      do
         echo ${i}
      done
      輸出:
      one
      two
      three
      four
      five
      

      反正我剛剛學到這里的時候感覺這里處理的好簡陋,不過bash就是這樣處理list的,list中兩個量之間使用空格作為分隔,并且看起來這個list沒有所謂的邊界,給人很不習慣的感覺。python和C中list類型的數據都使用方括號作為邊界進行分割的。

      另外如果我想要的一個元素中間帶有空格,我們需要使用雙引號來告訴計算機這是一個整體

      for i in one "two three" four five
      do
         echo ${i}
      done
      輸出:
      one
      two three
      four
      five
      

      記住雙引號可以告訴計算機那些是一個整體。關于雙引號的問題,其實在if篇章那里我就曾經遇到過,我記得當時發了一個相關問題的帖子在貼吧里面

      https://tieba.baidu.com/p/6602217756

      這里面我覺得5樓還是給了我一些提示,當變量中沒有空格的時候$var"$var"無差別,當存在空格的時候差別就會顯現出來,不過具體有什么差別我現在還弄不清本質的原因,不過如果你想對一個變量var進行操作,不論是判斷還是其他的,如果不嫌棄麻煩"${var}",這樣不論變量中是有空格還是有回車,準沒錯。所以我后來在寫腳本的時候為了避免不必要的報錯或者命令的錯誤運行,我都是寫成做后一種。

      此外,實際中我們較為常用的是將命令的結果作為list進行循環

      for i in `seq 10`
      do
         echo $i
      done
      

      這里的seq 10是bash中的命令,自動生成從1到10的數字,在本命令塊中相當于從1到10進行輸出,當然也可當作計數器來指定循環次數。例如

      for i in `seq 100`
      do 
         echo $RANDOM % 7 | bc
      done
      

      這里的作用是輸出100個0到6的隨機數字,至于這些隨機數字用來干什么,不論是抽簽還是模特卡羅計算都是隨你心意。


    • 更改字段分隔符

      bash中默認情況下是按照

      • 空格
      • 制表符
      • 換行符

      這幾個符號來將字段進行分割的,如果我想要一句一句地輸出一個英文詩歌,沒一句詩詞為一行,但是每一句中又有多個相互由空格分割的單詞構成,按照默認的方式輸出便會輸出每一個單詞。因此,我們可以將分隔符i變量進行更改

      IFS=$'\n'
      

      這樣分隔符變量IFS就只認換行符。或是可以自行添加其他符號作為分隔符內

      IFS=$'\n':;"
      

      上面這句就是將換行,冒號,分號,雙引號作為分隔符。

      另外需要注意有認為有以下幾點

      • 首先更改IFS變量的語法有些奇怪,比如將換行賦予IFS變量IFS=$'\n'是這個樣子的

      • 其次如果我們更改了IFS變量如何查看?如果按照普通查看系統變量的方法echo,你應該什么也看不到,因為換行,空格,還是制表符,你看到的都是nothing。在網上來看一般查看IFS變量內容的方法是查看其16進制內容echo $IFS | hexdump -C,或者echo $IFS | hexdump,即使這樣也不是很直觀。

      • 從以上兩點中可以看出,如果非必要最好不要更改次變量,不方便改,也不方便查看。當然如果是不改不行的情況下,一定要記住不要忘記更改回來,因為這個是系統變量,好多腳本都是用到這個變量的,如果不改回來會出現奇怪的錯誤。書中建議。

        IFS.OLD=$IFS
        IFS=$'\n'
        <在代碼中使用新的IFS值>
        IFS=$IFS.OLD
        
    • 用通配符讀取目錄內容

      在一般情況下,如果想要讀取該目錄下的內容,我習慣

      for i in `ls`
      do
         echo $i
      done
      

      但是這里的命令僅僅適用于目錄下的文件名稱均沒有空格的情況,所以,更加一般的讀取目錄的方法便是使用通配符來讀取

      for i in $HOME/*
      do
         echo $i
      done
      

  1. C語言風格的for命令

    之前的是pyhon風格的for命令,而bash中也有C風格的for命令,不過需要使用到雙圓括號((expression)),在之前if章節中我們學到雙圓括號能夠擴展數學運算,包括乘方,自加,自減等運算,C風格的for命令中主要就包含自加或者自減運算。

    for ((a=1;a<10;a++))
    do
     echo $a 
    done  
    

    當然,不論是python風格的還是C風格的都能夠達到同樣的效果。

  2. while命令

    while test command
    do
         other commands
      done
    

    test command結果是0,繼續循環,否則退出循環。這其中的test可以換成中括號,或是雙括號,甚至命令,比如正則表達。因該在使用正則表達時候ls -l | grep zip,當匹配到相應內容,可以嘗試使用echo $?來查看退出狀態碼是0,正好可以進入循環對相應的文件進行操作。


  3. until命令

    until test commands
    do
       other commands
    done
    

    until命令是當test commands的退出狀態為1時進行循環,直到其變為0,則停止循環。

    至于使用for,還是while,還是until。全憑借自己寫代碼的思路順暢程度,總的來說三種循環之間是可以相互轉化的。我比較習慣使用for或者while,until命令很少使用。


  4. 控制循環

    控制循環主要是兩個命令breakcontinue

    如果對于C熟悉的話對于break一定不陌生,break可以從循環中跳出來,不過bash中break可以跳出多層循環,例如break 2便可以跳出層循環

    for i in list1
    do
       for j in list2
       do
           if command
           then
               break 2
           fi
       done
    done
    

    上面的代碼塊便可以跳到for循環的最外面。

    continue命令用于跳過其下面的語句直接執行下一次循環,對于C熟悉的話不需要我多解釋。


十四章 處理用戶輸入

  1. 命令行參數與特殊參數變量

    • 讀取腳本以及腳本名稱

      該腳本名稱為hello.sh

      #/bin/bash
      echo $0
      echo $1
      echo $2
      echo $3
      

      執行上面新建的腳本如下

      ./hello.sh you are a chinese
      輸出:
      ./hello.sh
      you
      are
      a
      

      從上面代碼執行情況可以看出${數字}是一種特殊的變量,當然數字從必須大于等于0。改變量可以從腳本外面讀取參數,注意當數字大于9的時候必須要加上大括號,而數字為個位數字的時候可加可不加。一般來說$0變量顯示的是腳本的名還帶有路徑,這樣看起來并不是很好看,特別是當帶有絕對路徑的時候長長的一大串并不適合進行閱讀,因此有一個basename的工具可以去掉文件名中的路徑。

      #/bin/bash
      echo `basename $0`
      echo $1
      echo $2
      echo $3
      

      執行命令

      ./hello.sh you are a chinese
      輸出:
      hello.sh
      you
      are
      a
      

      這個basename命令一般我是在使用通配符遍歷文件過程中使用,可以有效去除文件名稱中的路徑。

    • 參數統計以及抓取所有參數

      特殊變量$#,在bash腳本中其含義是所有外界參數的個數,像是上面所執行的命令./hello.sh you are a chinese,如果在該腳本中加入$#變量,該變量的數值就會變成4,注意$#變量中不包含腳本名稱,或是說不包含$0,因此,我們就可以在腳本開頭添加if [ $# -eq n ]這種語句來判斷是否參數個數正確。因為如果腳本中使用到參數$n,而恰巧使用腳本過程中添加的參數個數沒有達到n,則腳本會出現錯誤。

      #/bin/bash
      echo `basename $0`
      echo $1
      echo $2
      echo $3
      echo $#
      

      執行

      ./hello.sh you are a chinese
      輸出:
      hello.sh
      you
      are
      a
      4
      

      另外還有兩個特殊的變量分別是$*$@

      $*將所有參數當作一個整體來進行保存

      $@將所有參數當作一個list來進行保存

      還是從前面的命令來看./hello.sh you are a chinese。對于這個腳本,$*變量的內容是"you are a chinese"作為對比,$@變量所代表的內容是you are a chinese。區別就在是否有雙引號,或是說是否看作整體。前者用for循環不出單詞的,但是后者可以。

    • 總結

      到目前為止,特殊變量基本全部論述完成,所以在這里總結一些

      $?             #上一條命令的退出狀態碼
      ${數字}          #腳本的參數變量
      $#             #腳本的參數總個數
      $*             #腳本的所有參數(作為整體)
      $@             #腳本的左右參數(作為list)
      

  2. 獲取用戶輸入內容

    相當于C中的scanf命令,C++中的cin >>命令,python中的input命令,bash中使用read命令來讀取用戶輸入內容。最基本的用法如下

    read 變量名
    

    將用戶輸入的內容付給變量,需要注意的是如果不給出需要賦予的變量的名稱,則系統默認將read來的變量賦予給系統變量REPLY

    更多的用法可以執行read --help來進行查看

    read: read [-ers] [-a 數組] [-d 分隔符] [-i 緩沖區文字] [-n 讀取字符數] [-N 讀取字符數] [-p 提示符] [-t 超時] [-u 文件描述符] [名稱 ...]
        從標準輸入讀取一行并將其分為不同的域。
        
        從標準輸入讀取單獨的一行,或者如果使用了 -u 選項,從文件描述符 FD 中讀取。
        該行被分割成域,如同詞語分割一樣,并且第一個詞被賦值給第一個 NAME 變量,第二
        個詞被賦值給第二個 NAME 變量,如此繼續,直到剩下所有的詞被賦值給最后一個 NAME
        變量。只有 $IFS 變量中的字符被認作是詞語分隔符。
        
        如果沒有提供 NAME 變量,則讀取的行被存放在 REPLY 變量中。
        
        選項:
          -a array   將詞語賦值給 ARRAY 數組變量的序列下標成員,從零開始
          -d delim   持續讀取直到讀入 DELIM 變量中的第一個字符,而不是換行符
          -e 使用 Readline 獲取行
          -i text    使用 TEXT 文本作為 Readline 的初始文字
          -n nchars  讀取 nchars 個字符之后返回,而不是等到讀取換行符。
             但是分隔符仍然有效,如果遇到分隔符之前讀取了不足 nchars 個字符。
          -N nchars  在準確讀取了 nchars 個字符之后返回,除非遇到文件結束符或者讀超時,
             任何的分隔符都被忽略
          -p prompt  在嘗試讀取之前輸出 PROMPT 提示符并且不帶
             換行符
          -r 不允許反斜杠轉義任何字符
          -s 不回顯終端的任何輸入
          -t timeout 如果在 TIMEOUT 秒內沒有讀取一個完整的行則超時并且返回失敗。
             TMOUT 變量的值是默認的超時時間。TIMEOUT 可以是小數。
             如果 TIMEOUT 是 0,那么僅當在指定的文件描述符上輸入有效的時候,
             read 才返回成功;否則它將立刻返回而不嘗試讀取任何數據。
             如果超過了超時時間,則返回狀態碼大于 128
          -u fd  從文件描述符 FD 中讀取,而不是標準輸入
        
        退出狀態:
        返回碼為零,除非遇到了文件結束符、讀超時(且返回碼不大于128)、
        出現了變量賦值錯誤或者無效的文件描述符作為參數傳遞給了 -u 選項。
    

    其中較為常用的是-p-t參數。

    更多關于參數的高級用法我不常用,因此本章沒有說明,詳細的可以閱讀書籍


十五章 呈現數據

  1. 理解輸出和輸入

    文件描述符 縮寫 描述
    0 STDIN 標準輸入
    1 STDOUT 標準輸出
    2 STDERR 標準錯誤

    首先我們可以來直觀地理解上述這些輸入和輸出。對于標準輸入,可以簡單地理解為計算機從外界獲取到的輸入信息,至于我們的電腦,那基本就是鍵盤和鼠標對電腦輸入的信息,標準輸入我們可以<來進行重定向。所謂重定向,就是更改原本默認的信息流向,比如標準輸入正常情況下我們只能從鍵盤或是鼠標中進行輸入,但是通過重定向,我們可以將文件中的內容作為輸入內容。

    同樣的標準輸出,也是同理的,正常情況下標準輸出的內容會顯示在顯示器上以供人類進行查看,但是我們也可以使用>將標準輸出重定向到一個文件中。

    ls -l > text
    wc -l < text
    

    上面的命令就是將該目錄下的文件重定向到一個text文本中,然后利用輸入重定向將text文本中的內容輸入到wc -l,作為其參數,計算一共有多少行文本,在這里其意義就是有多少個文件。看到這里其實可以想到之前的一個命令。

    ls -l | wc -l
    

    從某種角度上來講,管道命令也是一種特殊的重定向。

    最后一種就是標準錯誤,比如說輸入一個錯誤命令然后利用>符號進行重定向到一個文本中,你會發現依舊有一條錯誤信息顯示在屏幕上,并且打開剛才定向的文本,會發現里面是nothing。這是因為這種錯誤提示信息是標準錯誤,而> 符號是用于重定向標準輸出的,如果想要重定向標準錯誤因該使用下面的命令。

    nihao 2> text
    

    這樣打開text文本會發現里面有bash: nihao:未找到命令這樣的文字。

    由于標準錯誤的重定向符號不是>而是2>,因此很自然地可以想到,標準輸入和標準輸出的重定向符號是簡化的,完整的標準輸入和輸出的重定向符號應該是0<1>。這個在后面的創建重定向的時候非常有用。

    重定向的好處在于可以把系統顯示在屏幕上面的東西保存在一個文件中從而可以隨時查看以及翻閱,比如在我編譯一個程序的時候,假設時長在40分鐘,我不可能,也不愿意一直盯著屏幕來看,因此我就可以這樣做

    編譯程序命令 > stdout 2> stderr
    

    這樣在這個編譯程序的過程中我就可以去做其他事情,并且沒有一絲絲的擔心,因為如果編譯成功,當然是好事,但是如果編譯錯誤,我也可以查看兩個文本stdoutstderr 來仔細查看到底哪里出了問題。當然實際上我會使用

    編譯程序命令 | tee file 1>&2
    

    相關后續會進行解釋


  2. 腳本中的重定向輸出

    前面主要介紹了什么是輸入和輸出,以及簡單地介紹了一下一般常用的重定向,將標準輸入輸出定向到文本文件中。這里將要講述更一般的重定向。

    文件描述符號0,1,2,這三個已經是系統默認的文件描述符號,描述相應的標準錯誤,標準輸出以及標準輸入。自然會想到其他數字呢,其他數字是空閑的,這樣我們就可以自己創建自己的重定向,在創建重定向之前需要了解臨時重定向以及永久重定向,臨時重定向就是該重定向指令之負責想對應的語句,比如下面的腳本

    #!/bin/bash
    echo "Hello" > &2
    echo "World"
    

    這里要簡要說明一下echo "Hello" > &2這句,我們正常的重定向到文件是echo "Hello" > file這樣的。但是這句中并不是將作為標準輸出的Hello單詞重定向到普通文件,而是將其重定向到特殊的文件描述附——標準錯誤2,在重定向中想要定向的文件描述符需要在相應的數字前面加上&,這不禁讓我想到C語言中的地址描述,從某種角度來講這本質上也是一種地址。在執行這個腳本后表面上看不出什么,但是如果將輸出內容重定向一下

    ./Hello.sh > stdout 2> stderr
    

    就會發現stdout文件中只有world單詞,而stderr中只有Hello單詞。

    以上便是臨時重定向,重定向的語句只負責相應的句子。而永久沖定向則顧名思義,其重定向的作用是永久的,當然這個永久的對象是想對于腳本來說的,當腳本完成退出后,該重定向不再起作用

    #!/bin/bash
    exec 1 > &2
    echo "Hello" 
    echo "World"
    

    執行./Hello.sh > stdout 2> stderr后會發現stdout文本中無內容,stderr中存在Hello和World兩個單詞。這邊是永久重定向

    還有一個例子就是重定向輸入的

    $ cat test12
    #!/bin/bash
    # redirecting file input
    exec 0< testfile
    count=1
    while read line
    do
    echo "Line #$count: $line"
    count=$[ $count + 1 ]
    done
    $ ./test12
    Line #1: This is the first line.
    Line #2: This is the second line.
    Line #3: This is the third line.
    $
    

    這個書中的例子讓我聯想到C和python中的文件讀取f.open()f.read(),f.close()等過程,可以說bash腳本中的重定向對標這一般編程語言的文件讀取,寫入過程。


  3. 創建自己的重定向

    這里的內容我較少使用,主要因為3個系統重定向文件對于我來說基本足夠了,但是這里還是要說一下加深對于重定向的理解。

    exec 3>file
    echo "Hello" > &3
    

    這個便是創建一個自定義的重定向3,并且將其定向到一個文件file中,當我執行腳本過程中便有三個類型的數據流在輸出,標準輸出,將數據輸出到屏幕或者其他位置。標準錯誤,將數據輸出到屏幕或者其他位置。自定義輸出3,將數據輸出到文件file。

    最后由于exce 3>file在整個腳本中都其作用,如果在腳本一半之后想要關閉該文件描述,則可以執行下面的語句

    exce 3>&-
    

  4. 總結

    最后通過上面的論述對于重定向也有了一定的理解,最后想說明解釋一下重定向在我日常中使用最廣泛的情況,當我需要將一段指令的輸出(標準輸出以及標準錯誤)定向到一個文本中的時候,我一般會使用下面的命令

    command > file 2>&1
    

    首先命令的標準輸出定向到file文件中,也就是說只要是標準輸出,都會在file文件中記錄,那么標準錯誤怎么辦呢?之前我們曾經將標準錯誤和標準輸出分到兩個文件中進行記錄

    command > file 2> error
    

    從上面的代碼可以看到當指令輸出標準錯誤后,數據流會向后面流動,直到數據流遇到一個可以安排自己位置地方,如果到指令末尾也沒有安家之處,則標準錯誤會默認輸出到顯示屏幕上面。

    command > file 2>&1在這一條指令中,標準錯誤數據流會沿著指令向后走動,直到遇到了2>&1這條指令,標準錯誤才知道,哦原來我是被安排到標準輸出去了,因此標準錯誤數據流便搖身一變,重定向為標準輸出。而標準輸出又該到哪里呢?標準輸出能夠輸出到顯示屏幕中嗎?當然不能,因為在前面已經指定了標準輸出的數據流需要重定向到file文件中,因此原本的標準錯誤數據又流回到了file文件中。經過上面的這一圈下來,標準輸出和標準錯誤的數據都進到了file文件中,并且順序完全一樣。


十六章 控制腳本

這里我常用的內容主要是作業控制,并且這些內容和腳本的關系并不是很密切,或是說linux下的幾乎所有程序都可以利用這章節的相關內容來進行控制。另外本章的內容需要程序運行一段時間才能看到效果,因此編寫了下面的腳本以供測試

#!/bin/bash
for i in `seq 10`
do
    sleep 1
    echo $i                 #輸出到終端
    #echo $i >> sleep       #輸出到文本
done
  1. 處理信號

    關于linux的處理信號,如果長期使用linux的用戶我相信并不陌生,比如在使用tar或者7z命令對一個文件進行壓縮或是解壓,當由于某些原因需要暫停相應的進程,記住是暫停不是停止。可以使用組合按鍵Ctrl-Z。當想要繼續進行未完成的壓縮文件的進程時可以使用fg命令來繼續進行。

    常用的關于信號處理如下

    信號 描述
    1 SIGHUP 掛起進程
    2 SIGINT 終止進程
    3 SIGQUIT 停止進程
    9 SIGKILL 無條件終止進程
    15 SIGTERM 盡可能終止進程
    17 SIGSTOP 無條件停止進程,但不是終止進程
    18 SIGTSTP 停止或暫停進程,但不終止進程
    19 SIGCONT 繼續運行停止的進程

    因此,我們在使用linux中常用的比如Ctrl-Z是信號1,當想要強制停止某一個程序的時候Ctrl-C是信號2。再比如當某一個程序卡住。或者程序崩潰的時候我們可以使用kill -9 PID,來結束進程,也就是上面的SIGKILL信號。


  2. 后臺模式運行腳本

    后臺運行腳本或者程序,可以在程序或者腳本的后面加上&,比如

    sleep 10 &
    

    我們可以通過ps命令或者jobs命令來查看在后臺運行的命令狀態,是運行中,還是已完成。

    另外需要說明的一點是在后臺運行過程中,如果我們將終端關閉,則該終端下的后臺運行程序也會關閉,有另一種后臺運行方法可以消除終端和相應的后臺程序的關聯,及可以任意關閉終端,但是相應的后臺運行程序依舊運行

    nohub ./test.sh &
    

    由于該命令將終端與后臺運行程序的關聯切斷,因此作為程序test.sh的標準輸出內容就無法打印到顯示器(終端)上,nohup命令會自動將 STDOUT 和 STDERR 的消息重定向到一個名為nohup.out的文件中。


  3. 作業控制

    如果我們將一個進程掛到后臺,可以使用jobs或者jobs -l命令進行查看。

    [1]- 1950 Running             ./test10.sh > test10a.out &
    [2]+ 1952 Running             ./test10.sh > test10b.out &
    

    其中的1和2可以看作是一種編號,但我們想要重啟一個已經暫停的作業時,我們便可以使用這個作業編號來指定相應的進程,其中常用的重啟作業的命令有fgbg。簡單來說fg是將暫停的作業以前臺模式重啟,而bg是將暫停的作業以后臺模式重啟。

    fg 2
    

十七章 創建函數

不論是C還是python中都會有函數這個東西,bash腳本中也不例外,函數在bash腳本中的目的就是減少冗余重復的代碼塊,將一個功能封裝到一個函數中方便調用。

  1. 基本的腳本函數以及函數的返回值

    在bash腳本中基本的函數形式是

    function name {
     commands
    }
    

    或者

    name() {
     commands
    }
    

    這兩種創建函數的形式是等價的,依照個人習慣進行選擇。

    在bash腳本中,創建一個函數相當與在腳本中創建一個子腳本,對于腳本一定有一個退出狀態碼,運行成功即為0,否測出現意外情況則為其他數值。我們之前說過可以使用echo $?這個特殊變量進行查看。在函數中,這個退出狀態碼利用return來表示。比如在一個腳本中test.sh

    #!/bin/bash
    name(){
     commands
     return 3
    }
    echo $?
    

    上面便是腳本的內容,我們可以執行該腳本./test.sh,會發現該腳本輸出的數值為3,也就是腳本中的這個echo $?命令給出的結果。

    這個和C語言中的函數返回數值return有些區別,這里的函數返回的是退出狀態碼,而C中的return是將函數的結果輸出到函數外面。bash中的return限制比較多,return返回數值只能是0~255的整數,當然退出狀態碼的取值范圍就是這個。所以一般不使用return來傳遞出函數內計算的結果,一般使用下面的方法

    #!/bin/bash
    name(){
     commands
     echo "result"
     return 0
    }
    something=`name`
    

    像是這樣,就能夠把函數的計算結果賦給something這個變量。所以如果想要傳遞出函數的計算結果,一般使用echo


  2. 函數中的變量

    在C語言中如果想要向函數中輸入變量,一般如下

    int fun(int x)
    {
        int y;
        y=x;
        return y;
    }
    

    而在bash腳本中有所不同,由于腳本中的函數可以看作一個子腳本,因此自然可以想到在向腳本中傳入參數時候我們使用$1,$2等特殊參數。

    在此,我們可以利用bash腳本寫出和上述C形式函數的等價函數

    #!/bin/bash
    fun (){
     result=$1
     echo ${result}
    }
    echo `fun 12`
    

    從上面可以看出腳本中函數如果想要使用函數外部(腳本內部)給的參數,需要借用$數字,這個特殊參數來傳遞變量。

    提到函數就不得不提到函數變量的作用域,這個在這里不詳細說明,如果熟悉C或者C++的話一定相當熟悉,在這里只是簡單說明一下在函數中定義局部變量使用local value=3這種形式,關鍵字是local

    #!/bin/bash
    value=5
    fun (){
     local value=3
     echo ${value}
    }
    echo `fun`
    echo ${value}
    

  3. 數組變量和函數

    最后要說一下bash語言中的最后一種變量(或是第二種,我個人理解依舊是字符串類型變量)數組變量。數組變量的形式是由圓括號括起來的之間用空格想分隔的一串字符串,形式如下

    m=("hello" 2 "你好" 226 "my car")
    

    由于是字符串數據類型,當然和list的數據類型有所區別,不可以直接for in來進行循環。需要使用序號來引用

    echo ${m[0]}
    echo ${m[1]}
    echo $m[1]
    echo ${m[4]}
    echo $m
    echo ${m[*]}
    輸出:
    hello
    你好
    hello[1]
    my car
    hello
    hello 2 你好 226 my car
    

    從上面可以看出以下幾點

    • 大括號{}是必須存在的,因為數組序號使用了中括號,如果不加上大括號將變量名的范圍擴起來會產生歧義,認為中括號只是一個后加上去的字符串而已,這樣中括號就起不到指示數組變量位置的作用
    • 數組的序號是從0開始,這和其他語言都相同
    • 想要輸出整個數組echo $m這種形式是不行的,這種形式只能輸出數組的地一個變量,想要輸出整個數組需要這樣echo ${m[*]}其中*可以看作是起到了通配符的作用。

  4. 總結

    創建函數應該是我寫的bash腳本中的最后一章了,不能說其他的沒有用,只是對于我日常處理文件來說用不到而已。像是關于使用bash腳本寫“圖形UI”的章節我認為也很有趣,當然這里的UI需要打引號是因為bash寫出的UI和印象中的UI不太一樣,就有藍色的提示框,并且提示框后面還有陰影,不過這種UI還是無法與鼠標交互。我寫的一些處理文件的腳本一般來說只有我自己使用,當然UI是用不到的了。

    再有就是一些更加高級的,我現在水平還不夠寫出總結的一些東西,比如正則表達,這個我還是較為常用的,正則表達較為簡單,不過學的快,忘記的也快。還有就是sed和gawk,這兩個我現在依舊是需要的時候現用現查,這些如果有需要可以好好學習一下。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,156評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,401評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,069評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,873評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,635評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,128評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,203評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,365評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,881評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,733評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,935評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,475評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,172評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,582評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,821評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,595評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,908評論 2 372

推薦閱讀更多精彩內容