1. 程式人生 > >常見 Bash 內置變量介紹

常見 Bash 內置變量介紹

dock 換行符 編程 index 主機名稱 pro 常用 sep ear

目錄

$0
$1, $2 等等
$#
$* 與 "$*"
$@ 與 "$@"
$!
$_
$$
$PPID
$?
$BASH
$BASH_VERSION
$EUID 與 $UID
$GROUPS
$HOME
$HOSTNAME
$IFS
$PATH
$OLDPWD
$PWD
$PS1
$PS2
$PS4

$0

執行 Bash 腳本時,Bash 會自動將腳本的名稱保存在內置變量 $0 中。因為 $0 基於的是實際的腳本文件名稱,而不是在腳本中進行硬編碼,所以在重命名腳本文件的名稱後,不需要修改腳本的內容。比如下面的腳本片段:

#!/bin/bash

ARGS=3 # 這個腳本需要 3 個參數.
E_BADARGS=65 # 傳遞給腳本的參數個數不對.
echo "Args number is : $#" echo $0 if [ $# -ne "$ARGS" ] # 測試腳本的參數個數。 then echo "Usage: $(basename $0) first-parameter second-parameter third-parameter" exit $E_BADARGS fi # 開始幹正事兒

在上面的代碼中我們使用了 $(basename $0) 的寫法,這是因為 $0 會包含腳本文件的路徑,為了讓輸出看起來清爽一些,我用 $(basename $0) 去掉了腳本的路徑名稱,下面是運行的結果:

技術分享圖片

$1, $2 等等

$0, $1,$2... 被稱為位置參數。所謂的位置參數(positional parameter),指的是 Shell 腳本的命令行參數(argument);同時也表示在 Shell 函數內的函數參數。它們的名稱是以單個的整數來命名。出於歷史的原因,當這個整數大於 9 時,就應該以大括號{} 括起來。下面是一個簡單的 demo:

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

技術分享圖片

$#

位置參數的個數,具體的用法請參考 $0 中的示例。

$* 與 "$*"

所有的位置參數。但是 $* 與 "$*" 的表現是不一樣的,我們通過下面的 demo 來介紹其異同。
$* 提供分隔後的參數:

for arg in $*
do
    echo $arg
done

技術分享圖片

$* 和 $@ 的表現是一樣的。

"$*" 把所有參數看作一個字符串:

for arg in "$*"
do
    echo $arg
done

技術分享圖片

$@ 與 "$@"

所有的位置參數。$@ 和 $* 的表現是一樣的。
"$@" 能夠提供看上去比較合理的結果:

for arg in "$@"
do
    echo $arg
done

技術分享圖片

下面是 "$@" 的一個比較常見的用法如下:

if[ "$1"=node ]; then
    SCRIPT_FILE=
    for ARG in "$@"
    do
        if[ "${ARG}"=main.js ]; then
            SCRIPT_FILE=main.js
            break
        fi
    done
    if[ -z "$SCRIPT_FILE" ]; then
        exec "$@""main.js"
        exit 0;
    fi
fi
exec"$@"

這是在常見 nodejs 的 docker 鏡像時經常使用的一段代碼:

"$@" 還常常與 shift 命令一起使用來丟棄參數 $1
#!/bin/bash
# 使用./test.sh 1 2 3 4 5 來調用這個腳本
echo "$@" # 1 2 3 4 5
shift
echo "$@" # 2 3 4 5
shift
echo "$@" # 3 4 5
# 每次 "shift" 都會丟棄$1.
# "$@" 將包含剩下的參數. 

還可以使用 set 命令在腳本中設置位置參數:

#!/bin/bash

set -- "First one" "second" "third:one" "" "Fifth: :one"
# 設置這個腳本的參數, $1, $2, 等等.
index=1 # 起始計數.
echo "Listing args with \"\$@\":"
for arg in "$@"
do
    echo "Arg #$index = $arg"
    let "index+=1"
done # $@ 把每個參數都看成是單獨的單詞.
echo "Arg list seen as separate words."

技術分享圖片

$!

運行在後臺的最後一個作業的 PID。

$ sleep 60 &
[1] 6238
$ echo "$!"
6238

如果有多個在後臺運行的任務,就需要通過 $! 來獲得 PID 並進行 wait:

$ sleep 60 &
$ pid1=$!
$ sleep 100 &
$ pid2=$!
$ wait $pid1      # 等待第一個後臺進程結束
$ wait $pid2      # 等待第二個後臺進程結束

$_

這個變量保存之前執行的命令的最後一個參數的值。
把下面的代碼保存在 test.sh 文件中:

#!/bin/bash

echo $_ # ./test.sh

du >/dev/null # 這麽做命令行上將沒有輸出.
echo $_ # du

ls -al >/dev/null # 這麽做命令行上將沒有輸出.
echo $_ # -al (這是最後的參數)

:
echo $_ # :

技術分享圖片

下面是一個比較常見的用法,可以直接進入創建的目錄:

$ mkdir hello && cd $_

技術分享圖片

$$

腳本自身的 PID (當前 bash 進程的 PID):

技術分享圖片

$PPID

進程的 $PPID 就是這個進程的父進程的 PID。

$?

$? 保存了最後所執行的命令的退出狀態碼,一般表示命令執行成功或失敗。當函數返回之後,$? 保存函數中最後所執行的命令的退出狀態碼。這就是 bash 對函數 "返回值" 的處理方法。當一個腳本退出,$? 保存了腳本的退出狀態碼,這個退出狀態碼也就是腳本中最後一個執行命令的退出狀態碼。 0 表示成功,其它值表示錯誤。

技術分享圖片

當腳本以不帶參數的 exit 命令來結束時,腳本的退出狀態碼就由腳本中最後執行的命令來決定(就是exit之前的命令)。不帶參數的exit命令與 exit $? 的效果是一樣的,甚至腳本的結尾不寫 exit,也與前兩者的效果相同。
我們還可以把 $? 保存到變量中,從而讓腳本返回其中某個命令的返回值:

#!/bin/bash
set -x
go get -d -v golang.org/x/net/html
go get -u github.com/jstemmer/go-junit-report
go test -v 2>&1 > tmp
status=$?
$GOPATH/bin/go-junit-report < tmp > test_output.xml

exit ${status}

上面的程序把 go test 命令的返回值保存到了變量 status 中,並通過 exit ${status} 作為腳本的返回值。

關於退出狀態
在 Linux 系統中,程序(包括腳本)的退出狀態是非常有用的,只要程序執行完成,就會向 Shell 返回一個退出狀態碼。這個狀態碼是一個數值,指明了程序是否成功結束。按照慣例,退出狀態碼為 0 表示程序運行成功;非 0 表示程序運行失敗,不同的值對應著不同的失敗原因。
造成程序運行失敗的原因可能是非法參數,也可能是出現了錯誤的條件。比如 cp 命令,退出狀態碼 1 表示文件沒有找到,2 表示文件不可讀,3 表示目標目錄沒有找到,4 表示目標目錄不可寫,5 表示一般性錯誤。

$BASH

Bash 的二進制程序文件的路徑:

技術分享圖片

$BASH_VERSION

檢查系統上安裝的 Bash 版本號:

技術分享圖片

檢查 $BASH_VERSION 對於判斷系統上到底運行的是哪個 shell 來說是一種非常好的方法。變量 $SHELL有時候不能夠給出正確的答案。

$EUID 與 $UID

$EUID 表示 "有效" 用戶 ID。

$UID 表示 用戶ID號,是當前用戶的用戶標識號, 記錄在 /etc/passwd 文件中。這是當前用戶的真實 id, 即使只是通過使用 su 命令來臨時改變為另一個用戶標識, 這個 id 也不會被改變。$UID 是一個只讀變量,不能在命令行或者腳本中修改它。

$GROUPS

當前用戶所屬的組。
這是一個當前用戶的組 id 數組, 與記錄在 /etc/passwd 文件中的內容一樣:

技術分享圖片

$HOME

用戶的 home 目錄,一般是 /home/username。

$HOSTNAME

主機名稱。

$IFS

內部域分隔符。這個變量用來決定 Bash 在解釋字符串時如何識別域,或者單詞邊界。
$IFS默認為空白(空格, 制表符,和換行符),但這是可以修改的,比如在分析逗號分隔的數據文件時,就可以設置為逗號。註意 $* 使用的是保存在 $IFS 中的第一個字符來分隔位置參數的。
$IFS 處理其他字符與處理空白字符不同的 demo:

#!/bin/bash

output_args_one_per_line()
{
    for arg
        do echo "[$arg]"
    done
}

echo "IFS=\" \""
echo "-------"

IFS=" "
var=" a b c "
output_args_one_per_line $var
echo; echo "IFS=:"
echo "-----"

IFS=:
var=":a::b:c:::" # 與上邊一樣, 但是用" "替換了":".
output_args_one_per_line $var
# 使用 : 後,冒號前後的空字符也被解析了。
exit 0

執行上面的腳本,結果如下:

技術分享圖片

$PATH

可執行文件的搜索路徑。
當給出一個命令時,Bash 會自動生成一張哈希(hash)表,並且在這張哈希表中按照 PATH 變量中所列出的路徑來搜索這個可執行命令。路徑會存儲在環境變量中,$PATH 變量本身就一個以冒號分隔的目錄列表。通常情況下,系統都是在 /etc/profile 和 ~/.bashrc 中存儲 $PATH 的定義,Ubuntu 是定義在 /etc/environment 文件中。

PATH=${PATH}:/opt/bin

將會把目錄 /opt/bin 附加到當前目錄列表中,在腳本中,這是一種把目錄臨時添加到 $PATH 中的權宜之計。當這個腳本退出時,$PATH 將會恢復以前的值(一個子進程,比如說一個腳本,是不能夠修改父進程的環境變量的)。

當前的"工作目錄",通常是不會出現在 $PATH 中的,這樣做的目的是出於安全的考慮。因為當前目錄是不斷變化的,很有可能會存在與系統工具同名的惡意程序(比如你在網上下載了一個叫 cat 的惡意程序)。這時執行 cat 命令,就會運行當前目錄下的 cat 惡意程序(把當前目錄放在 PATH 變量的靠前位置的情況)。
還有一種情況,比如我們經常會自己用c語言或者其它的語言寫一些程序,然後編譯、鏈接為可執行文件。假如我們的可執行文件是做一些不可恢復性的操作,比如刪除文件,格式化磁盤之類的。而這些文件名字又恰巧和我們系統 $PATH 下的某些常用可執行文件名字相同時,那麽結果會出乎我們的意料。
也就是說當前目錄是總在變化的,一會我們 cd 到這兒了,一會又 cd 到另一個地方去了。這樣的話,當前目錄下有哪些可執行文件也會隨著改變的。有時候我們不會太在意自己處於的目錄位置,如果當前目錄在 $PATH中,那麽我們也就不清楚自己幹了什麽。

而 $PATH 裏面則放置了一些固定的目錄,這些目錄是不會變化的,這樣的話,當我們輸入命令時,永遠可以保證不會隨著自己的位置改變,而導致出乎意料。

$OLDPWD

前一個工作目錄,可以通過下面的命令快速的回到前一個工作目錄:

$ cd -

$PWD

工作目錄(你當前所在的目錄),這與內置命令 pwd 的作用相同:

技術分享圖片

下面的腳本演示了如何防止誤刪文件:

#!/bin/bash

E_WRONG_DIRECTORY=73
clear # 清屏.
TargetDirectory=/home/nick/testdir
cd $TargetDirectory
echo "Deleting stale files in $TargetDirectory."

if [ "$PWD" != "$TargetDirectory" ]
then # 防止偶然刪錯目錄.
    echo "Wrong directory!"
    echo "In $PWD, rather than $TargetDirectory!"
    echo "Bailing out!"
    exit $E_WRONG_DIRECTORY
fi

rm -rf * # 刪除文件
rm .[A-Za-z0-9]* # 刪除點文件

echo "Done."
echo "Old files deleted in $TargetDirectory."
exit 0

執行上面的腳本,顯示的結果如下:

技術分享圖片

$PS1

這是主提示符,可以在命令行中見到它,筆者的 Ubuntu16.04 中為:

技術分享圖片

看起來有些復雜,其實是添加了一些字體顏色的設置等內容。

$PS2

第二提示符,當你需要額外輸入的時候,你就會看到它,默認值為 ">":

技術分享圖片

當我們往命令行上粘貼一個多行的命令時就會看到它的身影:

技術分享圖片

$PS4

第四提示符,當我們使用 -x 選項來調用腳本時,這個提示符會出現在每行輸出的開頭,默認為 "+":

技術分享圖片

運行下面的腳本:

set -x
echo "Hello nick"
echo This will show $PS4

技術分享圖片

參考:
Bash Internal Variables
《高級 Bash 腳本編程指南》
《Unix/Linux/OS X 中的 Shell 編程》

常見 Bash 內置變量介紹