第十章 BASH
1.0)、认识BASH
作用:通过“ Shell ”可以将我们输入的指令与 Kernel 沟通,好让Kernel 可以控制硬件来正确无误的工作!
应用程序其实是在最外层,就如同鸡蛋的外壳一样,因此也就被称呼为壳程序 (shell) 了!其实壳程序的功能只是提供使用者操作系统的一个接口,因此这个壳程序需要可以调用其他软件才好。
Bash:Bourne Again SHell,第一个流行的 shell 是由 Steven Bourne 发展出来的,为了纪念他所以就称为 Bourne shell ,或直接简称为 sh。
bash 主要的优点
- 命令编修能力 (history):能够记忆使用过的指令,在~/.bash_history中记录;
- 命令与文件补全功能: ([tab])按键的好处: 命令补全,文件补齐,参数补齐;
- 命令别名设置功能: (alias)
- 工作控制、前景背景控制: (job control, foreground, background)
- 程序化脚本: (shell scripts)
- 万用字符: (Wildcard)
l 指令的下达和快速编辑
- 指令过长时,可输入“\[enter]”来逃脱[enter],使得在下一行也可以继续输入命令;
- 在指令输入错误时,可以使用[ctrl]+u/[ctrl]+k 来分别快速删除光标前/后的指令字符;
- 当想要将光标快速移动到整个指令的前端/后端时,需要[ctrl]+a/[ctrl]+e;
2.0)、认识Shell
l 变量:变量就是以一组文字或符号等,来取代一些设置或者是一串保留的数据,如环境变量(PATH,HOME)等。
显示变量:echo $[变量名称] 或 echo ${[变量名称]},如echo $HOME 或者是 echo ${HOME}。
在 bash 当中,当一个变量名称尚未被设置时,默认的内容是“空”的。
[gjm@study ~]$ echo ${myname}
<==这里并没有任何数据~因为这个变量尚未被设置!是空的!
[gjm@study ~]$ myname=GJM
[gjm@study ~]$ echo ${myname}
GJM <==出现了!因为这个变量已经被设置了!
l 特殊符号的使用
单引号和双引号:最大不同在于双引号仍然可以保有变量的内容,但单引号内仅能是一般字符 ,而不会有特殊符号。
举例:
[dmtsai@study ~]$ name=VBird
[dmtsai@study ~]$ echo $name
VBird
[dmtsai@study ~]$ myname="$name its me"
[dmtsai@study ~]$ echo $myname
VBird its me
[dmtsai@study ~]$ myname='$name its me'
[dmtsai@study ~]$ echo $myname
$name its me
发现了吗?没错!使用了单引号的时候,那么 $name 将失去原有的变量内容,仅为一般字符的显示型态而已。
反单引号(`):键盘左上角Esc按键下方
范例六:如何进入到您目前核心的模块目录?
[dmtsai@study ~]$ cd /lib/modules/`uname -r`/kernel
[dmtsai@study ~]$ cd /lib/modules/$(uname -r)/kernel # 以此例较佳
实例:若你有一个常去的工作目录名称为:“/cluster/server/work/*_2015/003/”,如何进行该目录的简化?
[dmtsai@study ~]$ work="/cluster/server/work/*_2015/003/" [dmtsai@study ~]$ cd $work 未来我想要使用其他目录作为我的模式工作目录时,只要变更 work 这个变量即可!而这个变量又可以在 bash 的配置文件(~/.bashrc)中直接指定,那我每次登陆只要执行“ cd $work ”就能够去到数值模式仿真的工作目录了!
l 自订变量和环境变量
两者之间的最大差异是: 该变量是否会被子程序所继续引用。
指令的下达行为:当你登陆 Linux 并取得一个 bash 之后,你的 bash 就是一个独立的程序,这个程序的识别使用的是一个称为程序识别码,被称为 PID 。 接下来你在这个 bash 下面所下达的任何指令都是由这个 bash 所衍生出来的,那些被下达的指令就被称为子程序。
而我们在原本的 bash 下面执行另一个 bash ,结果操作的环境接口会跑到第二个 bash 去(就是子程序), 那原本的 bash 就会在暂停的情况 (睡着了,就是 sleep)。若要回到原本的 bash 去, 就只有将第二个 bash 结束掉 (下达 exit 或 logout) 才行。
子程序仅会继承父程序的环境变量, 而不会继承父程序的自订变量。类似于编码中的全局变量和局部变量概念。
为了将自订变量转化为环境变量,可以通过export指令。
2.1)、变量键盘读取、阵列和宣告:read,array,declare
l 要读取来自键盘输入的变量,就是用 read 这个指令.
[dmtsai@study ~]$ read [-pt] variable 选项与参数: -p :后面可以接提示字符! -t :后面可以接等待的“秒数!”
范例1:
范例1:提示使用者 30 秒内输入自己的大名,将该输入字串作为名为 named 的变量内容 [dmtsai@study ~]$ read -p "Please keyin your name: " -t 30 named Please keyin your name: VBird Tsai <==注意看,会有提示字符喔! [dmtsai@study ~]$ echo ${named} VBird Tsai <==输入的数据又变成一个变量的内容了!
和typeset 是一样的功能,就是在“宣告变量的类型”。
l declare
[dmtsai@study ~]$ declare [-aixr] variable 选项与参数:
-a :将后面名为 variable 的变量定义成为阵列 (array) 类型
-i :将后面名为 variable 的变量定义成为整数数字 (integer) 类型
-x :用法与 export 一样,就是将后面的 variable 变成环境变量;
-r :将变量设置成为 readonly 类型,该变量不可被更改内容,也不能 unset
范例一:让变量 sum 进行 100+300+50 的加总结果
[dmtsai@study ~]$ sum=100+300+50
[dmtsai@study ~]$ echo ${sum}
100+300+50 <==咦!怎么没有帮我计算加总?因为这是文字体态的变量属性啊!
[dmtsai@study ~]$ declare -i sum=100+300+50
[dmtsai@study ~]$ echo ${sum}
450 <==瞭乎??
bash对变量有默认的定义:
- 变量类型默认为“字串”,所以若不指定变量类型,则 1+2 为一个“字串”而不是“计算式”。 所以上述第一个执行的结果才会出现那个情况的;
- bash 环境中的数值运算,默认最多仅能到达整数形态,所以 1/3 结果是 0;
故:如果需要非字串类型的变量,那就得要进行变量的宣告才行。
l 阵列:array
和数组一个概念,用法也是和数据结构里的数组相同。
范例:设置上面提到的 var[1] ~ var[3] 的变量。
[dmtsai@study ~]$ var[1]="small min"
[dmtsai@study ~]$ var[2]="big min"
[dmtsai@study ~]$ var[3]="nice min"
[dmtsai@study ~]$ echo "${var[1]}, ${var[2]}, ${var[3]}"
small min, big min, nice min
2.2)、变量内容的删除、取代和替换(Optional)
变量设置方式说明 |
|
${变量#关键字} ${变量##关键字} |
若变量内容从头开始的数据符合“关键字”,则将符合的最短数据删除 若变量内容从头开始的数据符合“关键字”,则将符合的最短数据删除 |
${变量%关键字} ${变量%%关键字} |
若变量内容从尾向前的数据符合“关键字”,则将符合的最短数据删除 若变量内容从尾向前的数据符合“关键字”,则将符合的最长数据删除 |
${变量/旧字串/新字串} ${变量//旧字串/新字串} |
若变量内容符合“旧字串”则“第一个旧字串会被新字串取代” 若变量内容符合“旧字串”则“全部的旧字串会被新字串取代” |
2.3)、历史命令(history)
[dmtsai@study ~]$ !number
[dmtsai@study ~]$ !command
[dmtsai@study ~]$ !!
选项与参数:
number :执行第几笔指令的意思;
command :由最近的指令向前搜寻“指令串开头为 command”的那个指令,并执行;
!! :就是执行上一个指令(相当于按↑按键后,按 Enter)
[dmtsai@study ~]$ history
66 man rm
67 alias
68 man history
69 history
[dmtsai@study ~]$ !66 <==执行第 66 笔指令
[dmtsai@study ~]$ !! <==执行上一个指令,本例中亦即 !66
[dmtsai@study ~]$ !al <==执行最近以 al 为开头的指令(上头列出的第 67 个)
3.0)、路径与指令搜寻顺序
指令运行的顺序可以这样看:
1. 以相对/绝对路径执行指令,例如“ /bin/ls ”或“ ./ls ”;
2. 由 alias 找到该指令来执行;
3. 由 bash 内置的 (builtin) 指令来执行;
4. 通过 $PATH 这个变量的顺序搜寻到的第一个指令来执行。
举例:
例题:
设置 echo 的命令别名成为 echo -n ,然后再观察 echo 执行的顺序
答:
[dmtsai@study ~]$ alias echo='echo -n' //设置别名
[dmtsai@study ~]$ type -a echo //查询echo指令搜寻顺序
echo is aliased to `echo -n' //1
echo is a shell builtin //2
echo is /usr/bin/echo //3
瞧!很清楚吧!先 alias 再 builtin 再由 $PATH 找到 /bin/echo 啰!
3.1)、登录信息显示
在/etc/issue内配置
[dmtsai@study ~]$ cat /etc/issue
\S
Kernel \r on an \m
各个缩写的意义:
issue 内的各代码意义
\d 本地端时间的日期;
\l 显示第几个终端机接口;
\m 显示硬件的等级 (i386/i486/i586/i686...);
\n 显示主机的网络名称;
\O 显示 domain name;
\r 操作系统的版本 (相当于 uname -r)
\t 显示本地端时间的时间;
\S 操作系统的名称;
\v 操作系统的版本。
例题:
例题:
如果你在 tty3 的进站画面看到如下显示,该如何设置才能得到如下画面?
CentOS Linux 7 (Core) (terminal: tty3)
Date: 2015-07-08 17:29:19
Kernel 3.10.0-229.el7.x86_64 on an x86_64
Welcome! 解答:
\S (terminal: \l)
Date: \d \t
Kernel \r on an \m
Welcome!
如果您想要让使用者登陆后取得一些讯息,例如您想要让大家都知道的讯息, 那么可以将讯息加入 /etc/motd 里面去,一定要用 root 的身份才能修改喔!类似于公告栏。
3.2)、终端机的环境设置stty
注:如果出现 ^ 表示[Ctrl] 那个按键的意思。
问:
因为鸟哥的工作经常在 Windows/Linux 之间切换,在 windows 下面,很多软件默认的储存快捷按钮是 [crtl]+s ,所以鸟哥习惯按这个按钮来处理。 不过,在 Linux 下面使用 vim 时,却也经常不小心就按下 [crtl]+s !问题来了,按下这个组合钮之后,整个vim 就不能动了 (整个画面锁死)! 请问鸟哥该如何处置? 答:
参考一下 stty -a 的输出中,有个 stop 的项目就是按下 [crtl]+s 的!那么恢复成 start 就是 [crtl]+q 啊!因此, 尝试按下 [crtl]+q 应该就可以让整个画面重新恢复正常咯!
3.3)、bash 默认的组合键给他汇整
组合按键 |
执行结果 |
Ctrl + C |
终止目前的命令 |
Ctrl + D |
输入结束 (EOF),例如邮件结束的时候; |
Ctrl + M |
就是 Enter 啦! |
Ctrl + S |
暂停屏幕的输出 |
Ctrl + Q |
恢复屏幕的输出 |
Ctrl + U |
在提示字符下,将整列命令删除 |
Ctrl + Z |
“暂停”目前的命令 |
3.4)、万用字符和特殊符号
实例:
[dmtsai@study ~]$ LANG=C <==由于与编码有关,先设置语系一下
范例一:找出 /etc/ 下面以 cron 为开头的文件名
[dmtsai@study ~]$ ll -d /etc/cron* <==加上 -d 是为了仅显示目录而已
范例二:找出 /etc/ 下面文件名“刚好是五个字母”的文件名
[dmtsai@study ~]$ ll -d /etc/????? <==由于 ? 一定有一个,所以五个 ? 就对了
范例三:找出 /etc/ 下面文件名含有数字的文件名
[dmtsai@study ~]$ ll -d /etc/*[0-9]* <==记得中括号左右两边均需 */
范例四:找出 /etc/ 下面,文件名开头非为小写字母的文件名:
[dmtsai@study ~]$ ll -d /etc/[^a-z]* <==注意中括号左边没有 *
范例五:将范例四找到的文件复制到 /tmp/upper 中
[dmtsai@study ~]$ mkdir /tmp/upper; cp -a /etc/[^a-z]* /tmp/upper
3.5)、数据流重导向redirect:>,>>,<,<<,tee
数据流重导向就是将某个指令执行后应该要出现在屏幕上的数据,给他传输到其他的地方。
指令执行过程的数据传输情况
l 标准输入输出
标准输出指的是“指令执行所回传的正确的讯息”;
标准错误输出可理解为“指令执行失败后,所回传的错误讯息”。
分别传送所用的特殊字符则如下所示:
1. 标准输入 (stdin) :代码为 0 ,使用 < 或 << ;
2. 标准输出 (stdout):代码为 1 ,使用 > 或 >> 或1> 或1>>;1可省略;
3. 标准错误输出(stderr):代码为 2 ,使用 2> 或 2>> ;
若以“>” 输出到一个文件中,该文件若不存在,系统会自动的将他创建起来;若已存在,则那个文件就会被覆盖掉。如果不想被覆盖掉原内容,则使用“>>”。
实例:
范例1:利用一般身份帐号搜寻 /home 下面是否有名为 .bashrc 的文件存在
[dmtsai@study ~]$ find /home -name .bashrc <==身份是 dmtsai 喔!
find: '/home/arod': Permission denied <== Standard error output
find: '/home/alex': Permission denied <== Standard error output
/home/dmtsai/.bashrc <== Standard output
那么如何将error和正确信息分开呢?可以利用上述功能:
范例2:承范例1,将 stdout 与 stderr 分存到不同的文件去
[dmtsai@study ~]$ find /home -name .bashrc > list_right 2> list_error
此时“屏幕上不会出现任何讯息”!因为刚刚执行的结果中,有 Permission 的那几行错误信息都会跑到 list_error 这个文件中,至于正确的输出数据则会存到 list_right 这个文件。
tee 可以让 standard output 转存一份到文件内并将同样的数据继续送到屏幕去处理!tee 这家伙在很多选择/填充的认证考试中很容易考呢!
l /dev/null 垃圾桶黑洞设备
范例3:承范例2,将错误的数据丢弃,屏幕上显示正确的数据
[dmtsai@study ~]$ find /home -name .bashrc 2> /dev/null
/home/dmtsai/.bashrc <==只有 stdout 会显示到屏幕上, stderr 被丢弃了
/dev/null会吃掉一切。
l 标准输入:<<
“<<”代表的意思是“结束的输入字符串”
例子:
[dmtsai@study ~]$ cat > catfile << "eof"
> This is a test.
> OK now stop
> eof <==输入这关键字,立刻就结束而不需要输入 [ctrl]+d
[dmtsai@study ~]$ cat catfile
This is a test.
OK now stop <==只有这两行,不会存在关键字那一行!
这个操作对程序编写非常有利,就像知道了循环的结束条件一样。
那么,在什么情况下需要用到上面这些神奇操作呢?
- 屏幕输出的信息很重要,而且我们需要将他存下来的时候;
- 背景执行中的程序,不希望他干扰屏幕正常的输出结果时;
- 一些系统的例行命令 (例如写在 /etc/crontab 中的文件) 的执行结果,希望他可以存下来时;
- 一些执行命令的可能已知错误讯息时,想以“ 2> /dev/null ”将他丢掉时;
- 错误讯息与正确讯息需要分别输出时。
l ?:(关于上个执行指令的回传值)
若前一个指令执行的结果为正确,在 Linux 下面会回传一个 $? = 0 的值。
多条指令连续输入有两种方式处理:
- 在指令与指令中间利用分号 (;) 来隔开;
- 撰写shell script 脚本去执行;
l “&&”及“||”:
综上可发现:“&&”和“||”是互为相反的。其实和Java中的“&&”和“||”是一个道理的。
3.6)、管线命令“|”
这个管线命令“ | ”仅能处理经由前面一个指令传来的正确信息,也就是 standard output 的信
息,对于 stdandard error 并没有直接处理的能力。
管线命令的处理示意图
在每个管线后面接的第一个数据必定是“指令”喔!而且这个指令必须要能够接受 standard input 的数据才行,这样的指令才可以是为“管线命令”,例如 less, more, head, tail 等都是可以接受 standard input 的管线命令啦。至于例如 ls, cp, mv 等就不是管线命令了!因为 ls, cp, mv
并不会接受来自 stdin 的数据。
[dmtsai@study ~]$ cut -d'分隔字符' -f fields <==用于有特定分隔字符
[dmtsai@study ~]$ cut -c 字符区间 <==用于排列整齐的讯息
选项与参数:
-d :后面接分隔字符。与 -f 一起使用;
-f :依据 -d 的分隔字符将一段讯息分区成为数段,用 -f 取出第几段的意思;
-c :以字符 (characters) 的单位取出固定字符区间;
范例一:将 PATH 变量取出,我要找出第五个路径。
[dmtsai@study ~]$ echo ${PATH}
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin
# 1 | 2 | 3 | 4 | 5 | 6 |
[dmtsai@study ~]$ echo ${PATH} | cut -d ':' -f 5
# 如同上面的数字显示,我们是以“ : ”作为分隔,因此会出现 /home/dmtsai/.local/bin
# 那么如果想要列出第 3 与第 5 呢?,就是这样:
[dmtsai@study ~]$ echo ${PATH} | cut -d ':' -f 3,5
范例二:将 export 输出的讯息,取得第 12 字符以后的所有字串
[dmtsai@study ~]$ export
declare -x HISTCONTROL="ignoredups"
declare -x HISTSIZE="1000"
declare -x HOME="/home/dmtsai"
declare -x HOSTNAME="study.centos.vbird"
.....(其他省略).....
# 注意看,每个数据都是排列整齐的输出!如果我们不想要“ declare -x ”时,就得这么做:
[dmtsai@study ~]$ export | cut -c 12-
HISTCONTROL="ignoredups"
HISTSIZE="1000"
HOME="/home/dmtsai"
HOSTNAME="study.centos.vbird"
.....(其他省略).....
# 知道怎么回事了吧?用 -c 可以处理比较具有格式的输出数据!
# 我们还可以指定某个范围的值,例如第 12-20 的字符,就是 cut -c 12-20 等等!
范例三:用 last 将显示的登陆者的信息中,仅留下使用者大名
[dmtsai@study ~]$ last
root pts/1 192.168.201.101 Sat Feb 7 12:35 still logged in
root pts/1 192.168.201.101 Fri Feb 6 12:13 - 18:46 (06:33)
root pts/1 192.168.201.254 Thu Feb 5 22:37 - 23:53 (01:16)
# last 可以输出“帐号/终端机/来源/日期时间”的数据,并且是排列整齐的
[dmtsai@study ~]$ last | cut -d ' ' -f 1
# 由输出的结果我们可以发现第一个空白分隔的字段代表帐号,所以使用如上指令:
管线命令在 bash 的连续的处理程序中是相当重要的!另外,在 log file 的分析当中也是相当重要的一环, 所以请特别留意。
3.7)、查找命令:grep
[dmtsai@study ~]$ grep [-acinv] '搜寻字串' filename
选项与参数:
-a :将 binary 文件以 text 文件的方式搜寻数据
-c :计算找到 '搜寻字串' 的次数
-i :忽略大小写的不同,所以大小写视为相同
-n :顺便输出行号
-v :反向选择,亦即显示出没有 '搜寻字串' 内容的那一行! 范例一:将 last 当中,有出现 root 的那一行就取出来;
[dmtsai@study ~]$ last | grep 'root'
范例二:与范例一相反,只要没有 root 的就取出!
[dmtsai@study ~]$ last | grep -v 'root'
grep 可以解析一行文字,取得关键字,若该行有存在关键字,就会整行列出来!此外,grep还有其他用处,暂不列出。
3.8)、字符转换命令: ttr,, coll,, jjoiin,, passtte,, expand
3.9)、分区命令: split
3.10)、参数代换: xargs
重点回顾
l 由于核心在内存中是受保护的区块,因此我们必须要通过“ Shell ”将我们输入的指令与 Kernel 沟通,好让 Kernel 可以控制硬件来正确无误的工作。
l 学习 shell 的原因主要有:文本界面的 shell 在各大 distribution 都一样;远端管理时文本界面速度较快; shell 是管理 Linux 系统非常重要的一环,因为 Linux 内很多控制都是以 shell 撰写的。
l 系统合法的 shell 均写在 /etc/shells 文件中;
l 使用者默认登陆取得的 shell 记录于 /etc/passwd 的最后一个字段;
l bash 的功能主要有:命令编修能力;命令与文件补全功能;命令别名设置功能;工作控制、前景背景控制;程序化脚本;万用字符type 可以用来找到执行指令为何种类型,亦可用于与 which 相同的功能;
l 变量就是以一组文字或符号等,来取代一些设置或者是一串保留的数据
l 变量主要有环境变量与自订变量,或称为全域变量与区域变量
l 使用 env 与 export 可观察环境变量,其中 export 可以将自订变量转成环境变量;
l set 可以观察目前 bash 环境下的所有变量;
l $? 亦为变量,是前一个指令执行完毕后的回传值。在 Linux 回传值为 0 代表执行成功;
l locale 可用于观察语系数据;
l 可用 read 让使用者由键盘输入变量的值ulimit 可用以限制使用者使用系统的资源情况
l bash 的配置文件主要分为 login shell 与 non-login shell。login shell 主要读取 /etc/profile 与 ~/.bash_profile, non-login shell 则仅读取~/.bashrc
l 在使用 vim 时,若不小心按了 [crtl]+s 则画面会被冻结。你可以使用 [ctrl]+q 来解除冻结
l 万用字符主要有: *, ?, [] 等等
l 数据流重导向通过 >, 2>, < 之类的符号将输出的信息转到其他文件或设备去;
l 连续命令的下达可通过 ; && || 等符号来处理
l 管线命令的重点是:“管线命令仅会处理 standard output,对于 standard error output 会予以忽略” “管线命令必须要能够接受来自前一个指令的数据成为 standard input 继续处理才行。”
l 本章介绍的管线命令主要有:cut, grep, sort, wc, uniq, tee, tr, col, join, paste, expand, split, xargs 等。
Over...