bash shell
我们知道,操作系统其实就是一组软件,由于这组软件在控制整个硬件与管理系统的活动监测,如果这组软件被用户随意操作,若用户操作不当,将会使得整个系统崩溃。
但是系统总还是需要用户操作系统的,所有就有了在操作系统上面发展的应用程序。用户可以通过应用程序来指挥内核,达到我们的目的。其实 shell 的功能只是提供用户操作系统的一个接口,因此这个 shell 需要可以调用其它软件才好。
Tips:也就是说,只要能操作应用程序的借口能够被称之为 shell。狭义的 shell 指的是命令行方面的软件,包括本章要介绍的 bash 等。广义的 shell 则包括图形界面的软件,因为图形界面也能够操作各种应用程序。
bash 的内置命令:type
通过 type 命令,我们可以知道这个命令是外部命令(非 bash 所提供的命令)或是内置在 bash 当中的。比如:
[root@mars ~]# type [-tpa] name
选项与参数:
type:不加任何参数,会显示 name 是外部命令还是内部
-t:当加入 -t 参数时,type 会将 name 以下面这些字显示出它的意义:
file:表示为外部命令
alias:表示该命令为命令别名所设置的名称
builtin:表示该命令为内置命令
-p:如果后面接的 name 为外部命令时,才会显示完整的文件名
-a:会由 PATH 设置的路径中,将所有含 name 的命令都列出来,包含alias
范例1:查询 ls 是否为内置
[root@mars ~]# type ls
ls is aliased to `ls --color=auto' <==列出ls最主要的使用情况
[root@mars ~]# type -t ls
alias <==仅列出ls执行时的依据
[root@mars ~]# type -a ls
ls is aliased to `ls --color=auto' <==最先使用 aliase
ls is /usr/bin/ls <==还有找到外部命令在/bin/ls
范例2:那么 cd 呢?
[root@mars ~]# type cd
cd is a shell builtin
shell 的变量
变量就是以一组文字或符号等,来替代一些设置或者是一串保留的数据。比如我们常说的 $PATH 变量,我们之所在任何目录下都可以执行 ls 命令,都是因为 ls 这个执行文件所在的目录/usr/bin 在PATH里面,可以被随时检测到。
我们使用echo就可以看到变量:
[root@mars ~]# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
变量的设置规则
1.变量与变量内容以一个等号“=”来连接
2.等号两边不能直接接空格符,如下所示为错误的
myname = VBird 或 myname=VBird Tsai
3.变量名称只能是英文字母与数字,但是开头字符不能是数字,以下为错误:
2myname=VBird
4.变量内容若有空格符可使用双引号或者单引号将变量内容结合起来,但
- 双引号内的特殊字符如 $ 等,可以保留原本的特性,如下所示:
var = “lang is $LANG” 则 echo \$var 可得 lang is en_US
- 单引号内的特殊字符则为一般字符(纯文本),如下:
var=’lang si $LANG’ 则 echo \$var 可得 lang is \$LAGN
5.可用转义字符“\”将特殊符号(如Enter、$、\、空格等)变成一般字符
6.在一串命令中,还需要通过其他的命令提供的信息,可以使用反单引号“ `命令` ”或“ $(命令)”。注意,是键盘数字1 键旁边的那个键,不是单引号。例如湖区内核版本的设置:
version=$(uname -r)
再echo $version
可得“2.6.18-128.el5”7.若该变量为了增加变量内容时,则可用“$ 变量名称”或 \${变量} 累加内容,如下所示:
PATH=”$PATH”:/home/bin
8.若该变量需要在其他子进程执行,则需要以 export 来使变量变成环境变量
export PATH
9.通常大写字符为系统默认变量,自行设置变量可以使用小写字符,方便判断
10.取消变量的方法为使用“unset 变量名称”
具体设置内容以及注意项,如下所示:
范例1:设置一个变量 name,且内容为 VBird's name
[root@mars ~]# name=VBird's name
# 单引号与双引号必须要成对,在上面的设置中仅有一个单引号,因此当你按下
# Enter后,你还可以继续输入变量内容。这与我们需要的功能不同,按Ctrl+C结束
[root@mars ~]# name="VBird's name" <==正确
[root@mars ~]# name='VBird's name' <==单引号不成对,失败
[root@mars ~]# name=VBird\'s\ name <==转义字符也行
范例2:在PATH变量中累加 /home/dmtsai/bin 这个目录
[root@mars ~]# PATH=$PATH:/home/dmtsai/bin
[root@mars ~]# PATH="$PATH":/home/dmtsai/bin
[root@mars ~]# PATH=${PATH}:/home/dmtsai/bin
# 三种方式都可以
范例3:如果要将 name 的内容多出“yes”呢
[root@mars ~]# name=$nameyes
# echo 发现为空白,如果没有双引号,那么变量就变成了$nameyes这个的内容
# 但是我们没有设置 nameyes 这个变量,自然为空
[root@mars ~]# name="$name"yes
[root@mars ~]# name=${name}yes <==两种方式都可以
范例4:如何让刚刚的 name 可以用在下个 shell 的程序
[root@mars ~]# bash <==进入子进程
[root@mars ~]# echo $name
<==什么都没有
[root@mars ~]# exit <==离开子进程
[root@mars ~]# export name
[root@mars ~]# bash
[root@mars ~]# echo $name
VBird's name <==出现了
[root@mars ~]# exit <==退出
什么是子进程呢?也就是说在目前的这个 shell 的情况下,再去打开另一个新的 shell,新的那个 shell 就是子进程。一般情况下,父进程的自定义变量是无法在子进程内使用的。但是通过 export 将变量变成环境变量后,就可以了。
Tips:那么在命令执行中,反单引号( `)这个符号有什么用呢?
比如,我想知道每个 crontab 相关文件名的权限,就可以这样做:ls -l `locate crontab`
变量的有效范围
之前我们提到父进程定义的变量不能被子进程所得知,除非使用 export ,现在我们就来说说父进程与子进程的概念:
子进程仅会集成父进程的环境变量,子进程不会继承父进程的自定义变量。所以我们才需要 export,有的地方也提到全局变量和局部变量,我们大致可以认为。环境变量就是全局变量,自定义变量就是局部变量。
变量键盘读取、数组与声明
读取:read
范例1:让用户由键盘输入内容,将该内容变成名为 atest 的变量
[root@mars ~]# read atest
This is a test. <==此时光标就在等待你输入
[root@mars ~]# echo $atest
This is a test. <==刚刚输入的内容已经变成了一个变量
declare / typeset
declare / typeset 是一样的功能,就是声明变量的类型。如果使用 declare 后面并没有接任何参数,那么 bash 会主动列出所有的变量名称和内容,和 set 一样。
[root@mars ~]# declare [-aixr] variable
选项与参数:
-a:将后面名为 variable 的变量定义为数组(array)类型
-i:将后面名为 variable 的变量定义为整数数字(integer)类型
-x:与 export 相同
-r:将变量设置成为 readonly 类型,该变量不可被改变内容,也不能重设
范例1:让 sum 进行 100+300+50 累加结果
[root@mars ~]# sum=100+300+50
[root@mars ~]# echo $sum
100+300+50 <==并没有帮我们进行计算,因为这是文字类型的变量
[root@mars ~]# declare -i sum=100+300+50
[root@mars ~]# echo $sum
450 <==得到了正确的结果
变量默认为字符串,所以不指定变量类型,则 1 + 2 为一个字符串而不是计算式。bash中的数值运算,默认最多仅能到达整数类型,所以1 /3 结果是 0。
范例2:让 sum 变成非环境变量的自定义变量(以用declare -xr sum 变为了环境变量和只读)
[root@mars ~]# declare +x sum <==将 - 变成 + 可以进行取消操作
[root@mars ~]# declare -p sum <== -p 可以单独列出变量类型
declare -ir sum="450" <==只剩下了i,r类型
有趣的是,如果你不小心设置了某个变量的只读属性,你只有注销再登录才能复原该变量的类型。
数组:array
在 bash 中,数组的设置方式是 var[index] = content,目前bash提供的是一维数组:
#设置一个数组
[root@mars ~]# var[1]="small min"
[root@mars ~]# var[2]="big min"
[root@mars ~]# var[3]="nice min"
[root@mars ~]# echo "${var[1]},${var[2]},${var[3]}"
small min,big min,nice min
#数组的读取建议用${数组}的方式来读取
变量内容的删除、替代与替换
变量内容的删除
#先让小写的 path 设置得与 PATH 内容相同,便于实验
[root@mars ~]# path=${PATH}
[root@mars ~]# echo $path
/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
范例1:假设我不喜欢 kerberos,如何操作
[root@mars ~]# echo ${path#/*kerberos/bin:}
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
上面的这个例子,我们可以详细地来剖析一下它:
${variable#/*kerberos/bin:}
这个 $ 是关键字,用在删除模式是必须的
${variable#/*kerberos/bin:}
这个变量原本的名称,比如我们这里写的 path
${variable #/*kerberos/bin:}
这就是重点,代表从变量内容的最前面开始向右删除,且仅删除最短的那个
${variable#/*kerberos/bin:}
代表要被删除的部分,由于 # 代表由开头开始删除,所有这里由开始的 / 写起
* 通配符,一直匹配到/kerberos/bin为止
从上面的范例我们可以看出,path这个变量被删除的内容如下所示:/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
接下来继续看 # 的功能:
范例2:删除前面的目录,保留后面的一个
[root@mars ~]# echo ${path#/*:}
/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
#由于一个 # 仅删除最短的那个
用删除线来看是这样:/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
[root@mars ~]# echo ${path##/*:}
/root/bin
#一个 # 变成 ## 之后,它变成了删除最长的那个
用删除线来看是这样:/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
所以我们可以发现删除的规则:
- #:符合替换文字的“最短的”那一个
- ##:符合替换文字的“最长的”那一个
前面提到的是从前向后,那么从后向前删除呢?其实就使用百分比(%)符号,就可以从后往前删除了,规则和 # 是一致的。
变量内容的替换
变量的替换也很好理解:
范例3:将 path 的变量 sbin 替换成大写的 SBIN
[root@mars ~]# echo ${path/sbin/SBIN}
/usr/kerberos/SBIN:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
#关键在与两个斜线,两个斜线中间的是旧字符
#后面的是新字符,所以结果就出现了上述的情况(只有一个SBIN被修改)
[root@mars ~]# echo ${path//sbin/SBIN}
/usr/kerberos/SBIN:/usr/kerberos/bin:/usr/local/SBIN:/usr/local/bin:/usr/SBIN:/usr/bin:/root/bin
#如果是两条斜线,那么所有的符合条件的都会被替换
总结一下前面的各项:
变量设置方式 | 说明 |
---|---|
${变量#关键字} | 若变量从头开始的数据符合“关键字”,则将符合的最短数据删除 |
${变量##关键字} | 若变量从头开始的数据符合“关键字”,则将符合的最长数据删除 |
${变量%关键字} | 若变量从尾向前的数据符合“关键字”,则将符合的最短数据删除 |
${变量%%关键字} | 若变量从尾向前的数据符合“关键字”,则将符合的最长数据删除 |
${变量/旧字符/新字符串} | 若变量内容符合“旧字符串”,则第一个旧字符川会被替换 |
${变量//旧字符/新字符串} | 若变量内容符合“旧字符串”,则全部的旧字符川会被替换 |
变量的测试与内容替换
范例1:测试一下是否存在 username 这个变量,若不存在则赋值内容为 root
[root@mars ~]# echo $username
<==由于出现空白,所以 username 可能不存在,也可能是空字符串
[root@mars ~]# username=${username-root}
[root@mars ~]# echo $username
root <==因为 username 没有设置,所以主动赋给root的内容
[root@mars ~]# username="mars name" <==主动设置内容
[root@mars ~]# username=${username-root}
[root@mars ~]# echo $username
mars name <==因为已经设置过,所有不会用root替代
上面的范例中,重点在于减号“ - ”后面接的关键字,我们可以这样理解:
new_var=${old_var_content}
新的变量,主要用来替代旧变量
new_var=${old_var_content}
这是本例中的关键字,必须要存在
new_var=${old_var-content}
旧的变量,被测试的选项
new_var=${old_var-content}
变量的内容,在本例中,是给未给予变量的内容
不过这还是有点问题,因为 username 可能被设置为空字符串。这样的话,你还可以通过这种方式:
范例2:若 username 未设置或为空字符串,则将 usernmae 内容设置为 root
[root@mars ~]# username=""
[root@mars ~]# username=${username-root}
[root@mars ~]# echo $username
<==因为 username 被设置为空字符串,所以还是保留
[root@mars ~]# username=${username:-root}
[root@mars ~]# echo $username
root <==加上“:”后若变量内容为空或者是未设置,都能够以后面的内容替换
命令别名与历史命令
alias
当我们的惯用命令很长的时候,我们可能希望通过某种方式让它便于输入。这就是命令别名,比如,在 Windows 中清屏是 “cls”,而 Linux 中是 “clear”,那我们其实可以通过别名的方式让 cls 同样可以使用:
[root@mars ~]# alias cls="clear"
#或者其他的命令,比如删除
[root@mars ~]# alias rmf="rm -f"
#我们如何看目前有哪些命令别名呢
[root@mars ~]# alias
alias cls='clear'
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 which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'
至于要取消别名,就用 unalias,比如unalias cls
历史命令:history
直接输入 histroy 就可以看到我们曾经执行的命令,默认是保存1000条。我们来看看它的参数:
[root@mars ~]# history [n]
[root@mars ~]# history [-c]
[root@mars ~]# history [-raw] histfiles
选项与参数:
n :数字,列出最近的 n 条命令
-c:将目前 shell 中的所有 history 清除
-a:将目前新增的history命令新增入histfiles,若没有则默认写入~/.bash_history
-r:将 histfiles 读入 history
-w:将 history 写入 histfiles
这里着重讲一下,history的另一个作用:
[root@mars ~]# !number
[root@mars ~]# !command
[root@mars ~]# !!
参数:
numbner:执行第几条命令
command:由最近的命令向前搜索命令串开头为 command 的命令并执行
!! :执行上个命令,相当于 ↑+Enter
[root@mars ~]# history
66 man rm
67 alias
68 man history
69 history
[root@mars ~]# !66 <==执行第66条命令
[root@mars ~]# !! <==执行上一个,即本例中的 !66
[root@mars ~]# !al <==执行最近的以 al 开头的命令(即本例中的第 67 个)
Tips:同一个账号同时多次登录的 history 写入问题
假设多个bansh同时写入,还是都是root。那么其实这些记录一开始都是写在内存中的,注销的时候才会更新。所以,最后一个注销的才会是最后写入的数据(其实都有记录,只是被覆盖了)