介紹 shell 腳本如何接收用戶的輸入
更多精彩
- 更多技術(shù)博客,請移步 IT人才終生實訓與職業(yè)進階平臺 - 實訓在線
導覽
- 運行腳本時可以往腳本中傳入命令行參數(shù),例如
./param.sh 1 2 3 -
shift命令可以從右向左移動傳入的參數(shù)位置,類似于迭代器,shift n命令可以指定參數(shù)移動的位置數(shù),默認為 1 -
$#可以在腳本中直接獲取傳入的參數(shù)總數(shù),${!#}可以在腳本中直接獲取傳入的最后一個參數(shù) -
$*可以在腳本中直接獲取傳入的所有參數(shù),但獲取到的內(nèi)容是一整個字符串 -
$@可以再腳本中直接獲取傳入的所有參數(shù),而且獲取到的內(nèi)容是可以進行參數(shù)遍歷的字符串 - 運行腳本時可以往腳本中傳入選項,例如
./option.sh -a -b -c -
getopt命令可以用于指定腳本的參數(shù)和選項的傳入規(guī)則,在腳本中的基本語法是set -- $(getopt ab:cd "$@") -
getopts命令不僅可以指定腳本的參數(shù)和選項的傳入規(guī)則,而且還支持帶空格、引號的參數(shù),在腳本中的基本語法是getopts ab:cd opt -
read命令用于接收用戶輸入,可以通過指定變量名來分別接收用戶的單個輸入或多個輸入,只需要輸入內(nèi)容使用空格進行分隔 -
read -p命令可以簡化腳本的輸入流程,將輸入提示語句和輸入接收語句合并為一條語句 -
read -t命令可以指定輸入超時的時間 -
read -n命令可以指定輸入允許接收的字符數(shù)量,達到字符數(shù)量時會自動執(zhí)行下一步操作 -
read -s命令可以隱藏用戶輸入的內(nèi)容
14.1 命令行參數(shù)
- 使用命令行參數(shù)是向 shell 腳本傳遞數(shù)據(jù)的最基本方式
14.1.1 讀取參數(shù)
- 通過命令行參數(shù)傳遞到 shell 腳本中的數(shù)據(jù)會被標記為 位置參數(shù)( Positional Parameter )
- 位置參數(shù)的索引從 1 開始,因為 0 表示執(zhí)行 shell 腳本時用的名稱
- 例如
./input.sh 1 2,那么在input.sh中 ,$0的值是./input.sh,$1的值是 1 ,$2的值是 2
- 例如
- 通過命令行參數(shù)傳遞的參數(shù)數(shù)量最好在 10 個以下,因為從第 10 個開始就無法直接使用
$1這樣的格式獲取參數(shù)了,而需要使用${10}這樣的格式才能獲取到參數(shù) -
寫一個簡單的例子演示一下,如下圖
14.1.2 使用 basename 讀取腳本名
- 雖然通過
$0可以獲取腳本在執(zhí)行時的名稱,但如果腳本在執(zhí)行時攜帶了相應的路徑,也會出現(xiàn)在$0參數(shù)中 - 使用
basename就可以過濾掉$0參數(shù)中腳本名以外的內(nèi)容,從而得到一個純凈的腳本名稱,如下圖
14.1.3 測試參數(shù)
- 因為命令行參數(shù)是可選的,在執(zhí)行腳本時,可以添加也可以不添加,所以在腳本中使用命令行參數(shù)時,最好是先判斷一下,如下圖
- 在使用
test命令對命令行參數(shù)進行條件判斷時,不能直接使用$1,需要使用"$1"才能正確獲取到參數(shù)
- 在使用
14.2 特殊參數(shù)變量
14.2.1 參數(shù)統(tǒng)計
- 使用
$#可以輸出腳本在執(zhí)行時傳入了多少個參數(shù),如下圖
- 既然
$#可以返回傳入?yún)?shù)的數(shù)量,那么理論上使用${$#}就可以獲取到最后一個參數(shù)的內(nèi)容,但其實不是 - 要快速獲取最后一個參數(shù)的內(nèi)容需要通過
${!#},如下圖
14.2.2 抓取所有的數(shù)據(jù)
- 使用
$*可以獲取通過命令行傳入的所有參數(shù),但這些參數(shù)會被作為一個字符整體被保存- 將傳入的所有參數(shù)作為一個值
- 使用
$@也可以獲取通過命令行傳入的所有參數(shù),這些參數(shù)則會被作為一個可以循環(huán)的字符串被保存- 將傳入的每個參數(shù)作為單獨的值
- 寫一個簡單的例子演示一下,如下圖
- 在對兩者采用了相同遍歷方式的操作之后,通過
$*得到的是一個參數(shù),而$@得到的是 5 個參數(shù)
- 在對兩者采用了相同遍歷方式的操作之后,通過
14.3 使用 shift 命令移動變量
- 使用
shift命令時,默認會將每個參數(shù)變量向左移動一個位置,也就是參數(shù)$3 -> $2- 類似于 Java 中的迭代器
- 而除了參數(shù)
$0,其他的參數(shù)的位置在被后續(xù)參數(shù)替換后,自身就不復存在了- 因為
$0保存著程序名,所以不會改變
- 因為
- 寫一個例子簡單演示一下,如下圖
- 在
while循環(huán)中每次獲取的參數(shù)都是$1,但獲取到的內(nèi)容卻能夠發(fā)生變化 - 這就是因為每次循環(huán)后都通過
shift命令把后續(xù)的參數(shù)向左移動了一位
- 在
14.3.1 使用 shift 命令移動指定數(shù)量的變量
- 使用
shift num命令可以一次移動多個位置的變量,如下圖
14.3.2 使用 shift 命令移動多個位置時,傳入的參數(shù)必須是位置數(shù)的倍數(shù)
- 上從圖可知,使用
shift 2命令將參數(shù)的位置移動兩位,然后腳本在執(zhí)行的時候傳入了 6 個參數(shù) -
這個時候如果只傳入 5 個參數(shù),那么腳本就會無限循環(huán),如下圖
14.4 處理選項
- 執(zhí)行腳本時,除了能往腳本中傳入命令行參數(shù),還能 在單破折號后面跟字母 往腳本中傳遞 選項
14.4.1 查找選項
14.4.1.1 處理簡單選項
- 當腳本在執(zhí)行時傳入了選項,通過
case命令進行判斷是最好的處理方式,如下圖- 為了讓傳入的多個選項能在一次執(zhí)行過程中都被執(zhí)行到,需要先使用一個
while+shift對選項進行遍歷 - 在每次遍歷時都通過
case命令對選項的具體參數(shù)進行判斷 - 同時還通過
*)提供一個默認判斷
- 為了讓傳入的多個選項能在一次執(zhí)行過程中都被執(zhí)行到,需要先使用一個
14.4.1.2 分離參數(shù)和選項
- 可以通過 雙破折號( -- ) 將參數(shù)和選項進行分離,這樣才執(zhí)行腳本時就可以同時傳入兩者
- 這里的說法 不是指直接將參數(shù)和選項進行混排 ,而是 先傳入?yún)?shù)后傳入選項,或者先傳入選項后傳入?yún)?shù) ,在這兩者之間通過雙破折號進行分離,如下圖
- 才判斷選項的
case命令中,新增了一個--)分支,用于判斷分支的存在 - 當從傳入的內(nèi)容中檢索到這個分支時,會先執(zhí)行一個
shift,目的是將雙破這行這個分隔符跳過去 - 之后再執(zhí)行一次
break是為了跳出while循環(huán),進行后續(xù)屬于參數(shù)遍歷的for循環(huán) - 可以看到,當直接執(zhí)行
./option-param.sh -a -b -e a b e時,由于沒有檢測到--)分支,所以后續(xù)的參數(shù)也被識別成了錯誤的選項 -
但是當使用雙破折號將選項和參數(shù)分離后,就能順利遍歷到選項和參數(shù)了
- 才判斷選項的
- 從上述這個例子,其實我們可以思考一下,雙破折號真的是作為一個 強語法的特殊符號 來被 shell 識別的嗎?很顯然不是!
- 我們可以對上述腳本稍作修改,就可以得出下圖的效果
- 將上圖中的
--)分支判斷部分修改為下圖中的-e)分支判斷 - 然后執(zhí)行上圖中第一次執(zhí)行導致錯誤結(jié)果的語句,卻可以得到和上圖第二次正確結(jié)果一致的輸出
- 這說明 雙破折號作為參數(shù)和選項分隔符只是一個規(guī)范上的約定,并不是強語法的特殊符號
- 將上圖中的
14.4.1.3 處理帶值的選項
- 最復雜的情況就是參數(shù)和選項的混合搭配,但是在 shell 腳本中,當參數(shù)和選項混排后,對應的參數(shù)會被認為是前一個選項的值
- 例如
./option.sh -a str1 -b str2 -c,這里的str1就會被認為是-a選項的值,需要在-a選項的分支中進行操作 - 寫一個簡單的例子演示一下,如下圖
-
-a后續(xù)的參數(shù)識別之后會執(zhí)行一次shift命令的目的是為了將參數(shù)跳過
-
14.4.2 使用 getopt 命令
14.4.2.1 基礎(chǔ)語法
-
getopt命令用于將通過簡化形式傳入腳本的命令行參數(shù)和選項進行規(guī)范化- 比如在執(zhí)行腳本時使用
./option.sh -abc,如果腳本中存在getopt命令,就可以被轉(zhuǎn)換為-a -b -c - 這樣就可以被腳本正確的識別,同時用戶的輸入也得到了簡化
- 比如在執(zhí)行腳本時使用
- 在腳本執(zhí)行時,會先將這些傳入腳本的命令行參數(shù)和選項自動轉(zhuǎn)換為適當?shù)母袷?,讓腳本在使用這些參數(shù)和選項時更加方便
- 比如它可以定義哪些字母選項是有效的,哪些字母選項需要指定參數(shù)值
-
getopt命令可以在命令行執(zhí)行執(zhí)行,雖然這樣做沒有什么實際意義,但是可以幫助我們熟練一下語法,如下圖-
ab:cd就是通過getopt命令指定的參數(shù)選項規(guī)則,表示當前可以傳入abcd選項,而且選項b后面存在一個參數(shù) -
-ab test -cd就是實際傳入的參數(shù)內(nèi)容,第二行的輸出結(jié)果就是對這些傳入的內(nèi)容進行格式化后的結(jié)果
-
- 默認如果傳入的內(nèi)容和規(guī)則不匹配,會拋出錯誤,可以通過
-q選項忽略錯誤,如下圖
14.4.2.2 在腳本中使用
-
getopt命令自然是要在腳本中使用才有意義,而且在腳本中使用的語法基本都是固定的 -
getopt命令是用來規(guī)范參數(shù)和選項的,所以肯定是要放在腳本的最前端,這個也是毋庸置疑 - 一般只需要在腳本前端加上
set -- $(getopt -q ab:cd "$@")即可-
ab:cd是可變化的部分,指的是具體參數(shù)和選項的匹配方式
-
- 寫一個簡單的例子演示一下,如下圖
- 這個例子是在 centOS 環(huán)境下運行的,因為 macOS 中的
set命令無法按預期執(zhí)行出效果
- 這個例子是在 centOS 環(huán)境下運行的,因為 macOS 中的
- 上圖中
case命令的--)分支判斷是必須添加的,否則在執(zhí)行命令時最后一個分支會被多判斷一次 - 至于具體是為什么多判斷一次,可以在執(zhí)行腳本前添加
sh -x來對腳本進行 DEBUG ,如下圖- 首先在原來的腳本中注釋掉
--)分支 - 然后在與之前相同的腳本執(zhí)行語句之前加上
sh -x,之后就可以很清楚的看到腳本的執(zhí)行過程 - 在執(zhí)行過程的第一處可以看到,傳入的
-ab hello -cd被解析后,最后多出來一個-- - 于是在腳本的最后,這個
--就被作為傳入的選項執(zhí)行了 - 這就是為什么如果必須
case命令添加--)分支,并且通過shift+break命令跳過的原因
- 首先在原來的腳本中注釋掉
14.4.3 使用更高級的 getopts
-
getopts命令是getopt命令的復數(shù)版本( 其實說是升級版更貼切 )- 關(guān)鍵在于
getopts可以處理 帶空格和引號的參數(shù) ,而getopt不行
- 關(guān)鍵在于
-
getopts比getopt更優(yōu)秀的地方在于,每次調(diào)用時,只會處理一個參數(shù),并且在處理完所有參數(shù)后會返回一個非 0 的退出狀態(tài)碼- 而
getopt是將參數(shù)和選項生成為一個完整的輸出
- 而
-
getopts也可以忽略錯誤信息,只需要在參數(shù)選項規(guī)則之前加上一個冒號即可 - 如果某個選項后面需要跟一個參數(shù),這個參數(shù)會被存儲在
OPTARG變量中 - 還有一個變量是
OPTIND,用于存放getopts當前正在處理的參數(shù)位置 -
寫一個簡單的例子統(tǒng)一演示一下,如下圖
- 可以對比一下兩個命令在處理相同內(nèi)容時的區(qū)別,如下圖
- 首先,
getopts在處理參數(shù)時可以直接加入到while的條件判斷中,這讓語法變的更簡單易懂 - 其次,
getopts的case判斷也更簡單,每個分支前不需要使用-a),而是直接a)即可 - 用于跳過
--選項的分支判斷在getopts中也不需要了 - 最后最關(guān)鍵的是,在
while的每次循環(huán)結(jié)束之前,不需要使用shift命令來對參數(shù)進行位置移動
- 首先,
- 如果在參數(shù)中添加空格,
getopts也可以很好的處理,如下圖
- 如果在參數(shù)選項中傳入了與定義的規(guī)范不匹配的選項,會被輸出一個問號,如下圖
- 可以看到,
-d選項因為沒有找到對應的分支判斷,所以被識別為其他選項,但輸出內(nèi)容中可以正確的顯示出來 - 但是
-e選項因為不在getopts定義的規(guī)范中,所以最后只輸出來一個問號
- 可以看到,
- 如果說選項處理完畢后,還需要處理單獨的參數(shù)列表,則需要在處理參數(shù)之前,使用
shift $[ $OPTIND - 1 ]來跳過參數(shù)之前的所有選項,如下圖-
shift $[ $OPTIND - 1 ]的意思如果不理解的話,這里簡單解釋一下 - 首先,
$[ $OPTIND - 1 ]的意思是用getopts當前執(zhí)行的選項的位置數(shù)減一,為什么要減一,因為命令行參數(shù)的位置從 0 開始的 - 所以就相當于是使用
shift將傳入的參數(shù)內(nèi)容中被getopts執(zhí)行過的選項全部跳過
-
- 如果還是不理解,可以將上述腳本的
shift $[ $OPTIND - 1 ]語句注釋后再使用相同命令執(zhí)行,如下圖-
可以看到,所有的選項都被當做參數(shù)輸出了
-
14.5 將選項標準化
- 腳本的選項雖然沒有一個強制的語法規(guī)范,但有不少建議性規(guī)范,如果能遵守這些規(guī)范,會讓你編寫的腳本更通用,其他人在上手使用你的腳本時,學習門檻也更低
-
下圖中提供一些選項的常用含義
14.6 獲得用戶輸入
14.6.1 使用 read 命令進行基本的讀取
- 使用
read命令可以接收用戶輸入,輸入的內(nèi)容會被放置在后續(xù)的變量中,如下圖-
echo -n表示輸入內(nèi)容后不換行 -
可以看到,通過鍵盤輸入的 asing1elife 被存入到變量 name 中
-
- 使用
read -p命令可以實現(xiàn)合并輸入前提醒語句的效果,如下圖- 少了
echo -n語句,卻可以達到一樣的輸出效果
- 少了
- 如果想講輸入的內(nèi)容分配給多個變量,只需要將內(nèi)容使用空格進行分隔,并且在
read命令后使用多個變量進行接收,如下圖
- 如果在輸入的時候使用空格分隔了輸入內(nèi)容,但是在
read命令后沒有使用數(shù)量與之對應的變量分別接收,那么剩余的輸入內(nèi)容都會被直接存放到最后一個變量中,如下圖- 可以看到,在腳本
read-p.sh中,只有一個變量 name 用于接收輸入內(nèi)容 - 那么所有的輸入內(nèi)容都被存放到這個變量中
- 在腳本
read-multi.sh中,輸入內(nèi)容通過逗號分隔后有 6 個,但是變量只有 3 個,所以從第 3 個輸入開始,之后的所有內(nèi)容都被存放到了第三個變量中
- 可以看到,在腳本
- 如果不想在輸入命令的最后顯式的指定一個變量用于內(nèi)容接收,也可以直接使用特殊環(huán)境變量
REPLY來獲取輸入內(nèi)容,如下圖-
REPLY變量其實就相當于一個默認的接收變量被隱式的放在了read命令的最后,所以會默認接收所有的輸入內(nèi)容
-
14.6.2 超時
- 為了防止用戶一直不輸入,而導致腳本的執(zhí)行流行被阻斷,可以使用
read -t second來指定一個定時器 - 當輸入時間超過指定的時間后,
read命令會返回一個非 0 的退出狀態(tài)碼,如下圖
14.6.2.1 字數(shù)控制
- 使用
read -nNum可以控制輸入的字符數(shù),例如read -n1就是當輸入 1 個字符時就開始判斷,如下圖-
當輸入一個字符時,不管輸入的是什么,也不需要按回車鍵,腳本都會直接開始分支判斷
-
14.6.3 隱藏方式讀取
- 使用
read -s命令可以避免輸入的內(nèi)容出現(xiàn)在顯示器上,最典型的例子就是輸入密碼,如下圖-
實際上,數(shù)據(jù)還是會顯示,只是文本的顏色被設置成和終端的背景色一致,所以看不出來
-
14.6.4 從文件中讀取
- 使用
read命令可以讀取文件中的數(shù)據(jù),每次調(diào)用都會讀取一行文本,當文件中沒有剩余內(nèi)容時,會返回非 0 的退出狀態(tài)碼,如下圖- 首先使用
cat read-s.sh讀取文件內(nèi)容 - 然后通過管道將數(shù)據(jù)直接傳遞給
read命令 -
read再將每次讀取的行為通過while進行循環(huán)
- 首先使用

































