Linux 命令行與 shell 腳本編程大全 14 處理用戶輸入

介紹 shell 腳本如何接收用戶的輸入

更多精彩

導覽

  1. 運行腳本時可以往腳本中傳入命令行參數(shù),例如 ./param.sh 1 2 3
  2. shift 命令可以從右向左移動傳入的參數(shù)位置,類似于迭代器,shift n 命令可以指定參數(shù)移動的位置數(shù),默認為 1
  3. $# 可以在腳本中直接獲取傳入的參數(shù)總數(shù),${!#} 可以在腳本中直接獲取傳入的最后一個參數(shù)
  4. $* 可以在腳本中直接獲取傳入的所有參數(shù),但獲取到的內(nèi)容是一整個字符串
  5. $@ 可以再腳本中直接獲取傳入的所有參數(shù),而且獲取到的內(nèi)容是可以進行參數(shù)遍歷的字符串
  6. 運行腳本時可以往腳本中傳入選項,例如 ./option.sh -a -b -c
  7. getopt 命令可以用于指定腳本的參數(shù)和選項的傳入規(guī)則,在腳本中的基本語法是 set -- $(getopt ab:cd "$@")
  8. getopts 命令不僅可以指定腳本的參數(shù)和選項的傳入規(guī)則,而且還支持帶空格、引號的參數(shù),在腳本中的基本語法是 getopts ab:cd opt
  9. read 命令用于接收用戶輸入,可以通過指定變量名來分別接收用戶的單個輸入或多個輸入,只需要輸入內(nèi)容使用空格進行分隔
  10. read -p 命令可以簡化腳本的輸入流程,將輸入提示語句和輸入接收語句合并為一條語句
  11. read -t 命令可以指定輸入超時的時間
  12. read -n 命令可以指定輸入允許接收的字符數(shù)量,達到字符數(shù)量時會自動執(zhí)行下一步操作
  13. read -s 命令可以隱藏用戶輸入的內(nèi)容

14.1 命令行參數(shù)

  1. 使用命令行參數(shù)是向 shell 腳本傳遞數(shù)據(jù)的最基本方式

14.1.1 讀取參數(shù)

  1. 通過命令行參數(shù)傳遞到 shell 腳本中的數(shù)據(jù)會被標記為 位置參數(shù)( Positional Parameter )
  2. 位置參數(shù)的索引從 1 開始,因為 0 表示執(zhí)行 shell 腳本時用的名稱
    • 例如 ./input.sh 1 2 ,那么在 input.sh 中 ,$0 的值是 ./input.sh ,$1 的值是 1 ,$2 的值是 2
  3. 通過命令行參數(shù)傳遞的參數(shù)數(shù)量最好在 10 個以下,因為從第 10 個開始就無法直接使用 $1 這樣的格式獲取參數(shù)了,而需要使用 ${10} 這樣的格式才能獲取到參數(shù)
  4. 寫一個簡單的例子演示一下,如下圖


14.1.2 使用 basename 讀取腳本名

  1. 雖然通過 $0 可以獲取腳本在執(zhí)行時的名稱,但如果腳本在執(zhí)行時攜帶了相應的路徑,也會出現(xiàn)在 $0 參數(shù)中
  2. 使用 basename 就可以過濾掉 $0 參數(shù)中腳本名以外的內(nèi)容,從而得到一個純凈的腳本名稱,如下圖

14.1.3 測試參數(shù)

  1. 因為命令行參數(shù)是可選的,在執(zhí)行腳本時,可以添加也可以不添加,所以在腳本中使用命令行參數(shù)時,最好是先判斷一下,如下圖
    • 在使用 test 命令對命令行參數(shù)進行條件判斷時,不能直接使用 $1 ,需要使用 "$1" 才能正確獲取到參數(shù)

14.2 特殊參數(shù)變量

