完整版 Linux技巧:使用bash read命令实现一个简易shell( 四 )

这个脚本使用 $# 获取到传入脚本的参数个数 。
如果参数个数不等于 0,那么用 $1 获取到第一个参数值,赋值给 filename 变量 。
这个参数值用于指定要执行的脚本文件名 。
如果没有提供任何参数,那么将 filename 赋值为 /dev/stdin,对应标准输入 。
注意不能将 filename 赋值为空字符串,否则重定向会提示文件找不到 。
重定向空字符串并不表示获取标准输入 。
为了避免所给文件名带有空格导致异常,要用双引号把 $filename 括起来 。
这里采用 bash 的 -i 选项来执行该脚本,所以要在 Linux 本地系统进行测试 。
如果想要用 source 命令来执行,需要做一些修改,包括调整 $#、$1 的使用 。
这里不再提供使用 source 命令来执行的例子 。
执行修改后的脚本,结果如下:
$ ./tinyshell.shtinyshell> lshfiletinyshell.shtinyshell> quit$ cat shfilelecho "This is in a test file."whoami$ ./tinyshell.sh shfileshfiletinyshell.shThis is in a test file.shy这个例子先执行 ./tinyshell.sh 命令,不带参数时,脚本指定从 /dev/stdin 获取输入,可以正常获取到标准输入 。
输入的是 l 字符,脚本执行 ls 命令,列出当前目录下的文件,可以看到有一个 shfile 文件 。
这个 shfile 文件就是要被执行的脚本文件,用 cat shfile 命令列出它的内容,只有三行,每一行都是要执行的命令 。
然后执行 ./tinyshell.sh shfile 命令,从打印结果来看,确实逐行读取到 shfile 文件的内容,并执行每一行的命令 。
Bash 的 whoami 命令会打印当前登录的用户名,这里打印出来是 shy 。
即,使用修改后的 ./tinyshell.sh 来模拟 shell 效果,具有执行脚本文件的能力 。
虽然功能还很弱,但基本框架已经搭好,后续可以根据实际需求进行扩展完善 。
注意:使用上面的 “while read” 循环来逐行读取文件内容,有一个隐晦的异常:如果所给文件的最后一行不是以换行符结尾时,那么这个 “while read” 循环会处理不到最后一行 。具体原因说明如下 。
如果文件的最后一行以换行符结尾,那么 read 命令遇到换行符,会暂停获取输入,并把之前读取到的内容赋值给指定的变量,命令自身的返回值是 0 。
之后 while 命令对这个值进行评估,0 对应 true,执行循环里面的语句,处理最后一行的内容 。
然后再次执行 read 命令,遇到文件结尾 (EOF),read 命令返回非 0 值,对应 false,退出 while 循环 。这是正常的流程 。
如果文件的最后一行不是以换行符结尾,read 读取完这一行内容,遇到了 EOF,会把读取到的内容赋值给指定的变量,命令自身返回值是非 0 值(使用 $? 获取这个返回值,遇到 EOF 应该是返回 1) 。
之后 while 命令对这个非 0 值进行评估,就会退出 while 循环,没有执行循环里面的语句 。
即,这种情况下,虽然 read 命令还是会把最后一行内容赋值给指定变量,但是退出了 while 循环,没有执行循环里面的语句,没有机会处理这一行的内容 。
除非在 while 循环外面再处理一次,但会造成代码冗余 。
下面修改 shfile 文件的内容,最后一行不以换行符结尾,然后执行 ./tinyshell.sh shfile 命令,结果如下:
$ echo -ne "lnwhoami" > shfile$ ./tinyshell.sh shfileshfiletinyshell.sh$ cat shfilelwhoami$这里使用 echo 命令的 -n 选项指定不在行末追加换行符,那么写入文件的最后一行不以换行符结尾 。
可以看到,执行 ./tinyshell.sh shfile 命令,只处理了第一行的 l 字符,第二行的 whoami 没有被执行 。
用 cat shfile 命令查看该文件内容,whoami 跟命令行提示符打印在同一行,确实不以换行符结尾 。
为了避免这个问题,可以在脚本中添加判断,如果所给文件的最后一行不以换行符结尾,则追加一个换行符到文件末尾 。
要添加的代码如下,新增的代码前面用 + 来标识:
if [ $# -ne 0 ]; thenfilename="$1"+if test -n "$(tail "$filename" -c 1)"; then+echo >> "$filename"+fielsefilename="/dev/stdin"fi新增的代码用 tail "$filename" -c 1 命令获取到 filename 文件的最后一个字符 。
"$(tail "$filename" -c 1)" 语句经过命令扩展后返回这个字符 。
如果这个字符是换行符,由于 bash 在扩展后会自动丢弃字符串的最后一个换行符,获取到的内容为空,test -n 返回为 false,不做处理 。
如果最后一个字符不是换行符,那么内容不为空,test -n 返回为 true,就会执行 echo >> "$filename" 命令追加一个换行符到文件末尾 。


推荐阅读