Shell進(jìn)階腳本-內(nèi)部變量

內(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.txtfile1.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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末隅俘,一起剝皮案震驚了整個(gè)濱河市邻奠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌为居,老刑警劉巖碌宴,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蒙畴,居然都是意外死亡贰镣,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進(jìn)店門膳凝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來碑隆,“玉大人,你說我怎么就攤上這事鸠项「甚耍” “怎么了子姜?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵祟绊,是天一觀的道長。 經(jīng)常有香客問我哥捕,道長牧抽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任遥赚,我火速辦了婚禮扬舒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘凫佛。我一直安慰自己讲坎,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布愧薛。 她就那樣靜靜地躺著晨炕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪毫炉。 梳的紋絲不亂的頭發(fā)上瓮栗,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼费奸。 笑死弥激,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的愿阐。 我是一名探鬼主播微服,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼缨历!你這毒婦竟也來了职辨?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤戈二,失蹤者是張志新(化名)和其女友劉穎舒裤,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體觉吭,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡腾供,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鲜滩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片伴鳖。...
    茶點(diǎn)故事閱讀 39,902評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖徙硅,靈堂內(nèi)的尸體忽然破棺而出榜聂,到底是詐尸還是另有隱情,我是刑警寧澤嗓蘑,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布须肆,位于F島的核電站,受9級特大地震影響桩皿,放射性物質(zhì)發(fā)生泄漏豌汇。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一泄隔、第九天 我趴在偏房一處隱蔽的房頂上張望拒贱。 院中可真熱鬧,春花似錦佛嬉、人聲如沸逻澳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽斜做。三九已至,卻和暖如春缰揪,著一層夾襖步出監(jiān)牢的瞬間陨享,已是汗流浹背葱淳。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留抛姑,地道東北人赞厕。 一個(gè)月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像定硝,于是被迫代替她去往敵國和親皿桑。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內(nèi)容