14.2.1 參數(shù)統(tǒng)計

  1. 使用 $# 可以輸出腳本在執(zhí)行時傳入了多少個參數(shù),如下圖
  2. 既然 $# 可以返回傳入?yún)?shù)的數(shù)量,那么理論上使用 ${$#} 就可以獲取到最后一個參數(shù)的內(nèi)容,但其實不是
  3. 要快速獲取最后一個參數(shù)的內(nèi)容需要通過 ${!#} ,如下圖

14.2.2 抓取所有的數(shù)據(jù)

  1. 使用 $* 可以獲取通過命令行傳入的所有參數(shù),但這些參數(shù)會被作為一個字符整體被保存
    • 將傳入的所有參數(shù)作為一個值
  2. 使用 $@ 也可以獲取通過命令行傳入的所有參數(shù),這些參數(shù)則會被作為一個可以循環(huán)的字符串被保存
    • 將傳入的每個參數(shù)作為單獨的值
  3. 寫一個簡單的例子演示一下,如下圖
    • 在對兩者采用了相同遍歷方式的操作之后,通過 $* 得到的是一個參數(shù),而 $@ 得到的是 5 個參數(shù)

14.3 使用 shift 命令移動變量

  1. 使用 shift 命令時,默認會將每個參數(shù)變量向左移動一個位置,也就是參數(shù) $3 -> $2
    • 類似于 Java 中的迭代器
  2. 而除了參數(shù) $0 ,其他的參數(shù)的位置在被后續(xù)參數(shù)替換后,自身就不復存在了
    • 因為 $0 保存著程序名,所以不會改變
  3. 寫一個例子簡單演示一下,如下圖
    • while 循環(huán)中每次獲取的參數(shù)都是 $1 ,但獲取到的內(nèi)容卻能夠發(fā)生變化
    • 這就是因為每次循環(huán)后都通過 shift 命令把后續(xù)的參數(shù)向左移動了一位

14.3.1 使用 shift 命令移動指定數(shù)量的變量

  1. 使用 shift num 命令可以一次移動多個位置的變量,如下圖

14.3.2 使用 shift 命令移動多個位置時,傳入的參數(shù)必須是位置數(shù)的倍數(shù)

  1. 上從圖可知,使用 shift 2 命令將參數(shù)的位置移動兩位,然后腳本在執(zhí)行的時候傳入了 6 個參數(shù)
  2. 這個時候如果只傳入 5 個參數(shù),那么腳本就會無限循環(huán),如下圖


14.4 處理選項

  1. 執(zhí)行腳本時,除了能往腳本中傳入命令行參數(shù),還能 在單破折號后面跟字母 往腳本中傳遞 選項

14.4.1 查找選項

14.4.1.1 處理簡單選項

  1. 當腳本在執(zhí)行時傳入了選項,通過 case 命令進行判斷是最好的處理方式,如下圖
    • 為了讓傳入的多個選項能在一次執(zhí)行過程中都被執(zhí)行到,需要先使用一個 while + shift 對選項進行遍歷
    • 在每次遍歷時都通過 case 命令對選項的具體參數(shù)進行判斷
    • 同時還通過 *) 提供一個默認判斷

14.4.1.2 分離參數(shù)和選項

  1. 可以通過 雙破折號( -- ) 將參數(shù)和選項進行分離,這樣才執(zhí)行腳本時就可以同時傳入兩者
  2. 這里的說法 不是指直接將參數(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ù)了


  3. 從上述這個例子,其實我們可以思考一下,雙破折號真的是作為一個 強語法的特殊符號 來被 shell 識別的嗎?很顯然不是!
  4. 我們可以對上述腳本稍作修改,就可以得出下圖的效果
    • 將上圖中的 --) 分支判斷部分修改為下圖中的 -e) 分支判斷
    • 然后執(zhí)行上圖中第一次執(zhí)行導致錯誤結(jié)果的語句,卻可以得到和上圖第二次正確結(jié)果一致的輸出
    • 這說明 雙破折號作為參數(shù)和選項分隔符只是一個規(guī)范上的約定,并不是強語法的特殊符號

14.4.1.3 處理帶值的選項

  1. 最復雜的情況就是參數(shù)和選項的混合搭配,但是在 shell 腳本中,當參數(shù)和選項混排后,對應的參數(shù)會被認為是前一個選項的值
  2. 例如 ./option.sh -a str1 -b str2 -c ,這里的 str1 就會被認為是 -a 選項的值,需要在 -a 選項的分支中進行操作
  3. 寫一個簡單的例子演示一下,如下圖
    • -a 后續(xù)的參數(shù)識別之后會執(zhí)行一次 shift 命令的目的是為了將參數(shù)跳過

14.4.2 使用 getopt 命令

