目录
Shell 快速指南
??概述
????什么是 shell
????什么是 shell 脚本
????Shell 环境
??????指定脚本解释器
????模式
??????交互模式
??????非交互模式
??Shell 编程
????解释器
????注释
????变量
??????局部变量
??????环境变量
??????位置参数
????Shell扩展
??????大括号扩展
??????命令置换
??????算数扩展
??????单引号和双引号
????数组
??????创建数组
??????获取数组元素
??????获取数组长度
??????向数组中添加元素
??????从数组中删除元素
????运算符
??????算术运算符
??????关系运算符
??????布尔运算符
??????逻辑运算符
??????字符串运算符
??????文件测试运算符
????语句
??????条件语句
??????循环语句
????函数
????流和重定向
??????输入、输出流
??????重定向
??????/dev/null 文件
????Debugging
????资料
Shell 快速指南
███████╗██╗ ██╗███████╗██╗ ██╗
██╔════╝██║ ██║██╔════╝██║ ██║
███████╗███████║█████╗ ██║ ██║
╚════██║██╔══██║██╔══╝ ██║ ██║
███████║██║ ██║███████╗███████╗███████╗
概述
什么是 shell
Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁。
Shell 既是一种命令语言,又是一种程序设计语言。
Shell 是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问 Linux 内核的服务。
Ken Thompson 的 sh 是第一种 Unix Shell,Windows Explorer 是一个典型的图形界面 Shell。
什么是 shell 脚本
Shell 脚本(shell script),是一种为 shell 编写的脚本程序,一般文件后缀为 .sh
。
业界所说的 shell 通常都是指 shell 脚本,但 shell 和 shell script 是两个不同的概念。
Shell 环境
Shell 编程跟 java、php 编程一样,只要有一个能编写代码的文本编辑器和一个能解释执行的脚本解释器就可以了。
Shell 的解释器种类众多,常见的有:
- sh - 即 Bourne Shell。sh 是 Unix 标准默认的 shell。
- bash - 即 Bourne Again Shell。bash 是 Linux 标准默认的 shell。
- fish - 智能和用户友好的命令行 shell。
- xiki - 使 shell 控制台更友好,更强大。
- zsh - 功能强大的 shell 与脚本语言。
指定脚本解释器
在 shell 脚本,#!
告诉系统其后路径所指定的程序即是解释此脚本文件的 Shell 解释器。#!
被称作shebang(也称为 Hashbang )。
所以,你应该会在 shell 中,见到诸如以下的注释:
- 指定 sh 解释器
#!/bin/sh
- 指定 bash 解释器
#!/bin/bash
注意
上面的指定解释器的方式是比较常见的,但有时候,你可能也会看到下面的方式:
#!/usr/bin/env bash
这样做的好处是,系统会自动在
PATH
环境变量中查找你指定的程序(本例中的bash
)。相比第一种写法,你应该尽量用这种写法,因为程序的路径是不确定的。这样写还有一个好处,操作系统的PATH
变量有可能被配置为指向程序的另一个版本。比如,安装完新版本的bash
,我们可能将其路径添加到PATH
中,来“隐藏”老版本。如果直接用#!/bin/bash
,那么系统会选择老版本的bash
来执行脚本,如果用#!/usr/bin/env bash
,则会使用新版本。
模式
shell 有交互和非交互两种模式。
交互模式
简单来说,你可以将 shell 的交互模式理解为执行命令行。
看到形如下面的东西,说明shell处于交互模式下:
user@host:~$
接着,便可以输入一系列 Linux 命令,比如 ls
,grep
,cd
,mkdir
,rm
等等。
非交互模式
简单来说,你可以将 shell 的非交互模式理解为执行 shell 脚本。
在非交互模式下,shell 从文件或者管道中读取命令并执行。
当 shell 解释器执行完文件中的最后一个命令,shell 进程终止,并回到父进程。
可以使用下面的命令让shell以非交互模式运行:
sh /path/to/script.sh
bash /path/to/script.sh
上面的例子中,script.sh
是一个包含shell解释器可以识别并执行的命令的普通文本文件,sh
和bash
是shell解释器程序。你可以使用任何喜欢的编辑器创建script.sh
(vim,nano,Sublime Text, Atom等等)。
除此之外,你还可以通过chmod
命令给文件添加可执行的权限,来直接执行脚本文件:
chmod +x /path/to/script.sh #使脚本具有执行权限
/path/to/test.sh
这种方式要求脚本文件的第一行必须指明运行该脚本的程序,比如:
#!/bin/bash
echo "Hello, world!"
上面的例子中,我们使用了一个很有用的命令echo
来输出字符串到屏幕上。
Shell 编程
由于 bash 是 Linux 标准默认的 shell,可以说 bash 是 shell 编程的基础。
所以,下面将全部基于 bash 来讲解 shell 编程。
此外,本篇章主要介绍的是 shell 编程的语法,对于 linux 指令不做任何介绍。
解释器
前面虽然两次提到了#!
,但是本着重要的事情说三遍的精神,这里再强调一遍:
在 shell 脚本,#!
告诉系统其后路径所指定的程序即是解释此脚本文件的 Shell 解释器。#!
被称作shebang(也称为 Hashbang )。
#!
决定了脚本可以像一个独立的可执行文件一样执行,而不用在终端之前输入sh
, bash
, python
, php
等。
示例:
# 以下两种方式都可以指定 shell 解释器为 bash,第二种方式更好
#!/bin/bash
#!/usr/bin/env bash
注释
shell 语法支持注释。注释是特殊的语句,会被 shell 解释器忽略。它们以 #
开头,到行尾结束。
示例:
#!/bin/bash
### This script will print your username.
whoami
Tip: 用注释来说明你的脚本是干什么的,以及为什么这样写。
变量
跟许多程序设计语言一样,你可以在 bash 中创建变量。
Bash 中没有数据类型,bash 中的变量可以保存一个数字、一个字符、一个字符串等等。同时无需提前声明变量,给变量赋值会直接创建变量。
你可以创建三种变量:局部变量,环境变量以及作为位置参数的变量。
局部变量
局部变量是仅在某个脚本内部有效的变量。它们不能被其他的程序和脚本访问。
局部变量可以用
=
声明(作为一种约定,变量名、=
、变量的值之间不应该有空格),其值可以用$
访问到。
示例:
username="zhangpeng" ### 声明变量
echo $username ### 输出变量的值
unset username ### 删除变量
可以用
local
关键字声明属于某个函数的局部变量。这样声明的变量会在函数结束时消失。
local local_var="I‘m a local value"
环境变量
环境变量是对当前 shell 会话内所有的程序或脚本都可见的变量。
创建它们跟创建局部变量类似,但使用的是
export
关键字。
export global_var="I‘m a global value"
常见的环境变量:
变量 | 描述 |
---|---|
$HOME |
当前用户的用户目录 |
$PATH |
用分号分隔的目录列表,shell会到这些目录中查找命令 |
$PWD |
当前工作目录 |
$RANDOM |
0到32767之间的整数 |
$UID |
数值类型,当前用户的用户ID |
$PS1 |
主要系统输入提示符 |
$PS2 |
次要系统输入提示符 |
这里 有一张更全面的 Bash 环境变量列表。
位置参数
位置参数是在调用一个函数并传给它参数时创建的变量。
位置参数变量表:
变量 | 描述 |
---|---|
$0 |
脚本名称 |
$1 … $9 |
第1个到第9个参数列表 |
${10} … ${N} |
第10个到N个参数列表 |
$* or $@ |
除了$0 外的所有位置参数 |
$# |
不包括$0 在内的位置参数的个数 |
$FUNCNAME |
函数名称(仅在函数内部有值) |
示例:
在下面的例子中,位置参数为:$0=‘./script.sh‘
,$1=‘foo‘
,$2=‘bar‘
:
$ ./script.sh foo bar
变量可以有默认值。我们可以用如下语法来指定默认值:
### 如果变量为空,赋给他们默认值
: ${VAR:=‘default‘}
: ${1:=‘first‘}
echo "\$1 : " $1
: ${2:=‘second‘}
echo "\$2 : " $2
### 或者
FOO=${FOO:-‘default‘}
Shell扩展
扩展 发生在一行命令被分成一个个的 记号(tokens) 之后。换言之,扩展是一种执行数学运算的机制,还可以用来保存命令的执行结果,等等。
感兴趣的话可以阅读关于shell扩展的更多细节。
大括号扩展
大括号扩展让生成任意的字符串成为可能。它跟 文件名扩展 很类似,举个例子:
echo beg{i,a,u}n ### begin began begun
大括号扩展还可以用来创建一个可被循环迭代的区间。
echo {0..5} ### 0 1 2 3 4 5
echo {00..8..2} ### 00 02 04 06 08
命令置换
命令置换允许我们对一个命令求值,并将其值置换到另一个命令或者变量赋值表达式中。当一个命令被```或
$()`包围时,命令置换将会执行。举个例子:
now=`date +%T`
### or
now=$(date +%T)
echo $now ### 19:08:26
算数扩展
在bash中,执行算数运算是非常方便的。算数表达式必须包在$(( ))
中。算数扩展的格式为:
result=$(( ((10 + 5*3) - 7) / 2 ))
echo $result ### 9
在算数表达式中,使用变量无需带上$
前缀:
x=4
y=7
echo $(( x + y )) ### 11
echo $(( ++x + y++ )) ### 12
echo $(( x + y )) ### 13
单引号和双引号
单引号和双引号之间有很重要的区别。在双引号中,变量引用或者命令置换是会被展开的。在单引号中是不会的。举个例子:
echo "Your home: $HOME" ### Your home: /Users/<username>
echo ‘Your home: $HOME‘ ### Your home: $HOME
当局部变量和环境变量包含空格时,它们在引号中的扩展要格外注意。随便举个例子,假如我们用echo
来输出用户的输入:
INPUT="A string with strange whitespace."
echo $INPUT ### A string with strange whitespace.
echo "$INPUT" ### A string with strange whitespace.
调用第一个echo
时给了它5个单独的参数 —— $INPUT
被分成了单独的词,echo
在每个词之间打印了一个空格。第二种情况,调用echo
时只给了它一个参数(整个$INPUT的值,包括其中的空格)。
来看一个更严肃的例子:
FILE="Favorite Things.txt"
cat $FILE ### 尝试输出两个文件: `Favorite` 和 `Things.txt`
cat "$FILE" ### 输出一个文件: `Favorite Things.txt`
尽管这个问题可以通过把FILE重命名成Favorite-Things.txt
来解决,但是,假如这个值来自某个环境变量,来自一个位置参数,或者来自其它命令(find
, cat
, 等等)呢。因此,如果输入 可能 包含空格,务必要用引号把表达式包起来。
数组
跟其它程序设计语言一样,bash中的数组变量给了你引用多个值的能力。在bash中,数组下标也是从0开始,也就是说,第一个元素的下标是0。
跟数组打交道时,要注意一个特殊的环境变量IFS
。IFS,全称 Input Field Separator,保存了数组中元素的分隔符。它的默认值是一个空格IFS=‘ ‘
。
创建数组
在 bash 中有好几种方法创建一个数组
array[0] = val
array[1] = val
array[2] = val
array=([2]=val [0]=val [1]=val)
array=(val val val)
获取数组元素
- 获取数组的单个元素:
echo ${array[1]}
- 获取数组的所有元素:
echo ${array[*]}
echo ${array[@]}
上面两行有很重要(也很微妙)的区别,假设某数组元素中包含空格:
colors[0]=Red
colors[1]="Dark Green"
colors[2]=Blue
为了将数组中每个元素单独一行输出,我们用内建的printf
命令:
printf "+ %s\n" ${colors[*]}
# 输出:
# + Red
# + Dark
# + Green
# + Blue
为什么Desert
和fig
各占了一行?尝试用引号包起来:
printf "+ %s\n" "${colors[*]}"
# 输出:
# + Red Dark Green Blue
现在所有的元素都跑去了一行 —— 这不是我们想要的!为了解决这个痛点,${colors[@]}
闪亮登场:
printf "+ %s\n" "${colors[@]}"
# 输出:
+ Red
+ Dark Green
+ Blue
在引号内,${colors[@]}
将数组中的每个元素扩展为一个单独的参数;数组元素中的空格得以保留。
- 访问数组的部分元素:
echo ${array[@]:0:2}
在上面的例子中,${array[@]}
扩展为整个数组,:0:2
取出了数组中从0开始,长度为2的元素。
获取数组长度
echo ${#array[*]}
向数组中添加元素
向数组中添加元素也非常简单:
colors=(Yellow "${colors[@]}" Pink Black)
echo ${colors[@]}
# 输出:
# Yellow Red Dark Green Blue Pink Black
上面的例子中,${colors[@]}
扩展为整个数组,并被置换到复合赋值语句中,接着,对数组colors
的赋值覆盖了它原来的值。
从数组中删除元素
用unset
命令来从数组中删除一个元素:
unset colors[0]
echo ${colors[@]}
# 输出:
# Red Dark Green Blue Pink Black
运算符
算术运算符
下表列出了常用的算术运算符,假定变量 a 为 10,变量 b 为 20:
运算符 | 说明 | 举例 |
---|---|---|
+ | 加法 | expr $a + $b 结果为 30。 |
- | 减法 | expr $a - $b 结果为 -10。 |
* | 乘法 | expr $a \* $b 结果为 200。 |
/ | 除法 | expr $b / $a 结果为 2。 |
% | 取余 | expr $b % $a 结果为 0。 |
= | 赋值 | a=$b 将把变量 b 的值赋给 a。 |
== | 相等。用于比较两个数字,相同则返回 true。 | [ $a == $b ] 返回 false。 |
!= | 不相等。用于比较两个数字,不相同则返回 true。 | [ $a != $b ] 返回 true。 |
注意:条件表达式要放在方括号之间,并且要有空格,例如: [\(a==\)b] 是错误的,必须写成 [ $a == $b ]。
示例:
a=10
b=20
echo "a=$a, b=$b"
val=`expr $a + $b`
echo "a + b : $val"
val=`expr $a - $b`
echo "a - b : $val"
val=`expr $a \* $b`
echo "a * b : $val"
val=`expr $b / $a`
echo "b / a : $val"
val=`expr $b % $a`
echo "b % a : $val"
if [ $a == $b ]
then
echo "a 等于 b"
fi
if [ $a != $b ]
then
echo "a 不等于 b"
fi
关系运算符
关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
下表列出了常用的关系运算符,假定变量 a 为 10,变量 b 为 20:
运算符 | 说明 | 举例 |
---|---|---|
-eq | 检测两个数是否相等,相等返回 true。 | [ $a -eq $b ] 返回 false。 |
-ne | 检测两个数是否相等,不相等返回 true。 | [ $a -ne $b ] 返回 true。 |
-gt | 检测左边的数是否大于右边的,如果是,则返回 true。 | [ $a -gt $b ] 返回 false。 |
-lt | 检测左边的数是否小于右边的,如果是,则返回 true。 | [ $a -lt $b ] 返回 true。 |
-ge | 检测左边的数是否大于等于右边的,如果是,则返回 true。 | [ $a -ge $b ] 返回 false。 |
-le | 检测左边的数是否小于等于右边的,如果是,则返回 true。 | [ $a -le $b ] 返回 true。 |
示例:
a=10
b=20
if [ $a -eq $b ]
then
echo "$a -eq $b : a 等于 b"
else
echo "$a -eq $b: a 不等于 b"
fi
if [ $a -ne $b ]
then
echo "$a -ne $b: a 不等于 b"
else
echo "$a -ne $b : a 等于 b"
fi
if [ $a -gt $b ]
then
echo "$a -gt $b: a 大于 b"
else
echo "$a -gt $b: a 不大于 b"
fi
if [ $a -lt $b ]
then
echo "$a -lt $b: a 小于 b"
else
echo "$a -lt $b: a 不小于 b"
fi
if [ $a -ge $b ]
then
echo "$a -ge $b: a 大于或等于 b"
else
echo "$a -ge $b: a 小于 b"
fi
if [ $a -le $b ]
then
echo "$a -le $b: a 小于或等于 b"
else
echo "$a