文本界面下指令的连续下达方式、数据流重新导向以及大量的应用题型!
前一节课针对 bash 做简单的变量与环境操作之介绍,本节将针对 bash 环境中常用的连续指令下达方式, 以及数据处理常用的数据流重导向与管线命令进行介绍。这些数据处理的技术对于管理员来说是相当重要的, 尤其在自撰脚本程序分析注册表时,这就显的非常的重要。
某些情况下,用户可能会连续的进行某些指令的下达。但这些指令之间可能会有关连性,例如前一个指令成功后才可进行下一个指令等。 这些情况就需要使用到特殊的字符来处理。
前面的章节我们一直使用到 bash 终端机环境下的指令操作,而且一直接触到很多的单引号、双引号、钱字号、反单引号、大括号、小括号等等, 其实在该成对的符号内,隐藏了:指令、变量、计算式等的功能!再次了解特殊的符号的用途,请完成底下的练习:
接下来,直接应用一下上面的功能:
指令的运行正确与否与后续的处理有关。Linux 环境下缺省的指令正常结束回传值为 0 ,调用的方式为使用『 echo $? 』即可, 亦即找出 ? 这个变量的内容即可。
上述的练习在让用户了解到,指令回传值是每个指令自己指定的,只要符合 bash 的基本规范即可。
指令是可以连续输入的,直接通过分号 (;) 隔开每个指令即可。在没有相依性的指令环境中,可以直接进行如下的行为:
[student@localhost ~]$ date; uptime; uname -r
日 4月 19 15:22:43 CST 2020
15:22:43 up 48 min, 2 users, load average: 0.00, 0.00, 0.00
4.18.0-147.el8.x86_64
如此一口气就可以直接将所有的指令运行完毕,无须考量其他问题。当用户有多个指令需要下达,每个指令又需要比较长的等待时间时, 可以使用这种方式来处理即可。但是,如果想要将这些信息同步输出到同一个文件时,应该如何处理?参考底下两个范例后,说明其差异为何?
请使用底下的两个指令处理数据流,探讨其原因
[student@localhost ~]$ date; uptime; uname -r > myfile.txt [student@localhost ~]$ (date; uptime; uname -r ) > myfile.txt
分号 (;) 是直接连续下达指令,指令间不必有一定程度的相依性。但当指令之间有相依性时,就能够使用 && 或 || 来处理。 这两个处理的方式如下:
[student@localhost ~]$ ls -ld /dev/shm/check ls: 无法访问 '/dev/shm/check': 没有此一文件或目录 # 确认该文件名是不存在的! [student@localhost ~]$ ls -ld /dev/shm/check || mkdir /dev/shm/check ls: 无法访问 '/dev/shm/check': 没有此一文件或目录 [student@localhost ~]$ ls -ld /dev/shm/check drwxrwxr-x. 2 student student 40 4月 19 16:26 /dev/shm/check # 文件出现了!这是因为 mkdir 的缘故! [student@localhost ~]$ ls -ld /dev/shm/check || mkdir /dev/shm/check drwxrwxr-x. 2 student student 40 4月 19 16:26 /dev/shm/check # 再次运作也不会出现 mkdir 的错误! || 会略过正确消息的动作!第一次运行时,由于尚未有该目录,所以显示找不到,但是第二次运行时,就会有该目录,因此 mkdir 的动作就没有进行。 若将中间分隔改为分号 (;) 时,就会产生重复 mkdir 的问题了。因此,比较好的运行方式,还是需要使用 || 较佳!
[student@localhost ~]$ ls -ld /dev/shm/check && rmdir /dev/shm/check drwxrwxr-x. 2 student student 40 4月 19 16:26 /dev/shm/check # 两个指令都分别顺利运作完毕 [student@localhost ~]$ ls -ld /dev/shm/check && rmdir /dev/shm/check ls: 无法访问 '/dev/shm/check': 没有此一文件或目录 # 第一个 ls 的指令失败,因此并没有运行 rmdir 喔!若不信,使用底下指令看看: [student@localhost ~]$ ls -ld /dev/shm/check ; rmdir /dev/shm/check ls: 无法访问 '/dev/shm/check': 没有此一文件或目录 rmdir: failed to remove '/dev/shm/check': 没有此一文件或目录与前一个例题相似,通过 ls 简易的进行搜索的任务,读者也同样会发现两次运行的结果并不相同。 此外,最后一个指令也能够让读者知道, && 与 ; 的差异!
假设我们需要一个指令来说明某个文件名是否存在,可以这样处理:
[student@localhost ~]$ ls -d /etc && echo exist || echo non-exist /etc exist [student@localhost ~]$ ls -d /vbird && echo exist || echo non-exist ls: 无法访问 /vbird: 没有此一文件或目录 non-exist
由于我们只是想要知道该文件是否存在,因此不需要如上表所示,连 ls 的结果也输出。此时可以使用 &> 的方式来将结果输出到垃圾桶,如下所示:
[student@localhost ~]$ ls -d /etc &> /dev/null && echo exist || echo non-exist exist [student@localhost ~]$ ls -d /vbird &> /dev/null && echo exist || echo non-exist non-exist
由于上述指令要修改很麻烦,假设我们需要使用『 checkfile filename 』来处理,此时可以撰写一只小脚本来进行此任务。 若该指令可以让所有用户运行,则可将指令写入 /usr/local/bin 目录内。
[root@localhost ~]# vim /usr/local/bin/checkfile #!/bin/bash ls -d ${1} &> /dev/null && echo "${1} exist" || echo "${1} non-exist" [root@localhost ~]# chmod a+x /usr/local/bin/checkfile [root@localhost ~]# checkfile /etc /etc exist [root@localhost ~]# checkfile /vbird /vbird non-exist
在 checkfile 文件中,第一行 (#!/bin/bash) 代表使用 bash 来运行底下的语法,第二行当中的变量 ${1} 代表在本文件后面所接的第一个参数,因此运行时,就能够直接将要判断的文件名接在 checkfile 后面即可。
事实上,前一小节使用 ls 进行确认文件名时,仅需要确认回传值是否为 0 而已。Linux 提供一个名为 test 的指令可以确认许多文件名参数, 常见的参数有:
测试的标志 | 代表意义 |
1. 关于某个文件名的『文件类型』判断,如 test -e filename 表示存在否 | |
-e | 该『文件名』是否存在?(常用) |
-f | 该『文件名』是否存在且为文件(file)?(常用) |
-d | 该『文件名』是否存在且为目录(directory)?(常用) |
-b | 该『文件名』是否存在且为一个 block device 设备? |
-c | 该『文件名』是否存在且为一个 character device 设备? |
-S | 该『文件名』是否存在且为一个 Socket 文件? |
-p | 该『文件名』是否存在且为一个 FIFO (pipe) 文件? |
-L | 该『文件名』是否存在且为一个链接档? |
2. 关于文件的权限侦测,如 test -r filename 表示可读否 (但 root 权限常有例外) | |
-r | 侦测该文件名是否存在且具有『可读』的权限? |
-w | 侦测该文件名是否存在且具有『可写』的权限? |
-x | 侦测该文件名是否存在且具有『可运行』的权限? |
-u | 侦测该文件名是否存在且具有『SUID』的属性? |
-g | 侦测该文件名是否存在且具有『SGID』的属性? |
-k | 侦测该文件名是否存在且具有『Sticky bit』的属性? |
-s | 侦测该文件名是否存在且为『非空白文件』? |
3. 两个文件之间的比较,如: test file1 -nt file2 | |
-nt | (newer than)判断 file1 是否比 file2 新 |
-ot | (older than)判断 file1 是否比 file2 旧 |
-ef | 判断 file1 与 file2 是否为同一文件,可用在判断 hard link 的判定上。 主要意义在判定,两个文件是否均指向同一个 inode 哩! |
4. 关于两个整数之间的判定,例如 test n1 -eq n2 | |
-eq | 两数值相等 (equal) |
-ne | 两数值不等 (not equal) |
-gt | n1 大于 n2 (greater than) |
-lt | n1 小于 n2 (less than) |
-ge | n1 大于等于 n2 (greater than or equal) |
-le | n1 小于等于 n2 (less than or equal) |
5. 判定字符串的数据 | |
test -z string | 判定字符串是否为 0 ?若 string 为空字符串,则为 true |
test -n string | 判定字符串是否非为 0 ?若 string 为空字符串,则为 false。 注: -n 亦可省略 |
test str1 == str2 | 判定 str1 是否等于 str2 ,若相等,则回传 true |
test str1 != str2 | 判定 str1 是否不等于 str2 ,若相等,则回传 false |
6. 多重条件判定,例如: test -r filename -a -x filename | |
-a | (and)两状况同时成立!例如 test -r file -a -x file,则 file 同时具有 r 与 x 权限时,才回传 true。 |
-o | (or)两状况任何一个成立!例如 test -r file -o -x file,则 file 具有 r 或 x 权限时,就可回传 true。 |
! | 反相状态,如 test ! -x file ,当 file 不具有 x 时,回传 true |
test 仅会回传 $? 而已,屏幕上不会出现任何的变化,因此如果需要取得回应,就需要使用 echo $? 的方式来查找, 或者使用 && 及 || 来处理。
由于 test 是直接加在变量判断之前,读者可能偶而会觉得怪异。此时可以使用中括号 [ ] 来取代 test 的语法。 同样以 checkfile 来处理时,该文件的内容应该需要改写成如下:
[root@localhost ~]# vim /usr/local/bin/checkfile #!/bin/bash [ -e "${1}" ] && echo "${1} exist" || echo "${1} non-exist"
由于中括号的意义非常多,包括第三堂课通配符当中,中括号代表的是『具有一个指定的任意字符』,未来第九堂课的正规表示法当中, 中括号也具有特殊的字符意义。而分辨是否为『判别式』的部份,就是其语法的差别。请注意,在 bash 环境下,使用中括号替代 test 指令时, 中括号的内部需要留白一个以上的空白字符!如下图标:
[ "$HOME" == "$MAIL" ] [□"$HOME"□==□"$MAIL"□] ↑ ↑ ↑ ↑
如果用户想要使用简易的 shell script 创立一个指令,也能够自己设置回传值的意义。
[root@localhost ~]# vim /usr/local/bin/myls.sh #!/bin/bash ls ${@} && exit 100 || exit 10 [root@localhost ~]# chmod a+x /usr/local/bin/myls.sh [student@localhost ~]$ myls.sh /vbird; echo $? ls: 无法访问 '/vbird': 没有此一文件或目录 10
上述的 ${@} 代表指令后面接的任何参数,因此你可以运行 myls.sh 后面接多个参数都没问题。 由于 exit 可以回传消息,因此可以让用户简易的设置好所需要的回传消息规范。
第一堂课开始读者应该就接到到 ls 与 ll 这两个指令,刚开始介绍时,读者们应该知道 ll 是 long list 的缩写。 若将 ll 这个指令用来取代 checkfile 这个脚本,是否可以处理?
[root@localhost ~]# vim /usr/local/bin/checkfile #!/bin/bash #[ -e "${1}" ] && echo "${1} exist" || echo "${1} non-exist" ll -d ${1} && echo "${1} exist" || echo "${1} non-exist" [root@localhost ~]# checkfile /etc /usr/local/bin/checkfile: 列 5: ll:命令找不到 /etc non-exist
但是,当你运行 checkfile /etc 时,竟然出现 command not found 的问题!这是为什么?因为系统上真的没有 ll 这个指令, 该指令为使用命令别名暂时创造出来的一个命令的别称 (别名) 而已。若你在 root 的身份输入 alias 与在 student 的身份输入 alias, 那就会得到两个不同身份的命令别名了:
[root@localhost ~]# alias alias cp='cp -i' alias egrep='egrep --color=auto' alias fgrep='fgrep --color=auto' alias grep='grep --color=auto' alias l.='ls -d .* --color=auto' alias ll='ls -l --color=auto' <==指令别名! alias ls='ls --color=auto' alias mv='mv -i' alias rm='rm -i' ...... [student@localhost ~]$ alias alias cp='cp -i' alias egrep='egrep --color=auto' alias fgrep='fgrep --color=auto' alias grep='grep --color=auto' alias l.='ls -d .* --color=auto' alias ll='ls -l --color=auto' alias ls='ls --color=auto' alias mv='mv -i' alias rm='rm -i' alias vi='vim' <==跟 root 比,多出来的! ......
因此读者应该能知道为何 /bin/ls -d /etc 与 ls -d /etc 输出的结果会有颜色差异的问题了。此外,管理员运行 mv, cp, rm 等管理文件的指令时, 为了避免不小心导致的文件覆盖等问题,于是仅有 root 的身份会加上 -i 的选项,提示管理员相关的文件覆盖问题。 同时,管理员的 vi 与 vim 缺省是不同的软件~一般帐号的 vi 与 vim 则通通是 vim 啊!
让指令不以命令别名的方式运行是相当值得注意的喔!很有趣!
某些时刻管理员可能需要进行一串指令后,再将这串指令进行数据的处理,而非每个指令独自运作。如下列指令的说明:
[student@localhost ~]$ date; cal -3; echo "The following is log" [student@localhost ~]$ date; cal -3; echo "The following is log" > mylog.txt [student@localhost ~]$ cat mylog.txt
读者会发现到,原本想要纪录的信息当中,仅有最后一个指令才可以被处理了。若需要每个指令都进行纪录,依据前面的介绍,则必须要如此处理:
[student@localhost ~]$ date > mylog.txt; cal -3 >> mylog.txt; echo "The following is log" >> mylog.txt
指令会变得相当复杂。此时,可以通过数据统整的方式,亦即将所有的指令包含在小括号内,就能够将消息统一输出了。
[student@localhost ~]$ (date; cal -3; echo "The following is log") > mylog.txt
某些时刻用户可能需要将屏幕的信息转存成为文件以方便纪录,就是前几堂课就已经谈到过得 > 这个符号的功能。 事实上,这个功能就是数据流重导向。
从前一小节的说明,读者可以知道指令运行后,至少可以输出正确与错误的数据 ($? 是否为 0), 某些指令运行时,则会从文件取得数据来处理,例如 cat, more, less 等指令。因此,将指令对于数据的加载与输出汇整如下图:
简单的说,标准输出指的是『指令运行所回传的正确的消息』,而标准错误输出可理解为『 指令运行失败后,所回传的错误消息』。 不管正确或错误的数据都是缺省输出到屏幕上,我们可以通过特殊的字符来进行数据的重新导向!
上述的例题练习完毕后,可以将特殊的字符归类成:
一般来说,文件无法让两个进程同时打开还同时进行读写!因为这样数据内容会反复的被改写掉,所以你不应该使用如下的方式来下达指令:
command > file.txt 2> file.txt
如果你需要将正确数据与错误数据同步写入同一个文件内,那就应该要这样思考:
若同样使用『 find /etc -name '*passwd*' 』这个指令来处理,则读者可以尝试使用底下的方案来运行上述三个数据转管道的动作:
[student@localhost ~]$ find /etc -name '*passwd*' > ~/find_passwd2.txt 2>&1 [student@localhost ~]$ find /etc -name '*passwd*' 2> ~/find_passwd2.txt 1>&2 [student@localhost ~]$ find /etc -name '*passwd*' &> ~/find_passwd2.txt
但请注意指令的输入顺序, 2>&1 与 1>&2 必须要在指令的后面输入才行。
有些指令在运行时,你需要敲击键盘才行,这个 standard input 就可以由文件内容来取代键盘输入的意思。 举例来说, cat 这个指令就是直接让你敲击键盘来由屏幕输出信息。
上面的例题中,我们可以通过 [ctrl]+d 的方式来结束输入,但是一般用户可能会看不懂 [ctrl]+d 代表的意义是什么。 如果能够使用 end 或 eof 等特殊关键字来结束输入,那似乎更为人性化一些。你可以使用底下的指令来处理:
[student@localhost ~]$ cat > yourtype.txt << eof
> here is GoGo!
> eof
最后一行一定要完整的输入 eof (上面的范例),这样就能够结束 cat 的输入,这就是 << 的意义~
读者在前几堂课曾经看过类似『 ll /etc | more 』的指令,较特别的是管线 (pipe, |) 的功能。管线的意思是, 将前一个指令的标准输出作为下一个指令的标准输入来处理!流程有点像底下这样的图标:
另外,这个管线命令『 | 』仅能处理经由前面一个指令传来的正确信息,也就是 standard output 的信息,对于 stdandard error 并没有直接处理的能力。如果你需要处理 standard error output ,就得要搭配 2>&1 这种方式来处理才行了。
常见的管线命令有:
建议用户可以 man wc 看一下该指令的选项功能喔!
作业硬盘一般操作说明:
作业当中,某些部份可能为简答题~若为简答题时,请将答案写入 /home/student/ans.txt 当中,并写好正确题号,方便老师订正答案。 请注意,文件名写错将无法上传!
请使用 root 的身份进行如下实做的任务。直接在系统上面操作,操作成功即可,上传结果的程序会主动找到你的实做结果。
1 r-s--x--- 1 rw-r--r-- 10 rwsr-xr-x 3 rws--x--x 3 rwx------ 4 rwxr-sr-x 241 rwxrwxrwx 8 rwxr-x--- 1633 rwxr-xr-x ...
[student@localhost ~]$ mymsg.sh
Hollo!!
My name is 'Internet Lover'...
My server's kernel version is $kver
I'm a student
bye bye!!
作业结果传输:请以 root 的身分运行 vbird_book_check_unit 指令上传作业结果。 正常运行完毕的结果应会出现【XXXXXX_aa:bb:cc:dd:ee:ff_unitNN】字样。若需要查阅自己上传数据的时间, 请在操作系统上面使用浏览器查找: http://192.168.251.254 检查相对应的课程文件。 相关流程请参考: vbird_book_check_unit