14.4.2.1 基礎(chǔ)語法

  1. getopt 命令用于將通過簡化形式傳入腳本的命令行參數(shù)和選項進行規(guī)范化
    • 比如在執(zhí)行腳本時使用 ./option.sh -abc ,如果腳本中存在 getopt 命令,就可以被轉(zhuǎn)換為 -a -b -c
    • 這樣就可以被腳本正確的識別,同時用戶的輸入也得到了簡化
  2. 在腳本執(zhí)行時,會先將這些傳入腳本的命令行參數(shù)和選項自動轉(zhuǎn)換為適當?shù)母袷?,讓腳本在使用這些參數(shù)和選項時更加方便
    • 比如它可以定義哪些字母選項是有效的,哪些字母選項需要指定參數(shù)值
  3. getopt 命令可以在命令行執(zhí)行執(zhí)行,雖然這樣做沒有什么實際意義,但是可以幫助我們熟練一下語法,如下圖
    • ab:cd 就是通過 getopt 命令指定的參數(shù)選項規(guī)則,表示當前可以傳入 abcd 選項,而且選項 b 后面存在一個參數(shù)
    • -ab test -cd 就是實際傳入的參數(shù)內(nèi)容,第二行的輸出結(jié)果就是對這些傳入的內(nèi)容進行格式化后的結(jié)果
  4. 默認如果傳入的內(nèi)容和規(guī)則不匹配,會拋出錯誤,可以通過 -q 選項忽略錯誤,如下圖

14.4.2.2 在腳本中使用

  1. getopt 命令自然是要在腳本中使用才有意義,而且在腳本中使用的語法基本都是固定的
  2. getopt 命令是用來規(guī)范參數(shù)和選項的,所以肯定是要放在腳本的最前端,這個也是毋庸置疑
  3. 一般只需要在腳本前端加上 set -- $(getopt -q ab:cd "$@") 即可
    • ab:cd 是可變化的部分,指的是具體參數(shù)和選項的匹配方式
  4. 寫一個簡單的例子演示一下,如下圖
    • 這個例子是在 centOS 環(huán)境下運行的,因為 macOS 中的 set 命令無法按預期執(zhí)行出效果
  5. 上圖中 case 命令的 --) 分支判斷是必須添加的,否則在執(zhí)行命令時最后一個分支會被多判斷一次
  6. 至于具體是為什么多判斷一次,可以在執(zhí)行腳本前添加 sh -x 來對腳本進行 DEBUG ,如下圖
    • 首先在原來的腳本中注釋掉 --) 分支
    • 然后在與之前相同的腳本執(zhí)行語句之前加上 sh -x ,之后就可以很清楚的看到腳本的執(zhí)行過程
    • 在執(zhí)行過程的第一處可以看到,傳入的 -ab hello -cd 被解析后,最后多出來一個 --
    • 于是在腳本的最后,這個 -- 就被作為傳入的選項執(zhí)行了
    • 這就是為什么如果必須 case 命令添加 --) 分支,并且通過 shift + break 命令跳過的原因

14.4.3 使用更高級的 getopts

  1. getopts 命令是 getopt 命令的復數(shù)版本( 其實說是升級版更貼切 )
    • 關(guān)鍵在于 getopts 可以處理 帶空格和引號的參數(shù) ,而 getopt 不行
  2. getoptsgetopt 更優(yōu)秀的地方在于,每次調(diào)用時,只會處理一個參數(shù),并且在處理完所有參數(shù)后會返回一個非 0 的退出狀態(tài)碼
    • getopt 是將參數(shù)和選項生成為一個完整的輸出
  3. getopts 也可以忽略錯誤信息,只需要在參數(shù)選項規(guī)則之前加上一個冒號即可
  4. 如果某個選項后面需要跟一個參數(shù),這個參數(shù)會被存儲在 OPTARG 變量中
  5. 還有一個變量是 OPTIND ,用于存放 getopts 當前正在處理的參數(shù)位置
  6. 寫一個簡單的例子統(tǒng)一演示一下,如下圖


  7. 可以對比一下兩個命令在處理相同內(nèi)容時的區(qū)別,如下圖
    • 首先,getopts 在處理參數(shù)時可以直接加入到 while 的條件判斷中,這讓語法變的更簡單易懂
    • 其次,getoptscase 判斷也更簡單,每個分支前不需要使用 -a) ,而是直接 a) 即可
    • 用于跳過 -- 選項的分支判斷在 getopts 中也不需要了
    • 最后最關(guān)鍵的是,在 while 的每次循環(huán)結(jié)束之前,不需要使用 shift 命令來對參數(shù)進行位置移動
  8. 如果在參數(shù)中添加空格,getopts 也可以很好的處理,如下圖
  9. 如果在參數(shù)選項中傳入了與定義的規(guī)范不匹配的選項,會被輸出一個問號,如下圖
    • 可以看到,-d 選項因為沒有找到對應的分支判斷,所以被識別為其他選項,但輸出內(nèi)容中可以正確的顯示出來
    • 但是 -e 選項因為不在 getopts 定義的規(guī)范中,所以最后只輸出來一個問號
  10. 如果說選項處理完畢后,還需要處理單獨的參數(shù)列表,則需要在處理參數(shù)之前,使用 shift $[ $OPTIND - 1 ] 來跳過參數(shù)之前的所有選項,如下圖
    • shift $[ $OPTIND - 1 ] 的意思如果不理解的話,這里簡單解釋一下
    • 首先,$[ $OPTIND - 1 ] 的意思是用 getopts 當前執(zhí)行的選項的位置數(shù)減一,為什么要減一,因為命令行參數(shù)的位置從 0 開始的
    • 所以就相當于是使用 shift 將傳入的參數(shù)內(nèi)容中被 getopts 執(zhí)行過的選項全部跳過
  11. 如果還是不理解,可以將上述腳本的 shift $[ $OPTIND - 1 ] 語句注釋后再使用相同命令執(zhí)行,如下圖
    • 可以看到,所有的選項都被當做參數(shù)輸出了


