前言
开始使用Ubuntu操作系统,使用起来感觉挺顺,但是对于Shell脚本了解不多,以下是一个简单的shell入门总结。
不同的类Unix系统可能使用不同的Shell程序, 这里所有的例子都是基于
Bash(Bourne Again Shell)
脚本语言,既然冠之以“语言”,就说明它跟其他C/C++等编译语言在形式上是完全一样的,有变量,有函数,有if,else,while等条件分支,只是脚本语言是解释性的执行:碰到一句,解释一句,执行。
写一个脚本看一看
运行脚本之前,需要做三件事情:
- 写一个脚本
- 使Shell有权限执行该脚本
- 把脚本放在Shell可以找到的地方
打开文本编辑器,输入下列文本,保存为my_scripts.sh:
1 |
|
第一脚本就写成了。运行脚本之前,你可能需要修改脚本的权限:
1 | chmod 755 my_scripts |
输入下面命令执行脚本:
1 | ./my_scripts.sh |
基础
变量
shell脚本提供了不少环境变量来获取系统信息,如用户名,主机名,时间等:
1 | $HOSTNAME,$USER,$DATE |
这些变量是全局的,在任何时候都可以使用。但,要在脚本中使用的变量(变量无需声明,直接使用即可),却并不具备这样的全局性,例如,有如下脚本 myvar.sh:
1 |
|
执行如下脚本命令:
1 | $ MY_VAR=hello |
输出:
1 | MY_VAR is: |
因而,变量MY_VAR并不具有全局的作用域,如果要像环境变量一样使用该变量,必须将其export:
1 | $ MY_VAR=hello |
这样输出就变了:
1 | MY_VAR is: hello |
附几个比较特殊的变量:
$$
该变量对应的PID(进程ID)$?
上一个脚本命令的退出条件值
数组
跟其他语言相似, Bash也支持数组, 数据元素之间通过一个IFS(Input Field Separator, 默认是一个空格字符)来分割的. 一般,声明一个数据有如下三个方式:
1 |
|
遍历数组中的元素, 引用单个元素:
1 |
|
引用所有元素:
1 |
|
如果只是引用一个数组中的一部分,可以如下操作, 其中第一个数字表示开始的位置, 第二个数字表示需要引用的数组的长度.
1 |
|
如何在数组中添加或者删除元素了? 实现起来并不麻烦.
1 |
|
如果我们要确定数组中元素的数量以及某个元素的长度可以通过参数扩展(shell的几种扩展形式在下文中可以参考下文的阐述)的方式来获取:
1 |
|
需要注意的是,Bash中的数据未初始化的元素为空不作为数组长度计入,这与编程语言不一样。由于数组中肯能存在空隙
,如果要知道哪些元素是存在的,同样需要通过参数扩展来遍历:
1 |
|
函数
1 | function fcn_name(){ ... } |
那么,怎么知道函数的参数了?很简单,$1
对应第一个参数,$2
对应第二个参数,以此类推,$0
则表示执行脚本本身的名字,另外有几个个比较特殊的变量:
$#
函数的参数个数(执行脚本的参数)$@
除了脚本名外所有的参数,$1 $2 ....
1 |
|
不是还有递归吗?Shell脚本同样可以实现:
1 |
|
语句
多举几个例子就看懂了。
if/else 条件判断
1 | # first form |
循环
Bash中支持for
跟while(until)
两种形式的循环, 先来看看for
循环:
1 |
|
上述ffor
循环可以写成C风格的形式:
1 |
|
while
循环(与if
命令一样,while
也是根据命令列表的退出状态值来判断是否继续执行循环):
1 |
|
until
与while
大同小异, 只不过while
是在退出状态值不为0时结束循环,而until
与之相反。上述例子如果用until
可以写成:
1 |
|
Case
1 |
|
退出条件
应用执行是否成功的标志,是一个0~255之间的整数,0表示应用没有发生错误,执行成功;其他任何值都表示发生了错误。
用 $?
即可查看一个命令是否执行成功。
例如判断某个文件是否存在:
1 |
|
通配符
在使用Shell过程中要用到大量文件名, 因此它提供了一种特殊字符, 帮助找快速指定一组文件名. 这种特殊字符叫做通配符(wildcard
). 使用通配符的过程也称为”通配符匹配”(globbing
). 在命令ls
/cp
/mkdir
/find
中都可以使用通配符来实现操作多个文件的目的.下表列出了常用的通配符:
通配符 | 含义 |
---|---|
* | 匹配任意多个字符 |
? | 匹配任意单个字符 |
[chars] | 匹配属于字符集合chars 中的任意单个字符 |
[!chars] | 匹配不属于字符集合chars 中的任意单个字符 |
[[:class:]] | 匹配字符类class 中的任意单个字符 |
常见的字符类有
[:alnum:]
(单个字母数字),[:alpha:]
(单个字母),[:digit]
的那个数字,[:lower:]
小写字母,[:upper:]
大写字母
利用通配符, 可以构建出复杂的文件名匹配条件:
模式 | 匹配 |
---|---|
* | 所有文件(不包含隐藏文件) |
g* | 以g 开头的任意文件 |
b*.txt | 以b 开头, 扩展名为.txt的文件 |
Data??? | 以Data开头并紧接3个字符的文件 |
[abc]* | 以a/b/c中任意字符开头的文件 |
backup.[0-9]{3} | 以backup.开头并紧接3个数字的文件 |
[[:upper:]]* | 以大写字母开头的文件 |
*[[:lower:]123] | 以小写字母或1/2/3任意数字结尾的文件 |
[![:digit:]]* | 不以数字开头的文件 |
扩展
每次执行Shell命令时, Bash会对命令中的某些符号, 比如前面说的*
/?
等通配符, 进行替换操作. 这一处理过程被称为扩展(expansion
). 经过扩展, 我们执行的命令在被Shell执行之前会被展开成其他内容. 比如:
1 |
|
会把当前文件目录的文件都展示出来. 实际在执行echo
之前, *
已经被替换扩展成了其他内容. Shell中存在如路径名扩展/算术扩展等多种扩展形式:
- 路径名扩展
通配符的工作机制被称为路径名扩展, 比如:
1 |
|
- 浪纹线扩展
浪纹线~
被扩展为同用户名的主目录. 如果未执行用户名, 则扩展为当前用户的目录:
1 | echo ~ |
- 算术扩展
Shell通过算术扩展来执行算术运算-这样我们可以把Shell当作一个简单的计算器来使用了:
1 |
|
算术扩展还支持嵌套:
1 |
|
- 花括号扩展
花括号扩展{}
允许创建多个文本字符串; 花括号表达式本身可以是逗号分割的字符串列表, 也可以是整数区间或单个字符, 但不能包括未经引用的空白字符, 例如:
1 |
|
花括号扩展可以用来创建多个类似模式的文件夹, 比如我们要创建一个以年月时间为名称的系列文件夹:
1 |
|
- 参数扩展
通过参数扩展可以将变量扩展为对应内容:
1 |
|
Shell通过引用可以有选择的禁止扩展: 第一种引用类型是双引号, 在双引号引用中, Shell中使用的特殊字符都将失去其特殊含义($, \,
除外`), 就是说路径名扩展, 浪纹线扩展, 花括号扩展全部失效, 但是参数扩展/算术扩展仍然可用; 第二种类型是单引号, 单引用会禁止所有扩展.
重定向
一般程序会将执行的状态或信息发送到标准输出(stdout
)或者标准错误(stderr
), 默认情况下标准输出/标准错误都会直接输出到屏幕, 如果要将其导入到文件则需要使用I/O重定向
功能. Bash中提供了三种重定向:
- 标准输出重定向
1 |
|
- 标准输入重定向
1 |
|
- 标准错误重定向: 标准错误没有专门的重定向符号, 需要使用文件描述符
2
来引用
1 |
|
命令索引
Expression | Description | Example |
---|---|---|
& | run the previous command in the background | ls & |
&& | logical AND | if [ "$foo" -ge "0" ] && [ "$foo" -le "9" ] |
or | logical OR | if [ "$foo" -ge "0" ] or [ "$foo" -le "9" ] |
^ | start of line | grep "^foo |
$ | end of line | grep "foo$ |
= | string equlity | if [ "$foo" = "bar" ] |
! | logical NOT | if [ "$foo" != "bar" ] |
$$ | pid of current shell | echo "PID=$$ |
$! | pid of last background command | ls & echo "PID of ls = $! |
&? | exit status of last command | ls; echo "ls returned code $? |
$0 | name of current command | echo "I am $0" |
$1 | name of 1st parameter | echo "first argument is $1 |
$9 | name of 9th parameter | echo "nith argument is $9 |
$@ | all of current commands’ parameters(preserving whitespace/quoting) | echo "my arguments are $@ |
$* | all of current commands’ parameters(not preserving whitespace/quoting) | echo "my arguments are $* |
-d file | True if file is a directory | if [ -d /bin ] |
-e file | True if file exists | if [ -e /home/bin/my.text ] |
-f file | True if file exists and is a regular file | if [ -f /bin/fs] |
-L file | True if file is symbolic link | if [ -L /bin/fs ] |
-r file | True if file is readable | if [ -r /bin/fs ] |
-w file | True if file is writable | if [ -w /bin/fs ] |
-x file | True if file is executable | if [ -x /bin/fs ] |
f1 -nt f2 | True if f1 is newer than f2(modification time) | if [ "@f1" -nt "$f2" ] |
f1 -ot f2 | True if f1 is older than f2 | if [ "@f1" -ot "$f2" ] |
-z string | True if string is empty | if [ -z "$f00" ] |
-n string | True if string is not empty | if [ -n "$f00" ] |
str1=str2 | True if str1 equal str2 | if [ "$foo" = "bar" ] |
str1!=str2 | True if str1 not equal to str2 | if [ "$foo" != "bar" ] |
cd - | 将工作目录切换到上一个工作目录 | cd /xxx/xxxx; cd - |
Markdown显示原因:
or
实际为||
参考资料
- Advanced Bash-Scripting Guide
- https://google.github.io/styleguide/shell.xml
- http://www.shellscript.sh/
- https://coolshell.cn/articles/8883.html
- https://coolshell.cn/articles/8619.html
- https://github.com/alebcay/awesome-shell
- https://github.com/awesome-lists/awesome-bash
- https://github.com/epety/100-shell-script-examples
- http://tldp.org/LDP/abs/html/
- https://github.com/denysdovhan/bash-handbook
- https://www.gnu.org/software/bash/manual/html_node/index.html#SEC_Contents