1. 程式人生 > >shell指令碼獲取當前指令碼所在位置

shell指令碼獲取當前指令碼所在位置

已開通新的部落格,後續文字都會發到新部落格
http://www.0xfree.top


我們在寫shell指令碼時,經常會遇到自己的一個指令碼需要呼叫到自己同目錄下另一個指令碼的情況,那麼如何能在指令碼中拿到我們指令碼所在的路徑呢?


update:20160127

BASH_SOURCE[0]這個變數只在bash下有效,所以從指令碼可移植性的角度來看,

  • 對於可以呼叫子shell執行的指令碼
    在指令碼開頭加入#!/bin/bash來指定直譯器,然後使用${BASH_SOURCE[0]}方式獲取指令碼所在路徑
  • 對於需要載入到當前shell環境執行的指令碼
    使用$0
    這種方式獲取指令碼所在路徑,這種方式不能適用於source執行的指令碼是link檔案

update: 20160126

發現readlink會將指定的檔案以絕對路徑格式輸出,那麼就不需要cd到當前目錄,然後pwd輸出絕對路徑了,因此精簡如下:
最終方案

SCRIPT=$(readlink -f "${BASH_SOURCE[0]}")
SCRIPTPATH=$(dirname $(SCRIPT))

當然,前提是readlink這個軟體的存在,目前在各個主流linux發行版中都有這個軟體,其他BSD發行版未測試
如果沒有這個軟體,我們也可以用如下方式來獲取,當然這種方式無法識別link檔案的真實路徑

SCRIPTPATH=$(cd $(dirname "${BASH_SOURCE[0]") && pwd )

原文:

具體的情況我們可以按照以下來細分:
注:假設/home/$USER/bin/test.sh為我們自己的指令碼

根據當前指令碼的檔案性質,可分為以下兩種情況:

  • link檔案:
    /usr/local/bin/test.sh -> /home/$USER/bin/test.sh

  • 普通可執行檔案:
    /home/$USER/bin/test.sh

根據指令碼執行的方式,又可分為以下兩種情況:

  • 子shell執行
    $ ./test.sh

    這種情況又可以根據呼叫的路徑分為以下兩種情況:

    • 絕對路徑呼叫:
      $ /home/$USER/bin/test.sh
    • 相對路徑呼叫:
      $ bin/test.sh
  • 父shell執行
    $ source test.sh

我們先來看第一種link檔案的情況:
linux有兩個軟體包realpathreadlink可以獲取到一個link檔案的真實路徑
使用方法:realpath $filepathreadlink -f $filepath
其中$filepath是指定的link檔案路徑,輸出為真實路徑

還有一種更直接的方式ls -ld $filepath,然後通過解析輸出的內容來獲取真實路徑

OK,處理完可能是link檔案這種情況之後,我們來看看對於普通檔案的那些情況:
我們在學習bash的過程中一定遇到過./test.shsource test.sh兩種執行指令碼的方式,這裡簡要說明一下:第一種方式bash將會開啟一個shell子程序來執行指令碼,第二種方式,會在當前shell程序內執行指令碼,兩種方式的區別之處在於shell子程序所做的操作包括變數的賦值不會影響到父程序(也就是當前程序),舉一個很簡單的例子:

### test.sh
cd /

如果你用./test.sh執行時,並不會跳轉到/目錄,但是如果你用source test.sh方式,則會執行cd /並跳轉

區分以上兩種情況是因為對於對bash比較熟悉的人來說,獲取當前指令碼所在的路徑的方式第一想到的就是使用引數$0這種方式,但是這種方式只適用於呼叫子shell執行指令碼的方式,如果你用source或者.執行,你就會得到$0的返回值為bash這個值,因此使用$0這種方式就不可取

注:如果你用zsh,你就會發現沒有這個問題,$0在任何情況下都可以得到當前指令碼的相對路徑

對於bash而言,還有一個內建的常量可以取得和子shell執行指令碼情況下與$0值一樣的效果,這個常量就是BASH_SOURCE,這是一個數組,用來儲存bash執行過程中的一些引數,其中BASH_SOURCE[0]所代表的就是當前指令碼以及它所在的相對路徑

注:這種方式有一個缺陷,就是隻是bash支援,對於指令碼的擴充套件性不是很好

OK,現在我們可以有效的拿到當前執行指令碼的相對路徑了,但是這個相對路徑還有一點需要說明,就是我在前邊區分的絕對路徑相對路徑的這兩種情況:
也就是呼叫指令碼方式的不同,絕對路徑與相對路徑大家都很應該瞭解,對於指令碼執行效果沒有什麼區別,但是對於獲取指令碼路徑來說,區別就不一樣了,可以使用以下指令碼驗證

### test.sh
echo "\${BASH_SOURCE[0] = ${BASH_SOURCE[0]}"

我們在bin目錄下直接執行./test.sh,我們可以得到以下輸出:

${BASH_SOURCE[0] = ./test.sh

如果在bin目錄下以絕度路徑執行/home/$USER/bin/test.sh,我們會獲得以下輸出:

${BASH_SOURCE[0] = /home/$USER/bin/test.sh

從以上實驗結果我們可以看出,如果我們在不同目錄下以相對路徑呼叫指令碼,那麼得出的${BASH_SOURCE[0]}就會不一樣,聰明的你或許會想到我們可以使用pwd或者$PWD來獲取當前的路徑,然後與${BASH_SOURCE[0]}獲取的路徑拼湊一下就可以得到,就像這樣$PWD/${BASH_SOURCE[0]},沒錯,這樣的方式確實能在這種情況下獲取,但如果我們用絕對路徑呼叫指令碼,你就會發現完全當前的路徑出現了兩遍
比如說,我們在bin目錄下使用絕對路徑呼叫test.sh~/bin $ /home/$USER/bin/test.sh 那麼$PWD/${BASH_SOURCE[0]}的輸出就是這樣的/home/$USER/bin/home/$USER/bin/test.sh,當然,如果你能保證你每次呼叫這個指令碼的時候都是使用相對路徑,那麼完全可以這樣,但是10天之後呢,20天之後甚至一個月之後呢,所以這種方式也不可取

綜合以上各種情況,我們

  1. 使用${BASH_SOURCE[0]}來避免source.執行方式的干擾
  2. 使用readlink -f $file來避免file是link檔案的干擾
  3. 對於絕對路徑與相對路徑的干擾,我們可以通過cd與pwd的方式巧妙獲取
    $(cd $(dirname ${BASH_SOURCE[0]}) && pwd)

最後我們得到了一個幾乎完美的獲取方式:

SCRIPTPATH=$(cd $(dirname $(readlink -f "${BASH_SOURCE[0]}")) && pwd )

進一步的思考:
對於指令碼中我們要引用到一些其他指令碼,
是用絕對路徑好呢?還是cd進去執行合適?
那麼我們是在當前路徑下執行好呢,還是cd到一個臨時目錄執行好呢?