14.5 將選項標準化

  1. 腳本的選項雖然沒有一個強制的語法規(guī)范,但有不少建議性規(guī)范,如果能遵守這些規(guī)范,會讓你編寫的腳本更通用,其他人在上手使用你的腳本時,學習門檻也更低
  2. 下圖中提供一些選項的常用含義


14.6 獲得用戶輸入

14.6.1 使用 read 命令進行基本的讀取

  1. 使用 read 命令可以接收用戶輸入,輸入的內(nèi)容會被放置在后續(xù)的變量中,如下圖
    • echo -n 表示輸入內(nèi)容后不換行
    • 可以看到,通過鍵盤輸入的 asing1elife 被存入到變量 name 中


  2. 使用 read -p 命令可以實現(xiàn)合并輸入前提醒語句的效果,如下圖
    • 少了 echo -n 語句,卻可以達到一樣的輸出效果
  3. 如果想講輸入的內(nèi)容分配給多個變量,只需要將內(nèi)容使用空格進行分隔,并且在 read 命令后使用多個變量進行接收,如下圖
  4. 如果在輸入的時候使用空格分隔了輸入內(nèi)容,但是在 read 命令后沒有使用數(shù)量與之對應的變量分別接收,那么剩余的輸入內(nèi)容都會被直接存放到最后一個變量中,如下圖
    • 可以看到,在腳本 read-p.sh 中,只有一個變量 name 用于接收輸入內(nèi)容
    • 那么所有的輸入內(nèi)容都被存放到這個變量中
    • 在腳本 read-multi.sh 中,輸入內(nèi)容通過逗號分隔后有 6 個,但是變量只有 3 個,所以從第 3 個輸入開始,之后的所有內(nèi)容都被存放到了第三個變量中

  5. 如果不想在輸入命令的最后顯式的指定一個變量用于內(nèi)容接收,也可以直接使用特殊環(huán)境變量 REPLY 來獲取輸入內(nèi)容,如下圖
    • REPLY 變量其實就相當于一個默認的接收變量被隱式的放在了 read 命令的最后,所以會默認接收所有的輸入內(nèi)容

14.6.2 超時

  1. 為了防止用戶一直不輸入,而導致腳本的執(zhí)行流行被阻斷,可以使用 read -t second 來指定一個定時器
  2. 當輸入時間超過指定的時間后,read 命令會返回一個非 0 的退出狀態(tài)碼,如下圖

14.6.2.1 字數(shù)控制

  1. 使用 read -nNum 可以控制輸入的字符數(shù),例如 read -n1 就是當輸入 1 個字符時就開始判斷,如下圖
    • 當輸入一個字符時,不管輸入的是什么,也不需要按回車鍵,腳本都會直接開始分支判斷


14.6.3 隱藏方式讀取

  1. 使用 read -s 命令可以避免輸入的內(nèi)容出現(xiàn)在顯示器上,最典型的例子就是輸入密碼,如下圖
    • 實際上,數(shù)據(jù)還是會顯示,只是文本的顏色被設置成和終端的背景色一致,所以看不出來


14.6.4 從文件中讀取

  1. 使用 read 命令可以讀取文件中的數(shù)據(jù),每次調(diào)用都會讀取一行文本,當文件中沒有剩余內(nèi)容時,會返回非 0 的退出狀態(tài)碼,如下圖
    • 首先使用 cat read-s.sh 讀取文件內(nèi)容
    • 然后通過管道將數(shù)據(jù)直接傳遞給 read 命令
    • read 再將每次讀取的行為通過 while 進行循環(huán)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容