Shell脚本快速入门
- 2019-02-13
- Phosphorus15
作者:Phosphorus15
shell
脚本是每一个linux开发者所必备的技能,相比其它脚本语言,shell
更加贴近linux本身,与bash高度整合,在不少应用中(尤其是涉及文本处理和IO的)有着更高的易用性和简洁性。
阅读本文需要一定的基础,笔者在写作时,期望读者:
有一定的linux使用经验
熟悉至少一门常规脚本语言/编程语言
知道管道为何物
不要读着读着就睡着了全文节奏较快,不会对细节作过多阐述(
因为作者也是个半吊子),旨在让读者在短时间快速上手,如存在问题,欢迎指正交流。此文不是成稿,可能存在描述错误或者考虑不周的情况,内容也不是十分完备,希望读者多多包涵。
变量与函数
变量
变量定义与赋值的标准形式是标识符=值
,不需要单独声明,等号附近不能有空格,示例如下:
1 |
|
用sh
命令运行脚本,获得输出
1 | williams is 35 years old |
函数定义
函数的定义方式如下
1 | func1() { |
运行脚本,获得输出
1 | oops |
命令与IO
命令与IO是shell
脚本中最重要的部分,在管道系统的帮助下,脚本可以很轻松地与系统命令和程序交互。
以下是一个简单的例子:
1 | txts=`ls | egrep *.\.txt` # 执行ls列出当前目录文件,并用egrep筛选.txt文件 |
此处的`符号与Ruby
相近,用于执行命令并将标准输出流
中的内容作为字符串值返回。
因此,我们得到的变量txts
就是在bash下执行命令时控制台上输出的内容。
再比如,读取文件内容,或者获取当前用户,利用上述方法就能很容易地实现
1 | text=`cat hello.txt` # => text 被赋值为 hello.txt 里的文本 |
向文件输出同样也十分简单
1 | text1="hello," |
运行上述脚本,目录下就会出现一个hello.txt
,内有一行文字: hello,world!
使用read
命令,可以从控制台中读入一个变量(类似于Haskell
中的read)
1 |
|
值得注意的是,变量也可以参与`符号所包含的命令中,比如:
1 | text="eureka!" |
上述脚本输出为EUREKA!
运算与流程控制
运算与括号
提起shell
脚本中的运算,就不得不介绍一下最常用的几种括号
双小括号(( ))
用于算术(整型)运算,同时也可以用于一些特殊功能
1 | a=2 |
中括号[ ]
和双中括号[[ ]]
用于比较运算,其中[ ]
实际上是对linux中test
命令的隐式调用(务必注意:在shell的定义中,true
为0,而false
为1)
1 | a=2; b=1; c=3; # 单行可以用分号隔开多个表达式 |
熟悉test
命令的使用对写出正确的逻辑运算表达式很有帮助,读者可以参考维基百科)来了解更多。
不难看出,双中括号[[ ]]
相比中括号更加人性化,可以轻松胜任近似C语言
的表达式。
大括号{}
用于定义范围和通配,在此暂不详细阐述,有兴趣的读者可以参阅这篇博客。
分支语句
与Python
不同,shell脚本并不强制要求任何形式的缩进,因此流程语句需要有块结束符号。
典型的if语句如下:
1 | read a |
注意:无论是循环还是分支控制,都不允许有空的块,即不能出现:
1 | if [ `whoami` = 'root' ]; then # 不能为空! |
循环语句
和许多高级语言一样,shell具有几种可用的循环控制结构,笔者在此会介绍自己常用的几种:
while 语句 - 耳熟能详,不说就会~
1
2
3
4
5
6a=1; b=0;
while [ $a -le 100 ]; do # 此处也可写作 while (( $a <= 100 )); do
b=$(($a+$b))
a=$(($a+1)) # bash 下可写作 ((a++))
done
echo $b # 输出为 5050until 语句 - 反其道而行
1
2
3
4
5
6a=1; b=0;
until [ $a -gt 100 ]; do
b=$(($a+$b))
a=$(($a+1))
done
echo $b # 输出为 5050其实就是把while反过来~
是不是觉得写起来怪难受的? 没关系~算术运算本来就不是shell脚本的特长,下面你要看到的,才是shell真正的精髓。
for each 语句 - 来自C11
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17for i in Apple Juice Cake; do
echo do you like $i ?
done
for j in `seq 0 3`; do
echo $j
done
for k in `seq 65 70`; do
echo $k | awk '{printf("%c", $1)}' # 将ascii码转为字符输出
# bash 下可以使用 printf, 即 printf \\x$(printf %x $k)
done
for c in {0..8..2}; do # 0~8之间的所有偶数
echo $c
done
# 注意,上述写法只在bash下有效输出内容:
1
2
3
4
5
6
7
8do you like Apple ?
do you like Juice ?
do you like Cake ?
0
1
2
3
ABCDEF不难想到,我们先前的
while
循环可以写作1
2
3
4
5s=0
for i in `seq 1 100`; do # 创建一个 1 到 100 的序列
s=$(($s+$i))
done
echo $s假如我们想给目录下的每个文件都创建一个备份,可以这么写:
1
2
3
4
5
6
7
8for file in $(ls); do # 列举所有文件
# 用 file 判断是否是文件夹, 是就不进行备份
if [ "$(file $file | grep -o directory)" != 'directory' ]
then
cp $file "$file.bak" # 用 cp 创建备份
echo backup $file
fi
donefor 语句 - 唔。。?
笔者在前面多次写道,一些语句只能被
bash
支持,而不能被sh
支持。鉴于很多时候我们习惯用sh
来运行脚本,在编写脚本时应当尽量避免使用此类语句。标准的
for
语句只有bash
能够支持,写法如下:1
2
3
4for((i=0;i<10;i++));
do
echo $(expr 10 - $i) # 等价于 $((10 - $i))
done啊~ Old good days,一股浓浓的C语言
风味扑面而来。但还是如上所述,不推荐使用。
附加内容
数组
数组是只有bash
下才能使用的功能,与很多语言中的数组都十分相似。定义方式如下:
1 | a=(2 7 5 6 8 1 3 0 4 9) |
数组可以直接通过下标访问或者展开
1 | echo ${a[0]} ${a[3]} # => 输出 2 6 |
相应的,用for each
循环可以快速对数组进行遍历:
1 | for e in ${c[@]}; do |
小结
到此,读者应当已经掌握了基本shell脚本编写的基本技巧,如果想要进一步熟练,推荐读者自己尝试写一些简单的脚本,以此更好地加深理解;同时,也要更多地熟悉linux系统的相关命令。
在最后,笔者放出一个小脚本来复习部分上述内容。
1 |
|
脚本运行结果:
1 | phosphorus15@ubuntu:~/Documents$ sh fac.sh |