Linux内建命令 - Shell脚本编程实践
1.如何确定内建命令:type
不要试图用脑子记住所有的命令,这不可能也不需要。判断一个命令是不是内建命令只需要借助于命令type
即可,如下所示:
# cd命令是内建命令
[root@banana ~]# type cd
cd is a shell builtin
# ifconfig命令不是内建命令,而是一个外部文件
[root@banana ~]# type ifconfig
ifconfig is /usr/sbin/ifconfig
2.执行程序:“.”(点号)
点号用于执行某个脚本,甚至脚本没有可执行权限也可以运行。有时候在测试运行某个脚本时可能并不想为此修改脚本权限,这时候就可以使用 “.”
来运行脚本。如果没有运行权限的话,用“./”
执行就会有报错,但是若在其前面使用点号来执行就不会报错,如下所示:
# 新建HelloWorld.sh,并添加代码
[root@banana ~]# vim HelloWorld.sh
echo "HelloWorld"
# 如果脚本没有可执行权限,则会报权限错误
[root@banana ~]# ./HelloWorld.sh
-bash: ./HelloWorld.sh: Permission denied
# 使用点号执行没有加执行权限的脚本可以正常运行
[root@banana ~]# . ./HelloWorld.sh
HelloWorld!
与点号类似,source
命令也可读取并在当前环境中执行脚本,同时还可返回脚本中最后一个命令的返回状态;如果没有返回值则返回0
,代表执行成功;如果未找到指定的脚本则返回false
。
[root@banana ~]# source HelloWorld.sh
HelloWorld!
3.别名:alias
可用于创建命令的别名,若直接输入该命令且不带任何参数,则列出当前用户使用了别名的命令。现在你应该能理解类似ll这样的命令为什么与ls-l的效果是一样的吧。
[root@banana ~]# 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 which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'
使用alias
可以自定义别名,比如说一般的关机命令是shutdown-h now,写起来比较长,这时可以重新定义一个关机命令,以后就方便多了。使用**alias定义的别名命令也是支持Tab键补全
**的,如下所示:
[root@banana ~]# alias myShutdown='shutdown -h now'
注意,这样定义alias
只能在当前Shell环境中有效,换句话说,重新登录后这个别名就消失了。为了确保永远生效,可以将该条目写到用户家目录中的.bashrc
文件中,如下所示:
[root@banana ~]# cat .bashrc
# .bashrc
# User specific aliases and functions
alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'
# 自定义关机命令的别名
myShutdown='shutdown -h now'
# Source global definitions
if [ -f /etc/bashrc ]; then
. /etc/bashrc
fi
4.删除别名:unalias
该命令用于删除当前Shell环境中的别名。有两种使用方法:
- 第一种用法:
unalias 命令
- 第二种用法:
unalias -a 命令
,删除当前Shell环境中所有的别名。
同样,这两种方法都是在当前Shell环境中生效的。
# 删除ll别名
[root@banana ~]# unalias ll
# 删除成功,发现.bashrc的ll别名被删除了。
[root@banana ~]# ll
-bash: ll: command not found
# 恢复ll
[root@banana ~]# alias ll='ls -l --color=auto'
5.任务前后台切换:bg、fg、jobs
该命令用于将任务放置后台运行,一般会与Ctrl+z、fg、&
符号联合使用。典型的使用场景是运行比较耗时的任务。比如打包某个占用较大空间的目录,若在前台执行,在任务完成前将会一直占用当前的终端,而导致无法执行其他任务,此时就应该将这类任务放置后台。
# 运行打包任务,并使用Ctrl+z组合键暂停前台任务
[root@banana ~]# tar -zcvf Python3.7.5-2.tgz Python-3.7.5
^Z
[1]+ Stopped tar -zcf Python3.7.5-2.tgz Python-3.7.5
# 查看任务状态
[root@banana ~]# jobs
[1]+ Stopped tar -zcf Python-3.7.5-2.tgz Python-3.7.5
# 继续运行后台任务tar,运行完之后Ctrl+c退出bg
[root@banana ~]# bg 1
^C
[1]+ Done tar -zcf Python-3.7.5-2.tgz Python-3.7.5
# 使用fg把后台任务调至前台运行
[root@banana ~]# fg 1
# 如果预知某个任务耗时很久,可以一开始就将命令放入后台运行
[root@banana ~]# tar -zcf Python-3.7.5-2.tgz Python-3.7.5 &
[1] 2215
注意:如果tar命令加了-v参数
产生输出,即使后台运行输出也会在打印到前台。此时我们可以使用 > 重定向
输出到output文件
[root@banana ~]# tar -vzcf Python-3.7.5-2.tgz Python-3.7.5 > output &
[2] 2223
延伸: 一般我们希望断开远程连接时,任务在后台继续执行,比如Web服务之类的任务。此时我们可以使用nohup 命令 &
。注意,nohup
不是内建命令。
[root@banana ~]# nohup tar -zcf Python-3.7.5-2.tgz Python-3.7.5 &
6.改变目录:cd
改变当前工作目录。如果不加参数,默认会进入当前用户的家目录。
7.声明变量:declare、typeset
这两个命令都是用来声明变量的,作用完全相同。很多语法严谨的语言(比如C语言)对变量的声明都是有严格要求的:变量的使用原则是必须在使用前声明、声明时必须说明变量类型,而Shell脚本中对变量声明的要求并不高,因为Shell弱化了变量的类概念,所以Shell又被称为弱类型编程语言,声明变量时并不需要指明类型。不过,若使用declare
命令,可以用-i参数声明整型变量
,如下所示:
# 声明整型变量
[root@banana ~]# i_num01=1
# 声明浮点型变量
[root@banana ~]# f_num01=3.14
# 声明字符串变量
[root@banana ~]# str01="HelloWorld"
# 使用declare声明整型变量
[root@banana ~]# declare -i i_num02=1
使用-r声明变量为只读
,如下所示:
# -r声明只读变量
[root@banana ~]# declare -r read_only=100
# 试图修改变量值,报错
[root@banana ~]# read_only=200
-bash: read_only: readonly variable
使用-a声明数组变量
,如下所示:
[root@banana ~]# declare -a arr='([0]="a" [1]="b" [2]="c")'
[root@banana ~]# echo ${arr[0]}
a
[root@banana ~]# echo ${arr[1]}
b
[root@banana ~]# echo ${arr[2]}
c
使用-F、-f显示脚本中定义的函数和函数体
,如下所示:
# 创建脚本fun.sh,内容如下
[root@banana ~]# cat fun.sh
#!/bin/bash
func_1()
{
echo "Function 1"
}
func_2()
{
echo "Function 2"
}
echo "declare -F:"
declare -F
echo "declare -f:"
declare -f
# 运行该脚本输出效果如下
[root@banana ~]# bash fun.sh
declare -F:
declare -f func_1
declare -f func_2
declare -f:
func_1 ()
{
echo "Function 1"
}
func_2 ()
{
echo "Function 2"
}
8.打印字符:echo
echo
用于打印字符,典型用法是使用echo
命令并跟上使用双引号括起的内容(即需要打印的内容),该命令会打印出引号中的内容,并在最后默认加上换行符。使用-n参数可以不打印换行符
。
# 自动换行
[root@banana ~]# echo "HelloWorld"
HelloWorld
# -n参数取消换行
[root@banana ~]# echo -n "HelloWorld"
HelloWorld[root@banana ~]#
默认情况下,echo命令会隐藏-e参数(禁止解释打印反斜杠转义的字符)。比如“\n”代表新的一行,如果尝试使用echo输出新的一行,在不加参数的情况下只会将“\n”当作普通的字符,若要打印转义字符,则需要通过使用-e参数来允许
。
# echo默认禁止打印反斜杠转义的字符
[root@banana ~]# echo "\n"
\n # "\n"被当做普通的字符
# 若要打印转义字符,则需要通过使用-e参数来允许
# 输出有两行,第一行是输出的新行,第二行的默认的换行符
[root@banana ~]# echo -e "\n"
[root@banana ~]#
9.跳出循环:break
从一个循环(for、while、until或者select)中退出。break后可以跟一个数字n,代表跳出n层循环,n必须大于1,如果n比当前循环层数还要大,则跳出所有循环。 下面的脚本演示了使用break和break 2的区别(运行前请将对应的注释符去掉)。
[root@banana ~]# cat break_01.sh
#!/bin/bash
for I in A B C D
do
# echo输出I
echo -n "$I:"
# J in range(10)
for J in `seq 10`
do
# 如果 J == 5,退出一层循环
if [ $J -eq 5 ]; then
break
# break 2
fi
# echo输出J
echo -n " $J"
done
# echo默认会输出"\n"
echo
done
echo
# 当J值为5时,break的输出结果(外层运行了4次)
[root@banana ~]# bash break_01.sh
A: 1 2 3 4
B: 1 2 3 4
C: 1 2 3 4
D: 1 2 3 4
# 当J值为5时,break 2的输出结果(外层仅运行了1次)
[root@banana ~]# bash break_01.sh
A: 1 2 3 4
10.循环控制:continue
停止当前循环,并执行外层循环(for、while、until或者select)的下一次循环。continue后可以跟上一个数字n,代表跳至外部第n层循环。n必须大于1,如果n比当前循环层数还要大,将跳至最外层的循环。 下面的脚本演示了使用continue和continue 2的区别(运行前请将对应的注释符去掉)。
# 创建演示脚本continue
[root@banana ~]# cat continue_01.sh
#!/bin/bash
# [A,B,C,D]
for I in A B C D
do
# echo输出I
echo -n "$I:"
# J从1,10
for J in {1..10}
do
# 如果J == 5,退出循环
if [ $J -eq 5 ]; then
continue
# continue 2
fi
# echo输出J
echo -n " $J"
done
# echo换行
echo
done
# echo换行
echo
# 判断J值为5时,continue的输出结果
[root@banana ~]# bash continue_01.sh
A: 1 2 3 4 6 7 8 9 10
B: 1 2 3 4 6 7 8 9 10
C: 1 2 3 4 6 7 8 9 10
D: 1 2 3 4 6 7 8 9 10
# 判断J值为5时,continue 2的输出结果
[root@banana ~]# bash continue_01.sh
A: 1 2 3 4B: 1 2 3 4C: 1 2 3 4D: 1 2 3 4
11.将所跟的参数作为Shell的输入,并执行产生的命令:eval
# eval用法例一:将字符串解析成命令执行
# 定义cmd为一个字符串,该字符串为"ls -l /etc/passwd"
[root@banana ~]# cmd="ls -l /etc/passwd"
# 使用eval,会将之前的字符串解析为命令并执行
[root@banana ~]# eval $cmd
-rw-r--r-- 1 root root 1171 Jun 10 22:11 /etc/passwd
# eval用法例二:程序运行中根据某个变量确定实际的变量名
[root@banana ~]# name1=john # 定义变量name1
[root@banana ~]# name2=wang # 定义变量name2
[root@banana ~]# num=1 # 使用该变量确定真实的变量名name$num
[root@banana ~]# eval echo "$"name$num
john
# eval用法例三:将某个变量的值当做另一个变量名并给其赋值
[root@banana ~]# name1=john
[root@banana ~]# name2=wang
[root@banana ~]# eval $name1="$name2"
[root@banana ~]# echo $john
wang
12.执行命令来取代当前的Shell:exec
内建命令exec并不启动新的Shell,而是用要被执行的命令替换当前的Shell进程,并且将老进程的环境清理掉,而且exec命令后的其他命令将不再执行。 假设在一个Shell里面执行了exec echo '‘Hello’'命令,在正常地输出一个“Hello”后Shell会退出,因为这个Shell进程已被替换为仅仅执行echo命令的一个进程,执行结束自然也就退出了。如图11-5所示,命令执行完成后,连接状态是一个红色的断开符。
想要避免出现这种情况,一般将exec命令放到一个Shell脚本里面,由主脚本调用这个脚本,主脚本在调用子脚本执行时,当执行到exec后,该子脚本进程就被替换成相应的exec的命令。注意source命令或者点号,不会为脚本新建Shell,而只是将脚本包含的命令在当前Shell执行。exec典型的用法是与find联合使用,用find找出符合匹配的文件,然后交给exec处理,如下所示:
# 列出系统中所有以.conf结尾的文件
[root@banana ~]# find / -name "*.conf" -exec ls -l {} \;
# 删除系统中所有的.tmp临时文件
[root@banana ~]# find / -name "*.tmp" -exec rm -f {} \;