內(nèi)建變量
影響 Bash 腳本行為的變量
$BASH
Bash程序的路徑
bash$ echo $BASH
/bin/bash
$BASH_ENV
這個(gè)環(huán)境變量會指向一個(gè) Bash 啟動文件魂仍,該文件在腳本被調(diào)用時(shí)會被讀取
$BASH_SUBSHELL
該變量用于提示所處的 subshell 層級
這是在 Bash version 3 中被引入的新特性
$BASHPID
當(dāng)前 Bash 進(jìn)程實(shí)例的進(jìn)程ID號
雖然與 $$
變量不一樣,但是通常它們會給出相同的結(jié)果
bash4$ echo $$
11015
bash4$ echo $BASHPID
11015
bash4$ ps ax | grep bash4
11015 pts/2 R 0:00 bash4
#!/bin/bash4
echo "\$\$ outside of subshell = $$" # 9602
echo "\$BASH_SUBSHELL outside of subshell = $BASH_SUBSHELL" # 0
echo "\$BASHPID outside of subshell = $BASHPID" # 9602
echo
( echo "\$\$ inside of subshell = $$" # 9602
echo "\$BASH_SUBSHELL inside of subshell = $BASH_SUBSHELL" # 1
echo "\$BASHPID inside of subshell = $BASHPID" ) # 9603
# 注意 $$ 總是返回父進(jìn)程的 PID靖秩。
$BASH_VERSINFO[n]
這是一個(gè)6個(gè)元素的數(shù)組驼鹅,其中包含了已經(jīng)安裝的 Bash 的版本信息特恬。該變量與變量 $BASH_VERSION 類似悠砚,但是更加詳細(xì)
# Bash 版本信息:
for n in 0 1 2 3 4 5
do
echo "BASH_VERSINFO[$n] = ${BASH_VERSINFO[$n]}"
done
# BASH_VERSINFO[0] = 3 # 主版本號
# BASH_VERSINFO[1] = 00 # 次版本號
# BASH_VERSINFO[2] = 14 # 補(bǔ)丁號
# BASH_VERSINFO[3] = 1 # 構(gòu)建版本號
# BASH_VERSINFO[4] = release # 發(fā)行狀態(tài)
# BASH_VERSINFO[5] = i386-redhat-linux-gnu # 架構(gòu)
# (與 $MACHTYPE 相同)
$BASH_VERSION
已經(jīng)安裝的 Bash 的版本信息
bash$ echo $BASH_VERSION
3.2.25(1)-release
tcsh% echo $BASH_VERSION
BASH_VERSION: Undefined variable.
利用 $BASH_VERSION
來判斷運(yùn)行的是哪個(gè) shell 是一個(gè)不錯(cuò)的方法士嚎,因?yàn)樽兞?$SHELL
并不總是能夠給出正確的答案
$CDPATH
變量指定 cd 命令可以搜索的路徑凹嘲,路徑之間用冒號進(jìn)行分隔
該變量的功能類似于指定可執(zhí)行文件搜索路徑的變量 $PATH
可以在本地文件 ~/.bashrc 中設(shè)置該變量
bash$ cd bash-doc
bash: cd : bash-doc: No such file or directory
bash$ CDPATH=/usr/share/doc
bash$ cd bash-doc
/usr/share/doc/bash-doc
bash$ echo $PWD
/usr/share/doc/bash-doc
$DIRSTACK
指代目錄棧中頂部的值师倔,目錄棧由命令 pushd 和 popd 控制
該變量相當(dāng)于命令 dirs,但是 dirs 命令會顯示整個(gè)目錄棧
$EDITOR
腳本所調(diào)用的默認(rèn)編輯器周蹭,通常是 vi 或是 emcas
$EUID
有效用戶ID(EUID)是指當(dāng)前用戶正在使用的用戶ID趋艘,可以通過 su 命令修改
$EUID 與 $UID 并不總是相同的
$FUNCNAME
當(dāng)前運(yùn)行函數(shù)的函數(shù)名
xyz23 ()
{
echo "$FUNCNAME now executing." # xyz2 now executing.
}
xyz23
echo "FUNCNAME = $FUNCNAME" # FUNCNAME =
# 如果在函數(shù)外則為空值。
$GLOBIGNORE
在文件匹配時(shí)所忽略的文件名模式列表凶朗。
$GROUPS
當(dāng)前用戶所屬的用戶組
該變量存儲了當(dāng)前用戶所歸屬的用戶組ID列表瓷胧,是一個(gè)數(shù)組
內(nèi)容與記錄在文件 /etc/passwd
和文件 /etc/group
中的一致
root# echo $GROUPS
0
root# echo ${GROUPS[1]}
1
root# echo ${GROUPS[5]}
6
$HOME
當(dāng)前用戶的主目錄,其值通常為 /home/username
$HOMENAME
系統(tǒng)啟動的初始化腳本通過命令 hostname
給系統(tǒng)分配主機(jī)名
而函數(shù) gethostname()
則是給 Bash 的內(nèi)部變量 $HOSTNAME
賦值
$HOSTTYPE
主機(jī)類型
類似變量 $MACHTYPE
棚愤,用于識別系統(tǒng)硬件信息
bash$ echo $HOSTTYPE
i686
$IFS
內(nèi)部字段分隔符
該變量決定了 Bash 在解析字符串時(shí)如何去識別 字段
或單詞邊界
$IFS
的缺省值是空白符(空格搓萧,制表符以及換行符)
但其可以被修改,例如你在處理逗號分隔的文件時(shí)可以將其設(shè)置為逗號
需要注意 $*
使用保存在 $IFS
中的第一個(gè)字符
bash$ echo "$IFS"
(當(dāng) $IFS 設(shè)置為缺省值時(shí)宛畦,顯示空行瘸洛。)
bash$ echo "$IFS" | cat -vte
^I$
$
(顯示空白符:首先是一個(gè)空格,然后是 ^I [水平制表符]次和,
然后是換行符反肋,最后在末尾顯示 "$"。)
bash$ bash -c 'set w x y z; IFS=":-;"; echo "$*"'
w:x:y:z
(從字符串中解析命令踏施,然后將命令參數(shù)分配給位置參數(shù)石蔗。)
通過設(shè)置 $IFS 來忽略文件路徑名中空格帶來的影響
IFS="$(printf '\n\t')"
相比于其他字符,變量 $IFS 在處理空白符時(shí)有所不同
樣例-1. $IFS 與空白符
#!/bin/bash
# ifs.sh
var1="a+b+c"
var2="d-e-f"
var3="g,h,i"
IFS=+
# 加號會被解析成分隔符畅形。
echo $var1 # a b c
echo $var2 # d-e-f
echo $var3 # g,h,i
echo
IFS="-"
# 恢復(fù)對加號的默認(rèn)解析抓督。
# 現(xiàn)在減號會被解析成分隔符。
echo $var1 # a+b+c
echo $var2 # d e f
echo $var3 # g,h,i
echo
IFS=","
# 現(xiàn)在逗號會被解析成分隔符束亏。
# 恢復(fù)對減號的默認(rèn)解析铃在。
echo $var1 # a+b+c
echo $var2 # d-e-f
echo $var3 # g h i
echo
IFS=" "
# 現(xiàn)在空格會被解析成分隔符。
# 逗號恢復(fù)成默認(rèn)解析碍遍。
echo $var1 # a+b+c
echo $var2 # d-e-f
echo $var3 # g,h,i
# ======================================================== #
# 然而...
# $IFS 處理空白符的方式不同其他字符定铜。
output_args_one_per_line()
{
for arg
do
echo "[$arg]"
done # ^ ^ 為了獲得更好的視覺體驗(yàn),把參數(shù)放到了括號里怕敬。
}
echo; echo "IFS=\" \""
echo "-------"
IFS=" "
var=" a b c "
# ^ ^^ ^^^
output_args_one_per_line $var # output_args_one_per_line `echo " a b c "`
# [a]
# [b]
# [c]
echo; echo "IFS=:"
echo "-----"
IFS=:
var=":a::b:c:::" # 與上面一樣的模式揣炕,
# ^ ^^ ^^^ #+ 僅僅是將 " " 替換成了 ":" ...
output_args_one_per_line $var
# []
# [a]
# []
# [b]
# [c]
# []
# []
# 注意那些“空的”括號。
# 同樣的情況也會出現(xiàn)在 awk 命令所使用的 "FS" 字段分隔符中东跪。
echo
exit
$IGNOREEOF
忽略 EOF:用于指示 Shell 在注銷前需要忽略多少個(gè)文件結(jié)束符(EOF畸陡,contrl-D)
$LC_COLLATE
經(jīng)常會在文件 .bashrc
或是文件 /etc/profile
中被設(shè)置鹰溜。該變量控制文件名擴(kuò)展和模式匹配中的排序順序
如果設(shè)置不得當(dāng),LC_COLLATE
將會導(dǎo)致 文件名匹配中出現(xiàn)非預(yù)期結(jié)果
在 Bash 2.05 版本之后丁恭,文件名匹配在不再區(qū)分中括號中字母的大小寫
例如 ls [A-M]*
將會同時(shí)匹配 File1.txt
和 file1.txt
兩個(gè)文件曹动,如果想要恢復(fù)成之前的模式
則需要在文件 /etc/profile
或文件 ~/.bashrc
中通過語句 export LC_COLLATE=C
設(shè)置 LC_COLLATE
的值為 C
$LC_CTYPE
這個(gè)內(nèi)部變量控制在 文件匹配 和模式匹配中的字符解析行為
$LINENO
該變量記錄了其在腳本中被使用時(shí)所處行的行號。該變量只有在被使用時(shí)才有意義牲览,在調(diào)試過程中非常有用
# *** 調(diào)試部分起始 ***
last_cmd_arg=$_ # 保存最后的命令墓陈。
echo "At line number $LINENO, variable \"v1\" = $v1"
echo "Last command argument processed = $last_cmd_arg"
# *** 調(diào)試部分終止 ***
$MACHTYPE
設(shè)備類型,是識別系統(tǒng)硬件
bash$ echo $MACHTYPE
i686
$OLDPWD
上一個(gè)工作目錄(OLD-Print-Working-Directory)第献,也就是之前所在的目錄
$OSTYPE
操作系統(tǒng)類型
bash$ echo $OSTYPE
linux
$PATH
可執(zhí)行文件搜索路徑贡必,其值通常包含 /usr/bin
,/usr/X11R6/bin/
庸毫,/usr/local/bin
等路徑
給定一個(gè)命令仔拟,shell就會自動從搜索路徑包含的目錄中利用哈希表搜索該可執(zhí)行命令
而搜索路徑就保存在環(huán)境變量 $PATH
中,其中包含的一系列目錄則通過冒號進(jìn)行分隔
通常情況下飒赃,$PATH
會定義在文件 /etc/profile
或文件 ~/.bashrc
bash$ echo $PATH
/bin:/usr/bin:/usr/local/bin/:/usr/X11R6/bin:/sbin:/usr/sbin
PATH=${PATH}:/opt/bin 表示添加目錄 /opt/bin 到當(dāng)前的搜索路徑中
在腳本中可以通過這種方式臨時(shí)添加目錄到搜索路徑
而當(dāng)腳本結(jié)束時(shí)理逊,$PATH 就會恢復(fù)到原始值
(類似于腳本這樣的子進(jìn)程所作出的修改,不會影響到盒揉。例如: Shell 這樣的父進(jìn)程的環(huán)境)
基于安全考慮晋被,通常在 $PATH 中會省略當(dāng)前工作目錄 ./
$PIPESTATUS
該 數(shù)組
變量保存了最后運(yùn)行的前臺 管道
的 退出狀態(tài)(es)
bash$ echo $PIPESTATUS
0
bash$ ls -al | bogus_command
bash: bogus_command: command not found
bash$ echo ${PIPESTATUS[1]}
127
bash$ ls -al | bogus_command
bash: bogus_command: command not found
bash$ echo $?
127
$PIPESTATUS 數(shù)組中的每一個(gè)元素都代表了該管道中相對應(yīng)命令的退出狀態(tài)
$PIPESTATUS[0] 表示管道中第一個(gè)命令的退出狀態(tài)
$PIPESTATUS[1] 表示第二個(gè)命令的退出狀態(tài),以此類推
在Bash 3.0 以下版本的登錄shell中刚盈,變量 $PIPESTATUS 可能會包含一個(gè)不正確的 0 值
tcsh% bash
bash$ who | grep nobody | sort
bash$ echo ${PIPESTATUS[*]}
0
如果腳本包含了上述代碼羡洛,應(yīng)該得到期望的輸出是 0 1 0
在某些場景下,$PIPESTATUS 變量將會產(chǎn)生非預(yù)期結(jié)果
bash$ echo $BASH_VERSION
3.00.14(1)-release
bash$ ls | bogus_command | wc
bash: bogus_command: command not found
0 0 0
bash$ echo ${PIPESTATUS[@]}
141 127 0
Chet Ramey 把上述非預(yù)期結(jié)果的原因歸咎于 ls
命令的行為
如果 ls
將結(jié)果輸出到?jīng)]有被讀取的管道上藕漱,產(chǎn)生的 SIGPIPE 信號將會終止 ls
命令欲侮,同時(shí)其 退出狀態(tài) 從期望的 0 變?yōu)?141,而同樣的情況也會發(fā)生在命令 tr
中
$PIPESTATUS
是一個(gè)易失的變量
該變量需要在目標(biāo)管道執(zhí)行完成后肋联,且其他任何命令執(zhí)行之前去捕獲
bash$ ls | bogus_command | wc
bash: bogus_command: command not found
0 0 0
bash$ echo ${PIPESTATUS[@]}
0 127 0
bash$ echo ${PIPESTATUS[@]}
0
在 $PIPESTATUS
不能給出所期望的信息的情況下威蕉,使用 pipeline 選項(xiàng)可能會有幫助
$PPID
一個(gè)進(jìn)程的 $PPID
即該進(jìn)程的父進(jìn)程的進(jìn)程ID(pid)
可以與命令 pidof
進(jìn)行比較
$PROMPT_COMMAND
該變量存儲在主提示符 $PS1
顯示之前所需要執(zhí)行的命令
$PS1
主提示符,即在命令行中顯示的提示符
$PS2
次要提示符橄仍,當(dāng)需要額外輸入時(shí)出現(xiàn)的提示符韧涨。默認(rèn)顯示為 >
$PS3
三級提示符,顯示在 select
循環(huán)中
$PS4
四級提示符侮繁,當(dāng)使用 -x [verbose trace]
選項(xiàng)調(diào)用腳本時(shí)顯示的提示符
默認(rèn)顯示為 +
其可以作為調(diào)試的輔助手段虑粥,把一些診斷信息顯示在 $PS4
中可能會有幫助
P4='$(read time junk < /proc/$$/schedstat; echo "@@@ $time @@@ " )'
# 根據(jù) Erik Brandsberg 提供的建議。
set -x
# 可以在后面寫各種命令...
$PWD
工作目錄(你當(dāng)前所在的目錄)
該變量是內(nèi)建命令 pwd
#!/bin/bash
E_WRONG_DIRECTORY=85
clear # 清空屏幕宪哩。
TargetDirectory=/home/bozo/projects/GreatAmericanNovel
cd $TargetDirectory
echo "Deleting stale files in $TargetDirectory."
if [ "$PWD" != "$TargetDirectory" ]
then # 小心不要偶然清空了錯(cuò)誤的目錄娩贷。
echo "Wrong directory!"
echo "In $PWD, rather than $TargetDirectory!"
echo "Bailing out!"
exit $E_WRONG_DIRECTORY
fi
rm -rf *
rm .[A-Za-z0-9]* # 刪除隱藏文件。
# rm -f .[^.]* ..?* 刪除那些以多個(gè)點(diǎn)開頭的文件锁孟。
# (shopt -s dotglob; rm -f *) 這樣寫也可以彬祖。
# 文件名可以包含ASCII碼中范圍為 0-255 的所有字符茁瘦,
#+ 除了字符 "/"。
# 刪除以一些特殊字符開頭的文件储笑,例如 -
#+ 留作練習(xí)甜熔。(提示: rm ./-weirdname 或者 rm -- -weirdname)
result=$? # 刪除操作的結(jié)果。如果刪除成功南蓬,值為0。
echo
ls -al # 是不是還有剩余沒有刪除的文件哑了?
echo "Done."
echo "Old files deleted in $TargetDirectory."
echo
# 如果有其他需要赘方,在這里完成。
exit $result
$REPLY
當(dāng)沒有給 read
命令提供接收參數(shù)時(shí)的默認(rèn)接收參數(shù)
該變量同樣適用于 select
菜單接收用戶輸入值的場景弱左,需要注意的是用戶只需要輸入菜單項(xiàng)的編號窄陡,而不需要輸入完整的菜單項(xiàng)內(nèi)容
#!/bin/bash
# reply.sh
# REPLY 是 'read' 命令的默認(rèn)接收參數(shù)。
echo
echo -n "What is your favorite vegetable? "
read
echo "Your favorite vegetable is $REPLY."
# 當(dāng)且僅當(dāng) 'read' 命令沒有接收參數(shù)的時(shí)候拆火,
#+ REPLY 才能保存最近一次 'read' 命令接收的值跳夭。
echo
echo -n "What is your favorite fruit? "
read fruit
echo "Your favorite fruit is $fruit."
echo "but..."
echo "Value of \$REPLY is still $REPLY."
# 因?yàn)樽兞?$fruit 接收了新一次 "read" 命令所讀入的值,
#+ 所以 $REPLY 仍舊存儲的是上一次接收的值们镜。
echo
exit 0
$SECONDS
該變量記錄到目前為止腳本執(zhí)行的時(shí)間币叹,單位為秒
#!/bin/bash
TIME_LIMIT=10
INTERVAL=1
echo
echo "Hit Control-C to exit before $TIME_LIMIT seconds."
echo
while [ "$SECONDS" -le "$TIME_LIMIT" ]
do # $SECONDS 是一個(gè) shell 的內(nèi)部變量。
if [ "$SECONDS" -eq 1 ]
then
units=second
else
units=seconds
fi
echo "This script has been running $SECONDS $units."
# 在一臺性能較差或負(fù)載過重的設(shè)備上模狭,
#+ 這個(gè)腳本可能會偶爾跳過幾個(gè)計(jì)數(shù)颈抚。
sleep $INTERVAL
done
echo -e "\a" # 發(fā)出蜂鳴聲!
exit 0
$SHELLOPTS
該只讀變量記錄了 Shell 中已啟用的 選項(xiàng)
列表
bash$ echo $SHELLOPTS
braceexpand:hashall:histexpand:monitor:history:interactive-comments:emacs
$SHLVL
當(dāng)前 shell 的層級嚼鹉,即嵌套了多少層 Bash
如果命令行的層級 $SHLVL
為 1贩汉,那么在其中執(zhí)行的腳本層級則增加到 2
該變量 不受 subshell 影響,當(dāng)你需要指出嵌套了多少層 subshell 時(shí)锚赤,需要使用變量 $BASH_SUBSHELL
$TMOUT
如果 $TMOUT
被設(shè)為非 0 值 time匹舞,那么 shell 會在 $time
秒后超時(shí),然后導(dǎo)致 Shell 登出
在 Bash 2.05b 版本之后线脚,可以在腳本中將 read 命令與 $TMOUT
變量進(jìn)行結(jié)合
# 只能在 Bash 2.05b 及之后的版本中成功執(zhí)行赐稽。
TMOUT=3 # 提示會在 3 秒后超時(shí)。
echo "What is your favorite song?"
echo "Quickly now, you only have $TMOUT seconds to answer!"
read song
if [ -z "$song" ]
then
song="(no answer)"
# 默認(rèn)值浑侥。
fi
echo "Your favorite song is $song."
在腳本中又憨,同樣也存在其他一些實(shí)現(xiàn)超時(shí)功能的更復(fù)雜的方法
其中一個(gè)方法是設(shè)置一個(gè)循環(huán)的計(jì)時(shí)器,當(dāng)腳本超時(shí)的時(shí)候锭吨,計(jì)時(shí)器會給腳本發(fā)送一個(gè)信號
同時(shí)蠢莺,也需要一個(gè)處理信號的程序來 捕獲
由循環(huán)計(jì)時(shí)器產(chǎn)生的中斷
樣例-2. 限時(shí)輸入
#!/bin/bash
# timed-input.sh
# TMOUT=3 在新版本的 Bash 中起效。
TIMER_INTERRUPT=14
TIMELIMIT=3 # 在該實(shí)例中設(shè)置為 3 秒零如。
# 同樣可以設(shè)置成其他值躏将。
PrintAnswer()
{
if [ "$answer" = TIMEOUT ]
then
echo $answer
else # 不要混淆了這兩個(gè)實(shí)例锄弱。
echo "Your favorite veggie is $answer"
kill $! # 終止在后臺運(yùn)行的
#+ 不再被需要的 TimerOn 函數(shù)。
# $! 代表最后一個(gè)在后臺運(yùn)行的作業(yè)的進(jìn)程ID祸憋。
fi
}
TimerOn()
{
sleep $TIMELIMIT && kill -s 14 $$ &
# 等待 3 秒会宪,然后給腳本發(fā)送一個(gè)信號。
}
Int14Vector()
{
answer="TIMEOUT"
PrintAnswer
exit $TIMER_INTERRUPT
}
trap Int14Vector $TIMER_INTERRUPT
# 我們的目的就是通過時(shí)間中斷 (14) 終止程序蚯窥。
echo "What is your favorite vegetable "
TimerOn
read answer
PrintAnswer
# 必須承認(rèn)掸鹅,這個(gè)實(shí)現(xiàn)限時(shí)輸入的方法并不優(yōu)雅。
# 但利用 "read" 命令的 "-t" 選項(xiàng)可以簡化這個(gè)操作拦赠。
# 參考腳本 "t-out.sh"巍沙。
# 思考一下,如果不是對用戶的單次輸入時(shí)間進(jìn)行限制荷鼠,
#+ 而是對整個(gè)腳本的運(yùn)行時(shí)間進(jìn)行限制句携,應(yīng)該怎么做?
# 如果你需要更優(yōu)雅的寫法 ...
#+ 可以考慮用 C 或者 C++ 來編寫應(yīng)用允乐,
#+ 并使用其中包含的類似 'alarm' 或是 ‘setitimer' 等合適的庫函數(shù)來實(shí)現(xiàn)計(jì)時(shí)矮嫉。
exit 0
還有一種方法是使用 stty
樣例-3. 再來一次,限時(shí)輸入
#!/bin/bash
# timeout.sh
INTERVAL=5 # 超時(shí)所需的時(shí)間間隔
timedout_read() {
timeout=$1
varname=$2
old_tty_settings=`stty -g`
stty -icanon min 0 time ${timeout}0
eval read $varname # 或者直接寫成 read $varname
stty "$old_tty_settings"
# 參考 "stty" 的幫助頁面 (man)牍疏。
}
echo; echo -n "What's your name? Quick! "
timedout_read $INTERVAL your_name
# 該腳本也許并不能在所有類型的終端上正常運(yùn)行蠢笋。
# 最大的超時(shí)時(shí)間間隔依賴于終端。
#+ (通常是 25.5 秒)鳞陨。
echo
if [ ! -z "$your_name" ] # 如果在超時(shí)前輸入了姓名 ...
then
echo "Your name is $your_name."
else
echo "Timed out."
fi
echo
# 該腳本的計(jì)時(shí)行為與 "timed-input.sh" 中的計(jì)時(shí)行為有所不同挺尿,
# 該腳本的計(jì)時(shí)器會在每次按鍵后被重置。
exit 0
可能最簡單的方法就是利用 read
命令的 -t
選項(xiàng)
樣例-4. 限時(shí) read
#!/bin/bash
# t-out.sh [time-out]
# 從 "syngin seven" 的建議中所汲取的靈感炊邦,謝謝你們编矾。
TIMELIMIT=4 # 4 秒
read -t $TIMELIMIT variable <&1
# ^^^
# 在這個(gè)實(shí)例中,只有 Bash 1.x 或 Bash 2.x 版本需要 "<&1"馁害,
# 而在 Bash 3 及更高版本則不需要窄俏。
echo
if [ -z "$variable" ] # 判斷是否為空。
then
echo "Timed out, variable still unset."
else
echo "variable = $variable"
fi
exit 0
$UID
用戶 ID
記錄在文件 /etc/passwd
中當(dāng)前用戶的用戶標(biāo)識號
該 ID 表示的是當(dāng)前用戶的真實(shí) ID碘菜,即使用戶通過 su
命令臨時(shí)切換至另一個(gè)用戶凹蜈,這個(gè) ID 也不會改變
$UID
是一個(gè)只讀變量,不能夠被命令行或是腳本中的命令所修改忍啸,并與內(nèi)建命令 id
]相對應(yīng)
樣例-5. 我是 root 用戶嗎仰坦?
#!/bin/bash
# am-i-root.sh: 我是否是 root 用戶?
ROOT_UID=0 # Root 用戶的 $UID 為 0计雌。
if [ "$UID" -eq "$ROOT_UID" ] # 只有真正的 "root" 用戶才能經(jīng)受得住考研悄晃。
then
echo "You are root."
else
echo "You are just an ordinary user (but mom loves you just the same)."
fi
exit 0
# ============================================================= #
# 下面的代碼將不會被執(zhí)行,因?yàn)槟_本已經(jīng)退出了。
# 另外一種判斷是否是 root 用戶的方法:
ROOTUSER_NAME=root
username=`id -nu` # 或是... username=`whoami`
if [ "$username" = "$ROOTUSER_NAME" ]
then
echo "Rooty, toot, toot. You are root."
else
echo "You are just a regular fella."
fi
變量 $ENV
妈橄,$LOGNAME
庶近,$MAIL
,$TERM
眷蚓,$USER
以及 $USERNAME
并不是 Bash 的 內(nèi)建變量
鼻种,而是在 Bash
或系統(tǒng)的某個(gè)啟動文件中,被設(shè)置而成的 環(huán)境變量
代表當(dāng)前用戶登錄 shell 名稱的變量 $SHELL
是在文件 /etc/password
或是某個(gè)初始化腳本中被設(shè)定的沙热,它也不是一個(gè) Bash 的內(nèi)建變量
tcsh% echo $LOGNAME
bozo
tcsh% echo $SHELL
/bin/tcsh
tcsh% echo $TERM
rxvt
bash$ echo $LOGNAME
bozo
bash$ echo $SHELL
/bin/tcsh
bash$ echo $TERM
rxvt
位置參數(shù)
$0, $1, $2 等
位置參數(shù)叉钥。出現(xiàn)在從命令行傳遞給腳本、函數(shù)或是通過內(nèi)建命令 `set`]設(shè)置變量時(shí)
$#
命令行參數(shù)或是位置參數(shù)的個(gè)數(shù)
$*
將所有的位置參數(shù)整合篙贸,視作一個(gè)單詞
該參數(shù)必須是被引用的狀態(tài)投队,"$*"
$@
該參數(shù)等同于 $*
,但其中每個(gè)參數(shù)都是獨(dú)立的被引用的字符串
也就是說歉秫,所有的參數(shù)都是被原封不動的進(jìn)行傳遞蛾洛,并沒有被解析或是擴(kuò)展
這意味著养铸,參數(shù)列表中的每一個(gè)參數(shù)都被獨(dú)立視為一個(gè)單詞
同樣雁芙,該參數(shù)必須是被引用的狀態(tài)$@
樣例-6. 參數(shù)列表:利用 $*
和 $@
列出參數(shù)
#!/bin/bash
# arglist.sh
# 在調(diào)用該腳本時(shí)需要跟上一些參數(shù),例如 "one two three" ...
E_BADARGS=85
if [ ! -n "$1" ]
then
echo "Usage: `basename $0` argument1 argument2 etc."
exit $E_BADARGS
fi
echo
index=1 # 初始化計(jì)數(shù)器钞螟。
echo "Listing args with \"\$*\":"
for arg in "$*" # 如果這里沒有引用 "$*"兔甘,腳本將不會正常運(yùn)行。
do
echo "Arg #$index = $arg"
let "index+=1"
done # $* 將所有參數(shù)視作一個(gè)單詞鳞滨。
echo "Entire arg list seen as single word."
echo
index=1 # 重置計(jì)數(shù)器洞焙。
# 如果忘了這一步將會發(fā)生什么?
echo "Listing args with \"\$@\":"
for arg in "$@"
do
echo "Arg #$index = $arg"
let "index+=1"
done # $@ 將所有參數(shù)視作獨(dú)立的單詞拯啦。
echo "Arg list seen as separate words."
echo
index=1 # 重置計(jì)數(shù)器澡匪。
echo "Listing args with \$* (unquoted):"
for arg in $*
do
echo "Arg #$index = $arg"
let "index+=1"
done # 未被引用的 $* 將所有參數(shù)視作獨(dú)立的單詞。
echo "Arg list seen as separate words."
exit 0
在 shift 命令執(zhí)行后褒链,1 之外的剩余的命令行參數(shù)唁情,而 $1 則會被丟棄
#!/bin/bash
# 使用 ./scriptname 1 2 3 4 5 調(diào)用腳本
echo "$@" # 1 2 3 4 5
shift
echo "$@" # 2 3 4 5
shift
echo "$@" # 3 4 5
# 每一次 "shift" 都會丟棄參數(shù) $1。
# "$@" 則包含了剩余的所有參數(shù)甫匹。
參數(shù) $@
也可被用作過濾 shell 腳本輸入的工具甸鸟。結(jié)構(gòu) cat $@
可以接受來自標(biāo)準(zhǔn)輸入 stdin 的輸入,也可以接受傳遞給腳本的參數(shù)中的文件中的輸入
根據(jù)分隔符 $IFS
設(shè)置的不同兵迅,$*
和 $@
有時(shí)會出現(xiàn)不一致或非預(yù)期行為
樣例-7. $*
和 $@
的不一致行為
#!/bin/bash
# Bash 的內(nèi)部變量 "$*" 和 "$@" 擁有不穩(wěn)定的行為抢韭,
#+ 這些行為是否出現(xiàn)通常依賴于它們是否是被引用的狀態(tài)。
# 下面的代碼會演示在分詞和換行時(shí)恍箭,這些變量所會出現(xiàn)的一些不一致的處理方式刻恭。
set -- "First one" "second" "third:one" "" "Fifth: :one"
# 設(shè)置腳本參數(shù),$1, $2, $3 等等扯夭。
echo
echo 'IFS unchanged, using "$*"'
c=0
for i in "$*" # 被引用狀態(tài)吠各。
do echo "$((c+=1)): [$i]" # 這一行在下面所有的例子中都保持不變臀突。
# 輸出參數(shù)。
done
echo ---
echo 'IFS unchanged, using $*'
c=0
for i in $* # 未被引用狀態(tài)贾漏。
do echo "$((c+=1)): [$i]"
done
echo ---
echo 'IFS unchanged, using "$@"'
c=0
for i in "$@"
do echo "$((c+=1)): [$i]"
done
echo ---
echo 'IFS unchanged, using $@'
c=0
for i in $@
do echo "$((c+=1)): [$i]"
done
echo ---
IFS=:
echo 'IFS=":", using "$*"'
c=0
for i in "$*"
do echo "$((c+=1)): [$i]"
done
echo ---
echo 'IFS=":", using $*'
c=0
for i in $*
do echo "$((c+=1)): [$i]"
done
echo ---
var=$*
echo 'IFS=":", using "$var" (var=$*)'
c=0
for i in "$var"
do echo "$((c+=1)): [$i]"
done
echo ---
echo 'IFS=":", using $var (var=$*)'
c=0
for i in $var
do echo "$((c+=1)): [$i]"
done
echo ---
var="$*"
echo 'IFS=":", using $var (var="$*")'
c=0
for i in $var
do echo "$((c+=1)): [$i]"
done
echo ---
echo 'IFS=":", using "$var" (var="$*")'
c=0
for i in "$var"
do echo "$((c+=1)): [$i]"
done
echo ---
echo 'IFS=":", using "$@"'
c=0
for i in "$@"
do echo "$((c+=1)): [$i]"
done
echo ---
echo 'IFS=":", using $@'
c=0
for i in $@
do echo "$((c+=1)): [$i]"
done
echo ---
var=$@
echo 'IFS=":", using $var (var=$@)'
c=0
for i in $var
do echo "$((c+=1)): [$i]"
done
echo ---
echo 'IFS=":", using "$var" (var=$@)'
c=0
for i in "$var"
do echo "$((c+=1)): [$i]"
done
echo ---
var="$@"
echo 'IFS=":", using "$var" (var="$@")'
c=0
for i in "$var"
do echo "$((c+=1)): [$i]"
done
echo ---
echo 'IFS=":", using $var (var="$@")'
c=0
for i in $var
do echo "$((c+=1)): [$i]"
done
echo
# 嘗試在 ksh 或是 zsh -y 下執(zhí)行這個(gè)腳本候学。
exit 0
$@
和 $*
僅在被雙引號引用時(shí)才會表現(xiàn)出不同
樣例-8. 當(dāng) $IFS
為空時(shí) $*
和 $@
的表現(xiàn)
#!/bin/bash
# 如果 $IFS 被設(shè)置為空,
#+ 那么 "$*" 和 "$@" 將不會像期望的那樣輸出位置參數(shù)纵散。
mecho () # 輸出位置參數(shù)梳码。
{
echo "$1,$2,$3";
}
IFS="" # 設(shè)置為空。
set a b c # 位置參數(shù)伍掀。
mecho "$*" # abc,,
# ^^
mecho $* # a,b,c
mecho $@ # a,b,c
mecho "$@" # a,b,c
# 當(dāng) $IFS 為空時(shí) $* 和 $@ 的行為
#+ 依賴于 Bash 或是 sh 所運(yùn)行的版本掰茶。
# 因此不宜在腳本中使用這個(gè)“特性”。
exit
其他特殊參數(shù)
$-
使用 set
命令設(shè)置的腳本標(biāo)記
這個(gè)參數(shù)最開始是從 ksh 引入到 Bash中的蜜笤。但很遺憾的是濒蒋,該參數(shù)在 Bash 腳本中并不能可靠地運(yùn)行
該參數(shù)可能的一個(gè)用法是用于 自檢腳本是否可交互
$!
運(yùn)行在后臺的最后一個(gè)任務(wù)的 進(jìn)程ID
LOG=$0.log
COMMAND1="sleep 100"
echo "Logging PIDs background commands for script: $0" >> "$LOG"
# 這樣就可以監(jiān)控命令,并在必要的時(shí)候終止它們把兔。
echo >> "$LOG"
# 記錄命令沪伙。
echo -n "PID of \"$COMMAND1\": " >> "$LOG"
${COMMAND1} &
echo $! >> "$LOG"
# "sleep 100" 的 PID 是 1506
將 $!
用于控制任務(wù):
possibly_hanging_job & { sleep ${TIMEOUT}; eval 'kill -9 $!' &> /dev/null; }
# 強(qiáng)制終止一個(gè)出錯(cuò)的程序。
# 非常有用县好,例如可以用在啟動腳本中围橡。
也可以這么使用:
TIMEOUT=30 # 以秒為單位的超時(shí)時(shí)間值。
count=0
possibly_hanging_job & {
while ((count < TIMEOUT )); do
eval '[ ! -d "/proc/$!" ] && ((count = TIMEOUT))'
# 當(dāng)前運(yùn)行進(jìn)程的詳細(xì)信息都可以在 /proc 中找到缕贡。
# "-d" 用于測試進(jìn)程是否存在(即在 /proc 文件夾下該進(jìn)程的文件夾是否存在)翁授。
# 我們在等待出問題的任務(wù)出現(xiàn)。
((count++))
sleep 1
done
eval '[ -d "/proc/$!" ] && kill -15 $!'
# 如果被掛起的任務(wù)正在運(yùn)行就終止它晾咪。
}
# -------------------------------------------------------------- #
# 然而收擦,如果另外一個(gè)進(jìn)程在 "hanging_job" 之后開始運(yùn)行
#+ 該函數(shù)可能不能正常運(yùn)行 ...
# 在那種情況下,一個(gè)非我們預(yù)期的任務(wù)會被終止谍倦。
# Ariel Meragelman 提出了如下的解決方案塞赂。
TIMEOUT=30
count=0
possibly_hanging_job & {
while ((count < TIMEOUT )); do
eval '[ !-d "/proc/$lastjob" ] && ((count = TIMEOUT))'
lastjob=$!
((count++))
sleep 1
done
eval '[ -d "/proc/$lastjob" ] && kill -15 $lastjob'
}
exit
$_
該變量被設(shè)置為上一個(gè)執(zhí)行的命令的最后一個(gè)參數(shù)
樣例-9. 下劃線變量
#!/bin/bash
echo $_ # /bin/bash
# 僅通過調(diào)用 /bin/bash 執(zhí)行該腳本。
# 注意這個(gè)結(jié)果會根據(jù)腳本如何被調(diào)用
#+ 而有所不同剂跟。
du >/dev/null # 這樣命令就不會在命令行上有任何輸出减途。
echo $_ # du
ls -al >/dev/null # 這樣命令就不會在命令行上有任何輸出。
echo $_ # -al (最后一個(gè)參數(shù))
:
echo $_ # :
$?
命令曹洽、函數(shù)或是腳本自身的 退出狀態(tài)
$$
腳本自身的進(jìn)程 ID
該變量 $$
通常在腳本構(gòu)建獨(dú)有的臨時(shí)文件時(shí)被使用鳍置,該方法通常比調(diào)用 mktemp
命令更簡單
注記
棧寄存器是一段連續(xù)的內(nèi)存空間,在該空間中送淆,存入(壓棧)的值是以倒序的方式取出(出棧)的税产,最后一個(gè)存入的值被最先取,其通常又被稱為后進(jìn)先出(LIFO)或是下堆棧
當(dāng)前運(yùn)行腳本的進(jìn)程 ID 就是 $$
類似于 遞歸,在本文中辟拷,嵌套是指代一種模式被嵌入在一種更大的模式中
在 1913 年出版的韋伯斯特大辭典中用一種更加優(yōu)雅的方式解釋了什么是嵌套:“一組按體積大小排列的盒子撞羽、箱子或是類似的東西,它們中的每一個(gè)都被放入到另一個(gè)更大的箱子中(A collection of boxes, cases, or the like, of graduated size, each put within the one next larger.)”術(shù)語“變量(argument)”和“參數(shù)(parameter)”通常情況下是可以互相交換使用的衫冻。在本書中诀紊,它們具有相同的含義:傳入腳本或函數(shù)的變量
在 subshell 中運(yùn)行的腳本,
$$
返回腳本的進(jìn)程 ID