亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.430618.com 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747


Linux技巧:使用bash read命令實(shí)現(xiàn)一個(gè)簡(jiǎn)易shell(完整版)

 

在 linux 上面,可以使用 bash 的 read 內(nèi)置命令來(lái)讀取用戶輸入。

當(dāng)在 while 循環(huán)中不斷調(diào)用 read 命令,并打印一些提示字符,如 $、#、> 等,就可以不斷接收用戶輸入,并執(zhí)行一些自定義的命令,類似一個(gè)簡(jiǎn)易的 shell。

下面主要是介紹 read 命令的常見用法,用來(lái)逐步實(shí)現(xiàn)一個(gè)簡(jiǎn)易的 shell 效果。

read 命令介紹

在 bash 中,read 內(nèi)置命令可以讀取用戶的一行輸入,并對(duì)輸入內(nèi)容進(jìn)行單詞拆分,依次賦值給指定的變量。

查看 help read 的說明如下:

read: read [-ers] [-a array] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ...]

Read a line from the standard input and split it into fields.

 

Reads a single line from the standard input, or from file descriptor FD if the -u option is supplied. The line is split into fields as with word splitting, and the first word is assigned to the first NAME, the second word to the second NAME, and so on, with any leftover words assigned to the last NAME.

 

Only the characters found in $IFS are recognized as word delimiters.

If no NAMEs are supplied, the line read is stored in the REPLY variable.

 

Exit Status:

The return code is zero, unless end-of-file is encountered, read times out (in which case it's greater than 128), a variable assignment error occurs, or an invalid file descriptor is supplied as the argument to -u.

即,read 命令從標(biāo)準(zhǔn)輸入讀取到一行,每行內(nèi)容以換行符結(jié)尾,但是讀取到的內(nèi)容不包含行末的換行符。

對(duì)于讀取到的內(nèi)容,會(huì)按照 bash 的 IFS 全局變量保存的分割字符把輸入行拆分成多個(gè)單詞,把這些詞依次賦值給提供的變量,如果所給的變量個(gè)數(shù)少于分割后的單詞數(shù),最后一個(gè)變量被賦值為剩余的所有單詞。

舉例說明如下:

$ read first second third last
1 2 3 4 5 678
$ echo $first, $second, $third, $last
1, 2, 3, 4 5 678
$ read input_line
This is a test input line.
$ echo $input_line
This is a test input line.

可以看到,默認(rèn)基于空格來(lái)拆分單詞。

所給的第一個(gè) first 變量被賦值為拆分后的第一個(gè)單詞。

第二個(gè) second 變量被賦值為拆分后的第二個(gè)單詞。

第三個(gè) third 變量被賦值為拆分后的第三個(gè)單詞。

最后一個(gè) last 變量被賦值為第三個(gè)單詞后的所有單詞。

顯然,只提供一個(gè)變量時(shí),整個(gè)輸入行都會(huì)賦值給這個(gè)變量,打印的 input_line 變量值可以看到這一點(diǎn)。

使用 -p 選項(xiàng)指定提示字符串

執(zhí)行 read 命令時(shí),默認(rèn)不打印提示字符串。

如果想要引導(dǎo)用戶輸入特定的內(nèi)容,可以使用 -p 選項(xiàng)來(lái)指定提示字符串。

查看 help read 對(duì)該選項(xiàng)的說明如下:

-p prompt

output the string PROMPT without a trailing newline before attempting to read

即,在 -p 選項(xiàng)后面跟著一個(gè) prompt 字符串。在讀取用戶輸入之前,會(huì)先打印這個(gè) prompt 字符串,以作提示。

這個(gè)提示字符串后面不會(huì)換行,會(huì)跟用戶的輸入在同一行。

具體舉例說明如下:

$ read -p "Please input your mood today: " mood
Please input your mood today: hAppy
$ echo $mood
happy

在執(zhí)行所給的 read 命令時(shí),會(huì)先打印 “Please input your mood today: ”字符串,沒有換行,等待用戶輸入。

上面的 “happy” 是輸入的字符串,會(huì)被賦值給指定 mood 變量。

當(dāng)使用 while 循環(huán)不斷調(diào)用 read 命令,且用 -p 選項(xiàng)指定 $ 字符時(shí),看起來(lái)就像是一個(gè)簡(jiǎn)易的 shell,可以根據(jù)用戶輸入做一些處理,也可以指定其他字符,如 >、# 等

假設(shè)有一個(gè) tinyshell.sh 腳本,內(nèi)容如下:

#!/bin/bash

while read -p "tinyshell> " input; do
    if [ "$input" == "l" ]; then
        ls
    elif [ "$input" == "quit" ]; then
        break
    else
        echo "Unknown input: $input"
    fi
done

該腳本在 while 循環(huán)中不斷調(diào)用 read 命令,使用 -p 選項(xiàng)設(shè)置提示字符串為 “tinyshell> ”,DOS 命令行的提示字符就是 >。

具體執(zhí)行結(jié)果如下:

$ ./tinyshell.sh
tinyshell> l
tinyshell.sh
tinyshell> d
Unknown input: d
tinyshell> quit
$

在執(zhí)行時(shí),先打印出 “tinyshell> ” 提示字符串,等待用戶輸入。

這里輸入 l 字符,腳本會(huì)執(zhí)行 ls 命令。

輸入 quit 字符串,會(huì)退出 while 循環(huán),終止執(zhí)行。

輸入其他內(nèi)容則提示 “Unknown input: ”。

在實(shí)際工作中,對(duì)這個(gè)例子進(jìn)行擴(kuò)展,就能模擬一個(gè)簡(jiǎn)易的 shell 效果,可以輸入自定義的命令簡(jiǎn)寫,來(lái)執(zhí)行一長(zhǎng)串的命令,非常方便

例如進(jìn)行 Android 系統(tǒng)開發(fā),經(jīng)常用到 adb shell 的各種命令,有些命令帶有很多參數(shù),比較難輸入,仿照這個(gè)例子,可以只輸入一個(gè)字符、或者幾個(gè)字符,然后執(zhí)行對(duì)應(yīng)的 adb shell 命令,減少很多輸入。

使用 -e 選項(xiàng)在交互式 shell 中獲取到歷史命令

前面提到在 while 循環(huán)中不斷執(zhí)行 read -p 命令,可以模擬一個(gè)簡(jiǎn)易的 shell 效果。

實(shí)際使用時(shí)遇到一個(gè)問題,那就是輸入上光標(biāo)鍵,會(huì)打印 ^[[A,輸入下光標(biāo)鍵,會(huì)打印 ^[[B,不能像 bash 那樣通過上下光標(biāo)鍵顯示執(zhí)行過的歷史命令。

具體執(zhí)行結(jié)果如下:

$ ./tinyshell.sh
tinyshell> ^[[A^[[B

這里打印的 ^[[A 是輸入上光標(biāo)鍵所顯示,^[[B 是輸入下光標(biāo)鍵所顯示。

如果想要在執(zhí)行 read 命令時(shí),可以通過上下光標(biāo)鍵來(lái)顯示歷史命令,需要加上 -e 選項(xiàng),且在交互式 shell 中運(yùn)行。

查看 help read 對(duì) -e 選項(xiàng)說明如下:

-e

use Readline to obtain the line in an interactive shell

即,在交互式 shell 中,read -e 會(huì)使用 readline 庫(kù)來(lái)獲取輸入。

readline 庫(kù)支持很多強(qiáng)大的功能,上下光標(biāo)鍵能夠顯示歷史命令,就是因?yàn)槟J(rèn)把上下光標(biāo)鍵綁定到 readline 庫(kù)獲取上下歷史命令的函數(shù)。

可以執(zhí)行下面的命令來(lái)進(jìn)行確認(rèn):

$ bind -p | grep -E "previous-history|next-history"
"C-n": next-history
"eOB": next-history
"e[B": next-history
"C-p": previous-history
"eOA": previous-history
"e[A": previous-history

這里的 "e[A" 就是對(duì)應(yīng)上光標(biāo)鍵,綁定到 previous-history 功能,也就是顯示上一個(gè)歷史命令。

"e[B" 對(duì)應(yīng)下光標(biāo)鍵,綁定到 next_history 功能,也就是顯示下一個(gè)歷史命令。

上面的 "C-p" 對(duì)應(yīng) CTRL-p,也就是同時(shí)按下 CTRL 鍵和 p 鍵,可以看到它也對(duì)應(yīng)上一個(gè)歷史命令。

"C-n" 對(duì)應(yīng) CTRL-n,對(duì)應(yīng)下一個(gè)歷史命令。

一般來(lái)說,直接在 bash shell 中執(zhí)行 read 命令,就處于交互式 shell(interactive shell)之下。

具體舉例說明如下:

$ read
^[[A^[[B
$ read -e
read -e

這個(gè)例子先是直接執(zhí)行 read 命令,然后輸入上光標(biāo)鍵,會(huì)打印 ^[[A,然后輸入下光標(biāo)鍵,又打印 ^[[B。

之后,執(zhí)行 read -e 命令,輸入上光標(biāo)鍵,會(huì)自動(dòng)填充上一個(gè)歷史命令,也就是正在執(zhí)行的 “read -e” 命令。

注意:這個(gè) -e 選項(xiàng)只在交互式 shell 中才會(huì)生效。一般來(lái)說,shell 腳本是在非交互式 shell 中執(zhí)行。

當(dāng)在 shell 腳本中使用 read -e 時(shí),輸入上下光標(biāo)鍵,不會(huì)再打印 ^[[A、^[[B,也不會(huì)顯示歷史命令,而是什么都沒有打印。

這跟 read -p 的效果有所不同,read -p 可以在輸入上下光標(biāo)鍵時(shí),打印出 ^[[A、^[[B。

讓 shell 腳本運(yùn)行在交互模式下

我們可以使用下面幾個(gè)方法來(lái)讓 shell 腳本在交互模式下執(zhí)行。

通過 bash -i 選項(xiàng)指定運(yùn)行在交互模式下

在 bash 中,可以使用 bash 的 -i 選項(xiàng)來(lái)讓 shell 腳本在交互模式下運(yùn)行。

查看 man bash 對(duì) -i 選項(xiàng)說明如下:

-i

If the -i option is present, the shell is interactive.

即,在 shell 腳本開頭,把腳本的解釋器寫為 #/bin/bash -i。執(zhí)行這個(gè) shell 腳本時(shí),就會(huì)運(yùn)行在交互模式下。

把前面的 tinyshell.sh 腳本修改成下面的內(nèi)容來(lái)進(jìn)行驗(yàn)證:

#!/bin/bash -i

while read -ep "tinyshell> " input; do
    if [ "$input" == "l" ]; then
        ls
    elif [ "$input" == "quit" ]; then
        break
    else
        echo "Unknown input: $input"
   fi
done

相比于之前的腳本,這次的改動(dòng)點(diǎn)是:

  • 把之前的 #!/bin/bash 改成 #!/bin/bash -i,添加 -i 選項(xiàng)指定運(yùn)行在交互模式下。
  • 把 read -p 改成 read -ep,添加 -e 選項(xiàng)指定在交互模式下用 readline 庫(kù)讀取用戶輸入。

執(zhí)行修改后的腳本,結(jié)果如下:

$ ./tinyshell.sh
tinyshell> #!/bin/bash -i
Unknown input: #!/bin/bash -i
tinyshell>

上面的在 “tinyshell>” 之后顯示的 “#!/bin/bash -i” 是輸入兩次上光標(biāo)鍵后顯示出來(lái)的歷史命令。第一個(gè)輸入光標(biāo)鍵會(huì)顯示腳本里面的整個(gè) while 循環(huán)語(yǔ)句。

注意:這個(gè)腳本在 Linux Debian 系統(tǒng)、 Linux Ubuntu 系統(tǒng)本地測(cè)試都能生效,可以通過上下光標(biāo)鍵顯示出歷史命令。

但是在 windows 下通過 ssh 遠(yuǎn)程登錄到 Ubuntu 系統(tǒng),在遠(yuǎn)程 Ununtu 系統(tǒng)下執(zhí)行這個(gè)腳本不生效,即使把腳本開頭的解釋器寫為 #!/bin/bash -i,read -e 命令也無(wú)法通過上下光標(biāo)鍵讀取到歷史命令,輸入上下光標(biāo)鍵,什么都沒有打印出來(lái)。

在 mac OSX 系統(tǒng)下測(cè)試也不生效。

這幾種情況都是在 login shell 下運(yùn)行,查看 readline 庫(kù)的配置文件也沒有看到異常,目前原因不明。

可以改成用 source 命令執(zhí)行腳本來(lái)避免這個(gè)異常。具體如后面說明所示。

通過 source 命令執(zhí)行 shell 腳本

通過 bash 的 source 內(nèi)置命令執(zhí)行 shell 腳本時(shí),這個(gè)腳本運(yùn)行在當(dāng)前 bash shell 下,而不是啟動(dòng)一個(gè)子 shell 來(lái)執(zhí)行腳本。

由于當(dāng)前 bash shell 是交互式,運(yùn)行在該 bash shell 下的腳本也是交互式。

此時(shí),腳本開頭的解釋器不需要加 -i 選項(xiàng),但 read 命令還是要加 -e 選項(xiàng)來(lái)指定用 readline 庫(kù)讀取輸入。

修改 tinyshell.sh 腳本內(nèi)容如下:

#!/bin/bash

while read -ep "tinyshell> " input; do
    if [ "$input" == "l" ]; then
        ls
    elif [ "$input" == "quit" ]; then
        break
    else
        bash -c "${input}"
    fi
done

這個(gè)腳本的改動(dòng)點(diǎn)是:

  • 腳本開頭的解釋器寫為 #!/bin/bash,不需要加 -i 選項(xiàng)。
  • read 命令加了 -e 選項(xiàng)。
  • 對(duì)于不識(shí)別的輸入,使用 bash -c 來(lái)執(zhí)行所輸入的內(nèi)容,這樣就可以執(zhí)行外部的命令。

使用 source 命令執(zhí)行這個(gè)腳本的結(jié)果如下:

$ source tinyshell.sh
tinyshell> l
tinyshell.sh
tinyshell> echo "This is a tinyshell."
This is a tinyshell.
tinyshell> source tinyshell.sh
tinyshell> quit
tinyshell> quit
$

在執(zhí)行的時(shí)候,先是手動(dòng)輸入 l 字符,該腳本會(huì)相應(yīng)執(zhí)行 ls 命令。

然后手動(dòng)輸入 echo "This is a tinyshell.",該腳本使用 bash -c 來(lái)執(zhí)行這個(gè)命令,打印出 This is a tinyshell.。

接著輸入上光標(biāo)鍵,出現(xiàn)上一個(gè)歷史命令,顯示當(dāng)前正在執(zhí)行的 source tinyshell.sh 命令。

回車之后會(huì)再次執(zhí)行這個(gè)腳本。

可以看到,需要手動(dòng)輸入兩次 quit,才退出這兩次執(zhí)行。

上面提到,在 Windows 下通過 ssh 遠(yuǎn)程登錄到 Ubuntu 系統(tǒng),在遠(yuǎn)程 Ununtu 系統(tǒng)下,使用 bash 的 -i 選項(xiàng)來(lái)執(zhí)行腳本,read -e 也不能通過上下光標(biāo)鍵來(lái)獲取歷史命令。

此時(shí),通過 source 命令執(zhí)行腳本,read -e 命令能通過上下光標(biāo)鍵來(lái)獲取歷史命令。

即,通過 bash 的 -i 選項(xiàng)來(lái)執(zhí)行腳本,可能會(huì)受到子 shell 環(huán)境配置的影響,導(dǎo)致 read -e 命令不能通過上下光標(biāo)鍵來(lái)獲取歷史。

而通過 source 命令來(lái)執(zhí)行腳本,直接運(yùn)行在當(dāng)前 bash shell 下,可以避免子 shell 環(huán)境配置的影響,兼容性較強(qiáng)。

注意:通過 source 命令執(zhí)行腳本時(shí),腳本內(nèi)不能執(zhí)行 exit 命令,否則不但會(huì)退出腳本執(zhí)行,還會(huì)退出所在的 bash shell。

把腳本自身執(zhí)行的命令添加到當(dāng)前歷史記錄

在前面的腳本代碼中,無(wú)論是通過 bash 的 -i 選項(xiàng)來(lái)執(zhí)行腳本,還是通過 source 命令來(lái)執(zhí)行腳本,這兩種方式有一個(gè)共同的問題:雖然可以使用上下光標(biāo)鍵查找歷史命令,但找不到腳本自身所執(zhí)行的命令。

例如輸入 l 字符,tinyshell.sh 腳本執(zhí)行了 ls 命令。

通過上光標(biāo)鍵還是只能查找到執(zhí)行腳本之前的歷史命令,查找不到輸入的 l 字符,也找不到腳本所執(zhí)行的 ls 命令,就像是這個(gè)腳本的命令沒有加入到歷史記錄。

如果想在執(zhí)行腳本時(shí),可以使用上下光標(biāo)鍵查找到腳本自身執(zhí)行的命令,可以使用 history -s 命令。

在上面 while 循環(huán)的末尾添加下面的語(yǔ)句,新增的代碼前面用 + 來(lái)標(biāo)識(shí):

    else
        bash -c "${input}"
    fi
+   history -s "${input}"
done

添加 history -s "${input}" 語(yǔ)句后,就能通過上下光標(biāo)鍵找到 input 變量指定的命令。

例如輸入 l 字符,tinyshell.sh 腳本執(zhí)行了 ls 命令。

而 input 變量保存的是 l 字符,能夠通過上下光標(biāo)鍵找到 l 命令,找不到 ls 命令。

查看 man bash 對(duì) history 內(nèi)置命令的 -s 選項(xiàng)說明如下:

history -s arg [arg ...]

-s: Store the args in the history list as a single entry.

即,history -s 命令把所給的參數(shù)添加到當(dāng)前歷史記錄中。

后續(xù)通過上下光標(biāo)鍵獲取歷史命令,就可以獲取到新添加的命令。

從文件中逐行讀取命令并執(zhí)行

既然是模擬一個(gè)簡(jiǎn)易的 shell 效果,當(dāng)然要具有執(zhí)行腳本文件的能力。

我們可以通過重定向用 read 命令逐行讀取文件內(nèi)容,然后執(zhí)行每一行的命令。一段示例代碼如下:

while read line; do
    echo $line
done < filename

這段代碼會(huì)逐行讀取 fliename 這個(gè)文件的內(nèi)容,讀取到最后一行 (EOF) 就會(huì)退出 while 循環(huán)。

參考這段代碼,對(duì) tinyshell.sh 腳本修改如下:

#!/bin/bash -i

if [ $# -ne 0 ]; then
    filename="$1"
else
    filename="/dev/stdin"
fi

while read -ep "tinyshell> " input; do
    if [ "$input" == "l" ]; then
        ls
    elif [ "$input" == "quit" ]; then
        break
    else
        bash -c "$input"
    fi
done < "$filename"

這個(gè)腳本使用 $# 獲取到傳入腳本的參數(shù)個(gè)數(shù)。

如果參數(shù)個(gè)數(shù)不等于 0,那么用 $1 獲取到第一個(gè)參數(shù)值,賦值給 filename 變量。

這個(gè)參數(shù)值用于指定要執(zhí)行的腳本文件名。

如果沒有提供任何參數(shù),那么將 filename 賦值為 /dev/stdin,對(duì)應(yīng)標(biāo)準(zhǔn)輸入。

注意不能將 filename 賦值為空字符串,否則重定向會(huì)提示文件找不到。

重定向空字符串并不表示獲取標(biāo)準(zhǔn)輸入。

為了避免所給文件名帶有空格導(dǎo)致異常,要用雙引號(hào)把 $filename 括起來(lái)。

這里采用 bash 的 -i 選項(xiàng)來(lái)執(zhí)行該腳本,所以要在 Linux 本地系統(tǒng)進(jìn)行測(cè)試。

如果想要用 source 命令來(lái)執(zhí)行,需要做一些修改,包括調(diào)整 $#、$1 的使用。

這里不再提供使用 source 命令來(lái)執(zhí)行的例子。

執(zhí)行修改后的腳本,結(jié)果如下:

$ ./tinyshell.sh
tinyshell> l
shfile  tinyshell.sh
tinyshell> quit
$ cat shfile
l
echo "This is in a test file."
whoami
$ ./tinyshell.sh shfile
shfile  tinyshell.sh
This is in a test file.
shy

這個(gè)例子先執(zhí)行 ./tinyshell.sh 命令,不帶參數(shù)時(shí),腳本指定從 /dev/stdin 獲取輸入,可以正常獲取到標(biāo)準(zhǔn)輸入。

輸入的是 l 字符,腳本執(zhí)行 ls 命令,列出當(dāng)前目錄下的文件,可以看到有一個(gè) shfile 文件。

這個(gè) shfile 文件就是要被執(zhí)行的腳本文件,用 cat shfile 命令列出它的內(nèi)容,只有三行,每一行都是要執(zhí)行的命令。

然后執(zhí)行 ./tinyshell.sh shfile 命令,從打印結(jié)果來(lái)看,確實(shí)逐行讀取到 shfile 文件的內(nèi)容,并執(zhí)行每一行的命令。

Bash 的 whoami 命令會(huì)打印當(dāng)前登錄的用戶名,這里打印出來(lái)是 shy。

即,使用修改后的 ./tinyshell.sh 來(lái)模擬 shell 效果,具有執(zhí)行腳本文件的能力。

雖然功能還很弱,但基本框架已經(jīng)搭好,后續(xù)可以根據(jù)實(shí)際需求進(jìn)行擴(kuò)展完善。

注意:使用上面的 “while read” 循環(huán)來(lái)逐行讀取文件內(nèi)容,有一個(gè)隱晦的異常:如果所給文件的最后一行不是以換行符結(jié)尾時(shí),那么這個(gè) “while read” 循環(huán)會(huì)處理不到最后一行。具體原因說明如下。

如果文件的最后一行以換行符結(jié)尾,那么 read 命令遇到換行符,會(huì)暫停獲取輸入,并把之前讀取到的內(nèi)容賦值給指定的變量,命令自身的返回值是 0。

之后 while 命令對(duì)這個(gè)值進(jìn)行評(píng)估,0 對(duì)應(yīng) true,執(zhí)行循環(huán)里面的語(yǔ)句,處理最后一行的內(nèi)容。

然后再次執(zhí)行 read 命令,遇到文件結(jié)尾 (EOF),read 命令返回非 0 值,對(duì)應(yīng) false,退出 while 循環(huán)。這是正常的流程。

如果文件的最后一行不是以換行符結(jié)尾,read 讀取完這一行內(nèi)容,遇到了 EOF,會(huì)把讀取到的內(nèi)容賦值給指定的變量,命令自身返回值是非 0 值(使用 $? 獲取這個(gè)返回值,遇到 EOF 應(yīng)該是返回 1)。

之后 while 命令對(duì)這個(gè)非 0 值進(jìn)行評(píng)估,就會(huì)退出 while 循環(huán),沒有執(zhí)行循環(huán)里面的語(yǔ)句。

即,這種情況下,雖然 read 命令還是會(huì)把最后一行內(nèi)容賦值給指定變量,但是退出了 while 循環(huán),沒有執(zhí)行循環(huán)里面的語(yǔ)句,沒有機(jī)會(huì)處理這一行的內(nèi)容。

除非在 while 循環(huán)外面再處理一次,但會(huì)造成代碼冗余。

下面修改 shfile 文件的內(nèi)容,最后一行不以換行符結(jié)尾,然后執(zhí)行 ./tinyshell.sh shfile 命令,結(jié)果如下:

$ echo -ne "lnwhoami" > shfile
$ ./tinyshell.sh shfile
shfile  tinyshell.sh
$ cat shfile
l
whoami$

這里使用 echo 命令的 -n 選項(xiàng)指定不在行末追加換行符,那么寫入文件的最后一行不以換行符結(jié)尾。

可以看到,執(zhí)行 ./tinyshell.sh shfile 命令,只處理了第一行的 l 字符,第二行的 whoami 沒有被執(zhí)行。

用 cat shfile 命令查看該文件內(nèi)容,whoami 跟命令行提示符打印在同一行,確實(shí)不以換行符結(jié)尾。

為了避免這個(gè)問題,可以在腳本中添加判斷,如果所給文件的最后一行不以換行符結(jié)尾,則追加一個(gè)換行符到文件末尾

要添加的代碼如下,新增的代碼前面用 + 來(lái)標(biāo)識(shí):

if [ $# -ne 0 ]; then
    filename="$1"
+    if test -n "$(tail "$filename" -c 1)"; then
+        echo >> "$filename"
+    fi
else
    filename="/dev/stdin"
fi

新增的代碼用 tail "$filename" -c 1 命令獲取到 filename 文件的最后一個(gè)字符。

"$(tail "$filename" -c 1)" 語(yǔ)句經(jīng)過命令擴(kuò)展后返回這個(gè)字符。

如果這個(gè)字符是換行符,由于 bash 在擴(kuò)展后會(huì)自動(dòng)丟棄字符串的最后一個(gè)換行符,獲取到的內(nèi)容為空,test -n 返回為 false,不做處理。

如果最后一個(gè)字符不是換行符,那么內(nèi)容不為空,test -n 返回為 true,就會(huì)執(zhí)行 echo >> "$filename" 命令追加一個(gè)換行符到文件末尾。

echo 不帶參數(shù)時(shí),默認(rèn)輸出一個(gè)換行符, >> 表示追加內(nèi)容到文件末尾。

添加這幾個(gè)語(yǔ)句后,再執(zhí)行 ./tinyshell.sh shfile 命令,就能處理到最后一行的 whoami,如下所示:

$ ./tinyshell.sh shfile
shfile  tinyshell.sh
shy
$ cat shfile
l
whoami
$

可以看到,執(zhí)行之后,shfile 文件的最后一行 whoami 后面被追加了一個(gè)換行符,輸出該文件內(nèi)容,命令行提示符會(huì)換行打印。

通常來(lái)說,在 Windows 下復(fù)制內(nèi)容到新建文件,然后保存這個(gè)文件,文件的最后一行可能就不以換行符結(jié)尾。

使用 -s 選項(xiàng)指定不回顯用戶輸入

在 bash 下,輸入密碼時(shí),一般不會(huì)回顯用戶輸入,而是什么都不顯示。我們可以使用 read 命令的 -s 選項(xiàng)模擬這個(gè)效果。

查看 help read 對(duì) -s 選項(xiàng)的說明如下:

-s

do not echo input coming from a terminal

具體舉例如下:

$ read -s -p "Your input will not echo: " input
Your input will not echo: $ echo $input
sure?

這個(gè)例子指定了 -s 選項(xiàng),不回顯輸入內(nèi)容到終端。

用 -p 指定了提示字符串,輸入內(nèi)容會(huì)被保存到 input 變量。

在輸入的時(shí)候,界面上不會(huì)顯示任何字符。

回車之后,命令行提示符直接顯示在同一行,由于沒有回顯換行符,所以沒有換行。

打印 input 變量的值,可以看到手動(dòng)輸入的內(nèi)容是 “sure?”。

如果需要在模擬的簡(jiǎn)易 shell 中輸入密碼,可以添加類似下面的代碼,讓輸入密碼時(shí)不回顯,新增的代碼前面用 + 來(lái)標(biāo)識(shí):

    elif [ "$input" == "quit" ]; then
        break
+    elif [ "$input" == "root" ]; then
+        read -s -p "Please input your password: " pwd
+        # handle password with $pwd
+        echo
+        echo "Your are root now."
    else
        bash -c "${input}"
    fi

這里添加了對(duì) root 字符串的處理,先執(zhí)行 read -s -p "Please input your password: " pwd 命令,提示讓用戶輸入密碼。

輸入的內(nèi)容不會(huì)回顯,會(huì)保存在 pwd 變量中,可以根據(jù)實(shí)際需要進(jìn)行處理。

新增的第一個(gè) echo 命令用于從 "Please input your password: " 字符串后面換行,否則會(huì)直接輸出到同一行上。

第二個(gè) echo 命令只是打印一個(gè)提示語(yǔ),可以根據(jù)實(shí)際需求改成對(duì)應(yīng)的提示。

使用 -n 選項(xiàng)指定讀取多少個(gè)字符

執(zhí)行 read 命令讀取標(biāo)準(zhǔn)輸入,會(huì)不停讀取輸入內(nèi)容,直到遇到換行符為止。

如果我們預(yù)期最多只讀取幾個(gè)字符,可以使用 -n 選項(xiàng)來(lái)指定。

查看 help read 對(duì) -n 選項(xiàng)說明如下:

-n nchars

return after reading NCHARS characters rather than waiting for a newline, but honor a delimiter if fewer than NCHARS characters are read before the delimiter

即,read -n nchars 指定最多只讀取 nchars 個(gè)字符。

輸入 nchars 個(gè)字符后,即使還沒有遇到換行符,read 也會(huì)停止讀取輸入,返回讀取到的內(nèi)容。

如果在輸入 nchar 個(gè)字符之前,就遇到換行符,也會(huì)停止讀取輸入。

使用 -n 選項(xiàng)并不表示一定要讀取到 nchars 個(gè)字符。

另外一個(gè) -N 選項(xiàng)表示一定要讀取到 nchars 個(gè)字符。這里對(duì) -N 選項(xiàng)不做說明。

下面會(huì)在模擬的簡(jiǎn)易 shell 中實(shí)現(xiàn)一個(gè)小游戲,增加一點(diǎn)趣味性。

這個(gè)小游戲使用 read -n 1 來(lái)指定每次只讀取一個(gè)字符,以便輸入字符就立刻停止讀取,不需要再按回車。

具體實(shí)現(xiàn)代碼如下,這也是 tinyshell.sh 腳本最終版的代碼:

#!/bin/bash -i

if [ $# -ne 0 ]; then
    filename="$1"
    if test -n "$(tail "$filename" -c 1)"; then
        echo >> "$filename"
    fi
else
    filename="/dev/stdin"
fi

function game()
{
    local count=0
    local T="T->"

    echo -e "NOW, ATTACK! $T"
    while read -s -n 1 char; do
        case $char in
            "h") ((--count)) ;;
            "l") ((++count)) ;;
            "q") break ;;
        esac

        for ((i = 0; i < count; ++i)); do
            echo -n "    "
        done
        echo -ne "$T      r"
    done
    echo
}

while read -ep "tinyshell> " input; do
    if [ "$input" == "l" ]; then
        ls
    elif [ "$input" == "quit" ]; then
        break
    elif [ "$input" == "root" ]; then
        read -s -p "Please input your password: " pwd
        # handle with $pwd
        echo
        echo "Your are root now."
    elif [ "$input" == "game" ]; then
        game
    else
        bash -c "${input}"
    fi
    history -s "${input}"
done < "$filename"

主要改動(dòng)是增加對(duì) game 字符串的處理,輸入這個(gè)字符串,會(huì)執(zhí)行自定義的 game 函數(shù)。

該函數(shù)打印 T-> 字符串,像是一把劍(也許吧),然后用 read -s -n 1 char 命令指定每次只讀取一個(gè)字符,且不回顯。

如果輸入 l 字符,則把 T-> 字符串的顯示位置往右移。

輸入 h 字符,則把 T-> 字符串的顯示位置往左移。

看起來(lái)是一個(gè)左右移動(dòng)的效果。

輸入 q 字符,退出該游戲。

具體執(zhí)行結(jié)果如下:

$ ./tinyshell.sh
tinyshell> game
NOW, ATTACK! T->
        T->
tinyshell>

由于沒有回顯輸入字符,且始終在同一行顯示 T-> 字符串,所以這個(gè)打印結(jié)果體現(xiàn)不出 T-> 字符串的移動(dòng),可以實(shí)際執(zhí)行這個(gè)腳本,多次輸入 l 、h 字符,就能看到具體效果,最后輸入 q 字符退出游戲。

總結(jié)

至此,我們已經(jīng)使用 read 命令來(lái)獲取用戶輸入,模擬了一個(gè)簡(jiǎn)易的 shell 效果。

這個(gè)簡(jiǎn)易的 shell 可以執(zhí)行腳本文件,可以通過上下光標(biāo)鍵獲取到 bash 的歷史命令,支持輸入密碼不回顯,還實(shí)現(xiàn)了一個(gè)小游戲。

總結(jié) read 命令的使用關(guān)鍵點(diǎn)如下:

  • 使用 -p 選項(xiàng)來(lái)打印提示字符,模擬 shell 的命令行提示符
  • 使用 -e 選項(xiàng)在交互式 shell 中用 readline 庫(kù)讀取輸入,可以避免輸入上下光標(biāo)鍵顯示亂碼
  • 使用 -s 選項(xiàng)指定不回顯輸入內(nèi)容,可用于輸入密碼、輸入游戲控制按鍵等情況
  • 使用 -n 1 選項(xiàng)指定只讀取一個(gè)字符,輸入字符立刻結(jié)束讀取,可以在游戲中快速響應(yīng)按鍵,不用按下回車才能響應(yīng)
  • 使用 “while read” 循環(huán)來(lái)重定向讀取文件,可以逐行讀取文件內(nèi)容,執(zhí)行相應(yīng)命令,就像是執(zhí)行腳本文件

分享到:
標(biāo)簽:bash read
用戶無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定