“Shell”的版本间的差异
(→for循环) |
|||
(未显示2个用户的13个中间版本) | |||
第2行: | 第2行: | ||
==== 什么是 shell ==== | ==== 什么是 shell ==== | ||
* Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁。 | |||
* Shell 既是一种命令语言,又是一种程序设计语言。 | |||
* Shell 是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问 Linux 内核的服务。 | |||
==== 什么是 shell 脚本 ==== | ==== 什么是 shell 脚本 ==== | ||
Shell 脚本(shell script),是一种为 shell 编写的脚本程序,一般文件后缀为 <code>.sh</code>。 | |||
==== 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 脚本,<code>#!</code> 告诉系统其后路径所指定的程序即是解释此脚本文件的 Shell 解释器。<code>#!</code> 被称作shebang(也称为 Hashbang )。 | |||
所以,你应该会在 shell 中,见到诸如以下的注释: | |||
* 指定 sh 解释器 | |||
#!/bin/sh | |||
* 指定 bash 解释器 | |||
#!/bin/bash | |||
<blockquote>'''注意''' | |||
上面的指定解释器的方式是比较常见的,但有时候,你可能也会看到下面的方式: | |||
#!/usr/bin/env bash | |||
这样做的好处是,系统会自动在 <code>PATH</code> 环境变量中查找你指定的程序(本例中的<code>bash</code>)。相比第一种写法,你应该尽量用这种写法,因为程序的路径是不确定的。这样写还有一个好处,操作系统的<code>PATH</code>变量有可能被配置为指向程序的另一个版本。比如,安装完新版本的<code>bash</code>,我们可能将其路径添加到<code>PATH</code>中,来“隐藏”老版本。如果直接用<code>#!/bin/bash</code>,那么系统会选择老版本的<code>bash</code>来执行脚本,如果用<code>#!/usr/bin/env bash</code>,则会使用新版本。</blockquote> | |||
==== | ==== 模式 ==== | ||
shell 有交互和非交互两种模式。 | |||
===== 交互模式 ===== | |||
简单来说,你可以将 shell 的交互模式理解为执行命令行。 | |||
看到形如下面的东西,说明 shell 处于交互模式下: | |||
user@host:~$ | |||
接着,便可以输入一系列 Linux 命令,比如 <code>ls</code>,<code>grep</code>,<code>cd</code>,<code>mkdir</code>,<code>rm</code> 等等。 | |||
===== 非交互模式 ===== | |||
简单来说,你可以将 shell 的非交互模式理解为执行 shell 脚本。 | |||
在非交互模式下,shell 从文件或者管道中读取命令并执行。当 shell 解释器执行完文件中的最后一个命令,shell 进程终止,并回到父进程。 | |||
可以使用下面的命令让 shell 以非交互模式运行:<syntaxhighlight lang="shell"> | |||
sh /path/to/script.sh | |||
bash /path/to/script.sh | |||
source /path/to/script.sh | |||
./path/to/script.sh | |||
</syntaxhighlight>上面的例子中,<code>script.sh</code>是一个包含 shell 解释器可以识别并执行的命令的普通文本文件,<code>sh</code>和<code>bash</code>是 shell 解释器程序。你可以使用任何喜欢的编辑器创建<code>script.sh</code>(vim,nano,Sublime Text, Atom 等等)。 | |||
其中,<code>source /path/to/script.sh</code> 和 <code>./path/to/script.sh</code> 是等价的。 | |||
除此之外,你还可以通过<code>chmod</code>命令给文件添加可执行的权限,来直接执行脚本文件:<syntaxhighlight lang="shell"> | |||
chmod +x /path/to/script.sh #使脚本具有执行权限 | |||
/path/to/test.sh | |||
</syntaxhighlight>这种方式要求脚本文件的第一行必须指明运行该脚本的程序,比如:<syntaxhighlight lang="shell"> | |||
#!/usr/bin/env bash | |||
echo "Hello, world!" | |||
</syntaxhighlight>使用<code>echo</code> 命令将字符串输出到屏幕上。 | |||
=== 基本语法 === | === 基本语法 === | ||
==== 解释器 ==== | ==== 解释器 ==== | ||
<code>#!</code> 决定了脚本可以像一个独立的可执行文件一样执行,而不用在终端之前输入<code>sh</code>, <code>bash</code>, <code>python</code>, <code>php</code>等。<syntaxhighlight lang="shell"> | |||
# 以下两种方式都可以指定 shell 解释器为 bash,第二种方式更好 | |||
#!/bin/bash | |||
#!/usr/bin/env bash | |||
</syntaxhighlight> | |||
==== 注释 ==== | ==== 注释 ==== | ||
注释可以说明你的代码是什么作用,以及为什么这样写。 | |||
shell 语法中,注释是特殊的语句,会被 shell 解释器忽略。 | |||
* 单行注释 - 以 <code>#</code> 开头,到行尾结束。 | |||
* 多行注释 - 以 <code>:<<EOF</code> 开头,到 <code>EOF</code> 结束。 | |||
<syntaxhighlight lang="shell"> | |||
#-------------------------------------------- | |||
# shell 注释示例 | |||
# author:xiaoming | |||
#-------------------------------------------- | |||
# echo '这是单行注释' | |||
########## 这是分割线 ########## | |||
:<<EOF | |||
echo '这是多行注释' | |||
echo '这是多行注释' | |||
echo '这是多行注释' | |||
EOF | |||
</syntaxhighlight> | |||
==== echo ==== | ==== echo ==== | ||
echo 用于字符串的输出。 | |||
* 输出普通字符串:<syntaxhighlight lang="shell"> | |||
echo "hello, world" | |||
# Output: hello, world | |||
</syntaxhighlight> | |||
* 输出含变量的字符串:<syntaxhighlight lang="shell"> | |||
name=xiaoming | |||
echo "hello, \"${name}\"" | |||
# Output: hello, "xiaoming" | |||
</syntaxhighlight> | |||
* 输出含换行符的字符串:<syntaxhighlight lang="shell"> | |||
# 输出含换行符的字符串 | |||
echo "YES\nNO" | |||
# Output: YES\nNO | |||
echo -e "YES\nNO" # -e 开启转义 | |||
# Output: | |||
# YES | |||
# NO | |||
</syntaxhighlight> | |||
* 输出含不换行符的字符串:<syntaxhighlight lang="shell"> | |||
echo "YES" | |||
echo "NO" | |||
# Output: | |||
# YES | |||
# NO | |||
echo -e "YES\c" # -e 开启转义 \c 不换行 | |||
echo "NO" | |||
# Output: | |||
# YESNO | |||
</syntaxhighlight> | |||
* 输出重定向至文件:<syntaxhighlight lang="shell"> | |||
echo "test" > test.txt | |||
</syntaxhighlight> | |||
* 输出执行结果:<syntaxhighlight lang="shell"> | |||
echo `pwd` | |||
# Output:(当前目录路径) | |||
</syntaxhighlight> | |||
==== printf ==== | ==== printf ==== | ||
printf 用于格式化输出字符串。 | |||
printf 命令的语法: | |||
printf format-string [arguments...] | |||
参数说明: | |||
* '''format-string:''' 为格式控制字符串 | |||
* '''arguments:''' 为参数列表。 | |||
默认,printf 不会像 echo 一样自动添加换行符,如果需要换行可以手动添加 <code>\n</code>。 | |||
=== 变量 === | === 变量 === | ||
跟许多程序设计语言一样,你可以在 bash 中创建变量。 | |||
bash 中没有数据类型,bash 中的变量可以保存一个数字、一个字符、一个字符串等等。同时无需提前声明变量,给变量赋值会直接创建变量。 | |||
==== 变量命名原则 ==== | ==== 变量命名原则 ==== | ||
* 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头。 | |||
* 中间不能有空格,可以使用下划线(_)。 | |||
* 不能使用标点符号。 | |||
* 不能使用 bash 里的关键字(可用 help 命令查看保留关键字)。 | |||
==== 声明变量 ==== | ==== 声明变量 ==== | ||
访问变量的语法形式为:<code>${var}</code> 和 <code>$var</code> 。 | |||
变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界,所以推荐加花括号。<syntaxhighlight lang="shell"> | |||
word="hello" | |||
echo ${word} | |||
# Output: hello | |||
</syntaxhighlight> | |||
==== 只读变量 ==== | ==== 只读变量 ==== | ||
使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变。<syntaxhighlight lang="shell"> | |||
rword="hello" | |||
echo ${rword} | |||
readonly rword | |||
# rword="bye" # 如果放开注释,执行时会报错 | |||
</syntaxhighlight> | |||
==== 删除变量 ==== | ==== 删除变量 ==== | ||
使用 unset 命令可以删除变量。变量被删除后不能再次使用。unset 命令不能删除只读变量。<syntaxhighlight lang="shell"> | |||
dword="hello" # 声明变量 | |||
echo ${dword} # 输出变量值 | |||
# Output: hello | |||
unset dword # 删除变量 | |||
echo ${dword} | |||
# Output: (空) | |||
</syntaxhighlight> | |||
==== 变量类型 ==== | ==== 变量类型 ==== | ||
* '''局部变量''' :局部变量是仅在某个脚本内部有效的变量。它们不能被其他的程序和脚本访问。 | |||
* '''环境变量''' :环境变量是对当前 shell 会话内所有的程序或脚本都可见的变量。创建它们跟创建局部变量类似,但使用的是 <code>export</code> 关键字,shell 脚本也可以定义环境变量。 | |||
常见的环境变量: | |||
{| class="wikitable" | |||
!变量 | |||
!描述 | |||
|- | |||
|<code>$HOME</code> | |||
|当前用户的用户目录 | |||
|- | |||
|<code>$PATH</code> | |||
|用分号分隔的目录列表,shell 会到这些目录中查找命令 | |||
|- | |||
|<code>$PWD</code> | |||
|当前工作目录 | |||
|- | |||
|<code>$RANDOM</code> | |||
|0 到 32767 之间的整数 | |||
|- | |||
|<code>$UID</code> | |||
|数值类型,当前用户的用户 ID | |||
|- | |||
|<code>$PS1</code> | |||
|主要系统输入提示符 | |||
|- | |||
|<code>$PS2</code> | |||
|次要系统输入提示符 | |||
|} | |||
=== 字符串 === | === 字符串 === | ||
==== 单引号和双引号 ==== | ==== 单引号和双引号 ==== | ||
shell 字符串可以用单引号 <code><nowiki>''</nowiki></code>,也可以用双引号 <code>“”</code>,也可以不用引号。 | |||
* 单引号的特点 | |||
** 单引号里不识别变量 | |||
** 单引号里不能出现单独的单引号(使用转义符也不行),但可成对出现,作为字符串拼接使用。 | |||
* 双引号的特点 | |||
** 双引号里识别变量 | |||
** 双引号里可以出现转义字符 | |||
综上,推荐使用双引号。 | |||
==== 拼接字符串 ==== | ==== 拼接字符串 ==== | ||
<syntaxhighlight lang="shell"> | |||
# 使用单引号拼接 | |||
name1='white' | |||
str1='hello, '${name1}'' | |||
str2='hello, ${name1}' | |||
echo ${str1}_${str2} | |||
# Output: | |||
# hello, white_hello, ${name1} | |||
# 使用双引号拼接 | |||
name2="black" | |||
str3="hello, "${name2}"" | |||
str4="hello, ${name2}" | |||
echo ${str3}_${str4} | |||
# Output: | |||
# hello, black_hello, black | |||
</syntaxhighlight> | |||
==== 获取字符串长度 ==== | ==== 获取字符串长度 ==== | ||
<syntaxhighlight lang="shell"> | |||
text="12345" | |||
echo ${#text} | |||
# Output: | |||
# 5 | |||
</syntaxhighlight> | |||
==== 截取子字符串 ==== | ==== 截取子字符串 ==== | ||
<syntaxhighlight lang="shell"> | |||
text="12345" | |||
echo ${text:2:2} | |||
# Output: | |||
# 34 | |||
</syntaxhighlight> | |||
==== 查找子字符串 ==== | ==== 查找子字符串 ==== | ||
<syntaxhighlight lang="shell"> | |||
text="hello" | |||
echo `expr index "${text}" ll` | |||
#查找 ll 子字符在 hello 字符串中的起始位置。 | |||
# Output: | |||
# 3 | |||
</syntaxhighlight> | |||
=== 数组 === | === 数组 === | ||
bash 只支持一维数组。数组下标从 0 开始,下标可以是整数或算术表达式,其值应大于或等于 0。 | |||
==== 创建数组 ==== | ==== 创建数组 ==== | ||
<syntaxhighlight lang="shell"> | |||
# 创建数组的不同方式 | |||
nums=([2]=2 [0]=0 [1]=1) | |||
colors=(red yellow "dark blue") | |||
</syntaxhighlight> | |||
==== 访问数组元素 ==== | ==== 访问数组元素 ==== | ||
* 访问数组的单个元素: | |||
<syntaxhighlight lang="shell"> | |||
echo ${nums[1]} | |||
# Output: 1 | |||
</syntaxhighlight> | |||
* 访问数组的所有元素: | |||
<syntaxhighlight lang="shell"> | |||
echo ${colors[*]} | |||
# Output: red yellow dark blue | |||
echo ${colors[@]} | |||
# Output: red yellow dark blue | |||
</syntaxhighlight> | |||
* 访问数组的部分元素: | |||
<syntaxhighlight lang="shell"> | |||
echo ${nums[@]:0:2} | |||
# Output: | |||
# 0 1 | |||
</syntaxhighlight>在上面的例子中,<code>${array[@]}</code> 扩展为整个数组,<code>:0:2</code>取出了数组中从 0 开始,长度为 2 的元素。 | |||
==== 访问数组长度 ==== | ==== 访问数组长度 ==== | ||
<syntaxhighlight lang="shell"> | |||
echo ${#nums[*]} | |||
# Output: | |||
# 3 | |||
</syntaxhighlight> | |||
==== 向数组中添加元素 ==== | ==== 向数组中添加元素 ==== | ||
<syntaxhighlight lang="shell"> | |||
colors=(white "${colors[@]}" green black) | |||
echo ${colors[@]} | |||
# Output: | |||
# white red yellow dark blue green black | |||
</syntaxhighlight>上面的例子中,<code>${colors[@]}</code> 扩展为整个数组,并被置换到复合赋值语句中,接着,对数组<code>colors</code>的赋值覆盖了它原来的值。 | |||
==== 从数组中删除元素 ==== | ==== 从数组中删除元素 ==== | ||
用<code>unset</code>命令来从数组中删除一个元素:<syntaxhighlight lang="shell"> | |||
unset nums[0] | |||
echo ${nums[@]} | |||
# Output: | |||
# 1 2 | |||
</syntaxhighlight> | |||
=== 运算符 === | === 运算符 === | ||
==== 算术运算符 ==== | ==== 算术运算符 ==== | ||
下表列出了常用的算术运算符,假定变量 x 为 10,变量 y 为 20: | |||
{| class="wikitable" | |||
!运算符 | |||
!说明 | |||
!举例 | |||
|- | |||
| + | |||
|加法 | |||
|<code>expr $x + $y</code> 结果为 30。 | |||
|- | |||
| - | |||
|减法 | |||
|<code>expr $x - $y</code> 结果为 -10。 | |||
|- | |||
|* | |||
|乘法 | |||
|<code>expr $x * $y</code> 结果为 200。 | |||
|- | |||
|/ | |||
|除法 | |||
|<code>expr $y / $x</code> 结果为 2。 | |||
|- | |||
|% | |||
|取余 | |||
|<code>expr $y % $x</code> 结果为 0。 | |||
|- | |||
|= | |||
|赋值 | |||
|<code>x=$y</code> 将把变量 y 的值赋给 x。 | |||
|- | |||
|== | |||
|相等。用于比较两个数字,相同则返回 true。 | |||
|<code>[ $x == $y ]</code> 返回 false。 | |||
|- | |||
|!= | |||
|不相等。用于比较两个数字,不相同则返回 true。 | |||
|<code>[ $x != $y ]</code> 返回 true。 | |||
|} | |||
'''注意:'''条件表达式要放在方括号之间,并且要有空格,例如: <code>[$x==$y]</code> 是错误的,必须写成 <code>[ $x == $y ]</code>。 | |||
==== 关系运算符 ==== | ==== 关系运算符 ==== | ||
关系运算符只支持数字,不支持字符串,除非字符串的值是数字。 | |||
下表列出了常用的关系运算符,假定变量 x 为 10,变量 y 为 20: | |||
{| class="wikitable" | |||
!运算符 | |||
!说明 | |||
!举例 | |||
|- | |||
|<code>-eq</code> | |||
|检测两个数是否相等,相等返回 true。 | |||
|<code>[ $a -eq $b ]</code>返回 false。 | |||
|- | |||
|<code>-ne</code> | |||
|检测两个数是否相等,不相等返回 true。 | |||
|<code>[ $a -ne $b ]</code> 返回 true。 | |||
|- | |||
|<code>-gt</code> | |||
|检测左边的数是否大于右边的,如果是,则返回 true。 | |||
|<code>[ $a -gt $b ]</code> 返回 false。 | |||
|- | |||
|<code>-lt</code> | |||
|检测左边的数是否小于右边的,如果是,则返回 true。 | |||
|<code>[ $a -lt $b ]</code> 返回 true。 | |||
|- | |||
|<code>-ge</code> | |||
|检测左边的数是否大于等于右边的,如果是,则返回 true。 | |||
|<code>[ $a -ge $b ]</code> 返回 false。 | |||
|- | |||
|<code>-le</code> | |||
|检测左边的数是否小于等于右边的,如果是,则返回 true。 | |||
|<code>[ $a -le $b ]</code>返回 true。 | |||
|} | |||
==== 布尔运算符 ==== | ==== 布尔运算符 ==== | ||
下表列出了常用的布尔运算符,假定变量 x 为 10,变量 y 为 20: | |||
{| class="wikitable" | |||
!运算符 | |||
!说明 | |||
!举例 | |||
|- | |||
|<code>!</code> | |||
|非运算,表达式为 true 则返回 false,否则返回 true。 | |||
|<code>[ ! false ]</code> 返回 true。 | |||
|- | |||
|<code>-o</code> | |||
|或运算,有一个表达式为 true 则返回 true。 | |||
|<code>[ $a -lt 20 -o $b -gt 100 ]</code> 返回 true。 | |||
|- | |||
|<code>-a</code> | |||
|与运算,两个表达式都为 true 才返回 true。 | |||
|<code>[ $a -lt 20 -a $b -gt 100 ]</code> 返回 false。 | |||
|} | |||
==== 逻辑运算符 ==== | ==== 逻辑运算符 ==== | ||
以下介绍 Shell 的逻辑运算符,假定变量 x 为 10,变量 y 为 20: | |||
{| class="wikitable" | |||
!运算符 | |||
!说明 | |||
!举例 | |||
|- | |||
|<code>&&</code> | |||
|逻辑的 AND | |||
|<code>[[ ${x} -lt 100 && ${y} -gt 100 ]]</code> 返回 false | |||
|- | |||
|<code><nowiki>||</nowiki></code> | |||
|逻辑的 OR | |||
|<code><nowiki>[[ ${x} -lt 100 || ${y} -gt 100 ]]</nowiki></code> 返回 true | |||
|} | |||
==== 字符串运算符 ==== | ==== 字符串运算符 ==== | ||
下表列出了常用的字符串运算符,假定变量 a 为 "abc",变量 b 为 "efg": | |||
{| class="wikitable" | |||
!运算符 | |||
!说明 | |||
!举例 | |||
|- | |||
|<code>=</code> | |||
|检测两个字符串是否相等,相等返回 true。 | |||
|<code>[ $a = $b ]</code> 返回 false。 | |||
|- | |||
|<code>!=</code> | |||
|检测两个字符串是否相等,不相等返回 true。 | |||
|<code>[ $a != $b ]</code> 返回 true。 | |||
|- | |||
|<code>-z</code> | |||
|检测字符串长度是否为 0,为 0 返回 true。 | |||
|<code>[ -z $a ]</code> 返回 false。 | |||
|- | |||
|<code>-n</code> | |||
|检测字符串长度是否为 0,不为 0 返回 true。 | |||
|<code>[ -n $a ]</code> 返回 true。 | |||
|- | |||
|<code>str</code> | |||
|检测字符串是否为空,不为空返回 true。 | |||
|<code>[ $a ]</code> 返回 true。 | |||
|} | |||
==== 文件测试运算符 ==== | ==== 文件测试运算符 ==== | ||
文件测试运算符用于检测 Unix 文件的各种属性。 | |||
属性检测描述如下: | |||
{| class="wikitable" | |||
!操作符 | |||
!说明 | |||
!举例 | |||
|- | |||
| -b file | |||
|检测文件是否是块设备文件,如果是,则返回 true。 | |||
|<code>[ -b $file ]</code> 返回 false。 | |||
|- | |||
| -c file | |||
|检测文件是否是字符设备文件,如果是,则返回 true。 | |||
|<code>[ -c $file ]</code> 返回 false。 | |||
|- | |||
| -d file | |||
|检测文件是否是目录,如果是,则返回 true。 | |||
|<code>[ -d $file ]</code> 返回 false。 | |||
|- | |||
| -f file | |||
|检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。 | |||
|<code>[ -f $file ]</code> 返回 true。 | |||
|- | |||
| -g file | |||
|检测文件是否设置了 SGID 位,如果是,则返回 true。 | |||
|<code>[ -g $file ]</code> 返回 false。 | |||
|- | |||
| -k file | |||
|检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。 | |||
|<code>[ -k $file ]</code>返回 false。 | |||
|- | |||
| -p file | |||
|检测文件是否是有名管道,如果是,则返回 true。 | |||
|<code>[ -p $file ]</code> 返回 false。 | |||
|- | |||
| -u file | |||
|检测文件是否设置了 SUID 位,如果是,则返回 true。 | |||
|<code>[ -u $file ]</code> 返回 false。 | |||
|- | |||
| -r file | |||
|检测文件是否可读,如果是,则返回 true。 | |||
|<code>[ -r $file ]</code> 返回 true。 | |||
|- | |||
| -w file | |||
|检测文件是否可写,如果是,则返回 true。 | |||
|<code>[ -w $file ]</code> 返回 true。 | |||
|- | |||
| -x file | |||
|检测文件是否可执行,如果是,则返回 true。 | |||
|<code>[ -x $file ]</code> 返回 true。 | |||
|- | |||
| -s file | |||
|检测文件是否为空(文件大小是否大于 0),不为空返回 true。 | |||
|<code>[ -s $file ]</code> 返回 true。 | |||
|- | |||
| -e file | |||
|检测文件(包括目录)是否存在,如果是,则返回 true。 | |||
|<code>[ -e $file ]</code> 返回 true。 | |||
|} | |||
=== 控制语句 === | === 控制语句 === | ||
第72行: | 第555行: | ||
==== if ==== | ==== if ==== | ||
<code>if</code>在使用上跟其它语言相同。如果中括号里的表达式为真,那么<code>then</code>和<code>fi</code>之间的代码会被执行。<code>fi</code>标志着条件代码块的结束。<syntaxhighlight lang="shell"> | |||
# 写成一行 | |||
if [[ 1 -eq 1 ]]; then echo "1 -eq 1 result is: true"; fi | |||
# Output: 1 -eq 1 result is: true | |||
# 写成多行 | |||
if [[ "abc" -eq "abc" ]] | |||
then | |||
echo ""abc" -eq "abc" result is: true" | |||
fi | |||
# Output: abc -eq abc result is: true | |||
</syntaxhighlight> | |||
==== if else ==== | ==== if else ==== | ||
同样,我们可以使用<code>if..else</code>语句,例如:<syntaxhighlight lang="shell"> | |||
if [[ 2 -ne 1 ]]; then | |||
echo "true" | |||
else | |||
echo "false" | |||
fi | |||
# Output: true | |||
</syntaxhighlight> | |||
==== if elif else ==== | ==== if elif else ==== | ||
有些时候,当<code>if..else</code>不能满足我们的要求。还可以使用<code>if..elif..else</code>,例如:<syntaxhighlight lang="shell"> | |||
x=10 | |||
y=20 | |||
if [[ ${x} > ${y} ]]; then | |||
echo "${x} > ${y}" | |||
elif [[ ${x} < ${y} ]]; then | |||
echo "${x} < ${y}" | |||
else | |||
echo "${x} = ${y}" | |||
fi | |||
# Output: 10 < 20 | |||
</syntaxhighlight> | |||
==== case ==== | ==== case ==== | ||
如果你需要面对很多情况,分别要采取不同的措施,那么使用<code>case</code>会比嵌套的<code>if</code>更有用。使用<code>case</code>来解决复杂的条件判断,例如:<syntaxhighlight lang="shell"> | |||
exec | |||
case ${oper} in | |||
"+") | |||
val=`expr ${x} + ${y}` | |||
echo "${x} + ${y} = ${val}" | |||
;; | |||
"-") | |||
val=`expr ${x} - ${y}` | |||
echo "${x} - ${y} = ${val}" | |||
;; | |||
"*") | |||
val=`expr ${x} \* ${y}` | |||
echo "${x} * ${y} = ${val}" | |||
;; | |||
"/") | |||
val=`expr ${x} / ${y}` | |||
echo "${x} / ${y} = ${val}" | |||
;; | |||
*) | |||
echo "Unknown oper!" | |||
;; | |||
esac | |||
</syntaxhighlight> | |||
=== 循环语句 === | === 循环语句 === | ||
Bash 中有四种循环:<code>for</code>,<code>while</code>,<code>until</code>和<code>select</code>。 | |||
==== for循环 ==== | ==== for循环 ==== | ||
<syntaxhighlight lang="shell"> | |||
for arg in elem1 elem2 ... elemN | |||
do | |||
### 语句 | |||
done | |||
</syntaxhighlight>在每次循环的过程中,<code>arg</code>依次被赋值为从<code>elem1</code>到<code>elemN</code>。这些值还可以是通配符或者大括号扩展。 | |||
我们还可以把<code>for</code>循环写在一行,但这要求<code>do</code>之前要有一个分号,例如:<syntaxhighlight lang="shell"> | |||
for i in {1..5}; do echo $i; done | |||
</syntaxhighlight>也可以像 C 语言那样使用<code>for</code>,比如:<syntaxhighlight lang="shell"> | |||
for (( i = 0; i < 10; i++ )); do | |||
echo $i | |||
done | |||
</syntaxhighlight>当我们想对一个目录下的所有文件做同样的操作时,<code>for</code>就很方便了。举个例子,如果我们想把所有的<code>.bash</code>文件移动到<code>script</code>文件夹中,我们的脚本可以这样写:<syntaxhighlight lang="shell"> | |||
DIR=/home/xiaoming | |||
for FILE in ${DIR}/*.sh; do | |||
mv "$FILE" "${DIR}/scripts" | |||
done | |||
# 将 /home/xiaoming 目录下所有 sh 文件拷贝到 /home/xiaoming/scripts | |||
</syntaxhighlight> | |||
==== while循环 ==== | ==== while循环 ==== | ||
<code>while</code>循环检测一个条件,只要这个条件为 真,就执行一段命令。被检测的条件跟<code>if..then</code>中使用的基元并无二异。因此一个<code>while</code>循环看起来会是这样: | |||
<syntaxhighlight lang="shell"> | |||
while [[ condition ]] | |||
do | |||
### 语句 | |||
done | |||
</syntaxhighlight> | |||
跟<code>for</code>循环一样,如果我们把<code>do</code>和被检测的条件写到一行,那么必须要在<code>do</code>之前加一个分号。 | |||
比如下面这个例子: | |||
<syntaxhighlight lang="shell"> | |||
### 0到9之间每个数的平方 | |||
x=0 | |||
while [[ ${x} -lt 10 ]]; do | |||
echo $((x * x)) | |||
x=$((x + 1)) | |||
done | |||
# Output: | |||
# 0 | |||
# 1 | |||
# 4 | |||
# 9 | |||
# 16 | |||
# 25 | |||
# 36 | |||
# 49 | |||
# 64 | |||
# 81 | |||
</syntaxhighlight> | |||
==== until循环 ==== | |||
<code>until</code>循环跟<code>while</code>循环正好相反。它跟<code>while</code>一样也需要检测一个测试条件,但不同的是,只要该条件为 假 就一直执行循环: | |||
<syntaxhighlight lang="shell"> | |||
x=0 | |||
until [[ ${x} -ge 5 ]]; do | |||
echo ${x} | |||
x=`expr ${x} + 1` | |||
done | |||
# Output: | |||
# 0 | |||
# 1 | |||
# 2 | |||
# 3 | |||
# 4 | |||
</syntaxhighlight> | |||
==== select in 循环 ==== | |||
select in 循环用来增强交互性,它可以显示出带编号的菜单,用户输入不同的编号就可以选择不同的菜单,并执行不同的功能。 | |||
==== | select in 是 Shell 独有的一种循环,非常适合终端(Terminal)这样的交互场景,C语言、C++、Java、Python、C# 等其它编程语言中是没有的。 | ||
Shell select in 循环的用法如下: | |||
<syntaxhighlight lang="text"> | |||
select variable in value_list | |||
do | |||
statements | |||
done | |||
</syntaxhighlight> | |||
variable 表示变量,value_list 表示取值列表,in 是 Shell 中的关键字。你看,select in 和 for in 的语法是多么地相似。 | |||
我们先来看一个 select in 循环的例子: | |||
<syntaxhighlight lang="shell"> | |||
#!/bin/bash | |||
echo "What is your favourite OS?" | |||
select name in "Linux" "Windows" "Mac OS" "UNIX" "Android" | |||
do | |||
echo $name | |||
done | |||
echo "You have selected $name" | |||
</syntaxhighlight> | |||
运行结果: | |||
<syntaxhighlight lang="text"> | |||
What is your favourite OS? | |||
1) Linux | |||
2) Windows | |||
3) Mac OS | |||
4) UNIX | |||
5) Android | |||
#? 4↙ | |||
You have selected UNIX | |||
#? 1↙ | |||
You have selected Linux | |||
#? 9↙ | |||
You have selected | |||
#? 2↙ | |||
You have selected Windows | |||
#?^D | |||
</syntaxhighlight> | |||
<code>#?</code>用来提示用户输入菜单编号;<code>^D</code>表示按下 Ctrl+D 组合键,它的作用是结束 select in 循环。 | |||
运行到 select 语句后,取值列表 value_list 中的内容会以菜单的形式显示出来,用户输入菜单编号,就表示选中了某个值,这个值就会赋给变量 variable,然后再执行循环体中的 statements(do 和 done 之间的部分)。 | |||
每次循环时 select 都会要求用户输入菜单编号,并使用环境变量 PS3 的值作为提示符,PS3 的默认值为#?,修改 PS3 的值就可以修改提示符。 | |||
如果用户输入的菜单编号不在范围之内,例如上面我们输入的 9,那么就会给 variable 赋一个空值;如果用户输入一个空值(什么也不输入,直接回车),会重新显示一遍菜单。 | |||
注意,select 是无限循环(死循环),输入空值,或者输入的值无效,都不会结束循环,只有遇到 break 语句,或者按下 Ctrl+D 组合键才能结束循环。 | |||
完整实例 | |||
select in 通常和 case in 一起使用,在用户输入不同的编号时可以做出不同的反应。 | |||
修改上面的代码,加入 case in 语句: | |||
<syntaxhighlight lang="shell"> | |||
#!/bin/bash | |||
echo "What is your favourite OS?" | |||
select name in "Linux" "Windows" "Mac OS" "UNIX" "Android" | |||
do | |||
case $name in | |||
"Linux") | |||
echo "Linux是一个类UNIX操作系统,它开源免费,运行在各种服务器设备和嵌入式设备。" | |||
break | |||
;; | |||
"Windows") | |||
echo "Windows是微软开发的个人电脑操作系统,它是闭源收费的。" | |||
break | |||
;; | |||
"Mac OS") | |||
echo "Mac OS是苹果公司基于UNIX开发的一款图形界面操作系统,只能运行与苹果提供的硬件之上。" | |||
break | |||
;; | |||
"UNIX") | |||
echo "UNIX是操作系统的开山鼻祖,现在已经逐渐退出历史舞台,只应用在特殊场合。" | |||
break | |||
;; | |||
"Android") | |||
echo "Android是由Google开发的手机操作系统,目前已经占据了70%的市场份额。" | |||
break | |||
;; | |||
*) | |||
echo "输入错误,请重新输入" | |||
esac | |||
done | |||
</syntaxhighlight> | |||
用户只有输入正确的编号才会结束循环,如果输入错误,会要求重新输入。 | |||
运行结果1,输入正确选项: | |||
<syntaxhighlight lang="text"> | |||
What is your favourite OS? | |||
1) Linux | |||
2) Windows | |||
3) Mac OS | |||
4) UNIX | |||
5) Android | |||
#? 2 | |||
Windows是微软开发的个人电脑操作系统,它是闭源收费的。 | |||
</syntaxhighlight> | |||
运行结果2,输入错误选项: | |||
<syntaxhighlight lang="text"> | |||
What is your favourite OS? | |||
1) Linux | |||
2) Windows | |||
3) Mac OS | |||
4) UNIX | |||
5) Android | |||
#? 7 | |||
输入错误,请重新输入 | |||
#? 4 | |||
UNIX是操作系统的开山鼻祖,现在已经逐渐退出历史舞台,只应用在特殊场合。 | |||
</syntaxhighlight> | |||
= | 运行结果3,输入空值: | ||
<syntaxhighlight lang="text"> | |||
What is your favourite OS? | |||
1) Linux | |||
2) Windows | |||
3) Mac OS | |||
4) UNIX | |||
5) Android | |||
#? | |||
1) Linux | |||
2) Windows | |||
3) Mac OS | |||
4) UNIX | |||
5) Android | |||
#? 3 | |||
Mac OS是苹果公司基于UNIX开发的一款图形界面操作系统,只能运行与苹果提供的硬件之上。 | |||
</syntaxhighlight> | |||
==== break和continue ==== | ==== break和continue ==== | ||
如果想提前结束一个循环或跳过某次循环执行,可以使用 shell 的<code>break</code>和<code>continue</code>语句来实现。它们可以在任何循环中使用。 | |||
<code>break</code>语句用来提前结束当前循环。 | |||
<code>continue</code>语句用来跳过某次迭代。 | |||
<syntaxhighlight lang="shell"> | |||
# 查找 10 以内第一个能整除 2 和 3 的正整数 | |||
i=1 | |||
while [[ ${i} -lt 10 ]]; do | |||
if [[ $((i % 3)) -eq 0 ]] && [[ $((i % 2)) -eq 0 ]]; then | |||
echo ${i} | |||
break; | |||
fi | |||
i=`expr ${i} + 1` | |||
done | |||
# Output: 6 | |||
</syntaxhighlight> | |||
<syntaxhighlight lang="shell"> | |||
# 打印10以内的奇数 | |||
for (( i = 0; i < 10; i ++ )); do | |||
if [[ $((i % 2)) -eq 0 ]]; then | |||
continue; | |||
fi | |||
echo ${i} | |||
done | |||
# Output: | |||
# 1 | |||
# 3 | |||
# 5 | |||
# 7 | |||
# 9 | |||
</syntaxhighlight> | |||
=== 函数 === | === 函数 === | ||
bash 函数定义语法如下:<syntaxhighlight lang="shell"> | |||
[ function ] funname [()] { | |||
action; | |||
[return int;] | |||
} | |||
</syntaxhighlight><blockquote>'''说明:''' | |||
# 函数定义时,<code>function</code> 关键字可有可无。 | |||
# 函数返回值 - return 返回函数返回值,返回值类型只能为整数(0-255)。如果不加 return 语句,shell 默认将以最后一条命令的运行结果,作为函数返回值。 | |||
# 函数返回值在调用该函数后通过 <code>$?</code> 来获得。 | |||
# 所有函数在使用前必须定义。这意味着必须将函数放在脚本开始部分,直至 shell 解释器首次发现它时,才可以使用。调用函数仅使用其函数名即可。 | |||
</blockquote><syntaxhighlight lang="shell"> | |||
#!/usr/bin/env bash | |||
calc(){ | |||
PS3="choose the oper: " | |||
select oper in + - \* / # 生成操作符选择菜单 | |||
do | |||
echo -n "enter first num: " && read x # 读取输入参数 | |||
echo -n "enter second num: " && read y # 读取输入参数 | |||
exec | |||
case ${oper} in | |||
"+") | |||
return $((${x} + ${y})) | |||
;; | |||
"-") | |||
return $((${x} - ${y})) | |||
;; | |||
"*") | |||
return $((${x} * ${y})) | |||
;; | |||
"/") | |||
return $((${x} / ${y})) | |||
;; | |||
*) | |||
echo "${oper} is not support!" | |||
return 0 | |||
;; | |||
esac | |||
break | |||
done | |||
} | |||
calc | |||
echo "the result is: $?" # $? 获取 calc 函数返回值 | |||
</syntaxhighlight>执行结果:<syntaxhighlight lang="shell"> | |||
$ ./function-demo.sh | |||
1) + | |||
2) - | |||
3) * | |||
4) / | |||
choose the oper: 3 | |||
enter first num: 10 | |||
enter second num: 10 | |||
the result is: 100 | |||
</syntaxhighlight> | |||
==== 位置参数 ==== | ==== 位置参数 ==== | ||
位置参数是在调用一个函数并传给它参数时创建的变量。 | |||
位置参数变量表: | |||
{| class="wikitable" | |||
!变量 | |||
!描述 | |||
|- | |||
|<code>$0</code> | |||
|脚本名称 | |||
|- | |||
|<code>$1 … $9</code> | |||
|第 1 个到第 9 个参数列表 | |||
|- | |||
|<code>${10} … ${N}</code> | |||
|第 10 个到 N 个参数列表 | |||
|- | |||
|<code>$*</code> or <code>$@</code> | |||
|除了<code>$0</code>外的所有位置参数 | |||
|- | |||
|<code>$#</code> | |||
|不包括<code>$0</code>在内的位置参数的个数 | |||
|- | |||
|<code>$FUNCNAME</code> | |||
|函数名称(仅在函数内部有值) | |||
|} | |||
<syntaxhighlight lang="shell"> | |||
#!/usr/bin/env bash | |||
x=0 | |||
if [[ -n $1 ]]; then | |||
echo "第一个参数为:$1" | |||
x=$1 | |||
else | |||
echo "第一个参数为空" | |||
fi | |||
y=0 | |||
if [[ -n $2 ]]; then | |||
echo "第二个参数为:$2" | |||
y=$2 | |||
else | |||
echo "第二个参数为空" | |||
fi | |||
paramsFunction(){ | |||
echo "函数第一个入参:$1" | |||
echo "函数第二个入参:$2" | |||
} | |||
paramsFunction ${x} ${y} | |||
</syntaxhighlight>执行结果:<syntaxhighlight lang="shell"> | |||
$ ./function-demo2.sh | |||
第一个参数为空 | |||
第二个参数为空 | |||
函数第一个入参:0 | |||
函数第二个入参:0 | |||
$ ./function-demo2.sh 10 20 | |||
第一个参数为:10 | |||
第二个参数为:20 | |||
函数第一个入参:10 | |||
函数第二个入参:20 | |||
</syntaxhighlight>执行 <code>./variable-demo4.sh hello world</code> ,然后在脚本中通过 <code>$1</code>、<code>$2</code> ... 读取第 1 个参数、第 2 个参数。。。 | |||
==== 函数处理参数 ==== | ==== 函数处理参数 ==== | ||
另外,还有几个特殊字符用来处理参数: | |||
{| class="wikitable" | |||
!参数处理 | |||
!说明 | |||
|- | |||
|<code>$#</code> | |||
|返回参数个数 | |||
|- | |||
|<code>$*</code> | |||
|返回所有参数 | |||
|- | |||
|<code>?</code> | |||
|脚本运行的当前进程 ID 号 | |||
|- | |||
|<code>$!</code> | |||
|后台运行的最后一个进程的 ID 号 | |||
|- | |||
|<code>$@</code> | |||
|返回所有参数 | |||
|- | |||
|<code>$-</code> | |||
|返回 Shell 使用的当前选项,与 set 命令功能相同。 | |||
|- | |||
|<code>$?</code> | |||
|函数返回值 | |||
|} | |||
<syntaxhighlight lang="shell"> | |||
runner() { | |||
return 0 | |||
} | |||
name=xiaoming | |||
paramsFunction(){ | |||
echo "函数第一个入参:$1" | |||
echo "函数第二个入参:$2" | |||
echo "传递到脚本的参数个数:$#" | |||
echo "所有参数:" | |||
printf "+ %s\n" "$*" | |||
echo "脚本运行的当前进程 ID 号:?" | |||
echo "后台运行的最后一个进程的 ID 号:$!" | |||
echo "所有参数:" | |||
printf "+ %s\n" "$@" | |||
echo "Shell 使用的当前选项:$-" | |||
runner | |||
echo "runner 函数的返回值:$?" | |||
} | |||
paramsFunction 1 "abc" "hello, \"xiaoming\"" | |||
# Output: | |||
# 函数第一个入参:1 | |||
# 函数第二个入参:abc | |||
# 传递到脚本的参数个数:3 | |||
# 所有参数: | |||
# + 1 abc hello, "xiaoming" | |||
# 脚本运行的当前进程 ID 号:26400 | |||
# 后台运行的最后一个进程的 ID 号: | |||
# 所有参数: | |||
# + 1 | |||
# + abc | |||
# + hello, "xiaoming" | |||
# Shell 使用的当前选项:hB | |||
# runner 函数的返回值:0 | |||
</syntaxhighlight> |
2021年12月1日 (三) 00:23的最新版本
简介
什么是 shell
- Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁。
- Shell 既是一种命令语言,又是一种程序设计语言。
- Shell 是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问 Linux 内核的服务。
什么是 shell 脚本
Shell 脚本(shell script),是一种为 shell 编写的脚本程序,一般文件后缀为 .sh
。
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
source /path/to/script.sh
./path/to/script.sh
上面的例子中,script.sh
是一个包含 shell 解释器可以识别并执行的命令的普通文本文件,sh
和bash
是 shell 解释器程序。你可以使用任何喜欢的编辑器创建script.sh
(vim,nano,Sublime Text, Atom 等等)。
其中,source /path/to/script.sh
和 ./path/to/script.sh
是等价的。
除此之外,你还可以通过chmod
命令给文件添加可执行的权限,来直接执行脚本文件:
chmod +x /path/to/script.sh #使脚本具有执行权限
/path/to/test.sh
这种方式要求脚本文件的第一行必须指明运行该脚本的程序,比如:
#!/usr/bin/env bash
echo "Hello, world!"
使用echo
命令将字符串输出到屏幕上。
基本语法
解释器
#!
决定了脚本可以像一个独立的可执行文件一样执行,而不用在终端之前输入sh
, bash
, python
, php
等。
# 以下两种方式都可以指定 shell 解释器为 bash,第二种方式更好
#!/bin/bash
#!/usr/bin/env bash
注释
注释可以说明你的代码是什么作用,以及为什么这样写。
shell 语法中,注释是特殊的语句,会被 shell 解释器忽略。
- 单行注释 - 以
#
开头,到行尾结束。 - 多行注释 - 以
:<<EOF
开头,到EOF
结束。
#--------------------------------------------
# shell 注释示例
# author:xiaoming
#--------------------------------------------
# echo '这是单行注释'
########## 这是分割线 ##########
:<<EOF
echo '这是多行注释'
echo '这是多行注释'
echo '这是多行注释'
EOF
echo
echo 用于字符串的输出。
- 输出普通字符串:
echo "hello, world" # Output: hello, world
- 输出含变量的字符串:
name=xiaoming echo "hello, \"${name}\"" # Output: hello, "xiaoming"
- 输出含换行符的字符串:
# 输出含换行符的字符串 echo "YES\nNO" # Output: YES\nNO echo -e "YES\nNO" # -e 开启转义 # Output: # YES # NO
- 输出含不换行符的字符串:
echo "YES" echo "NO" # Output: # YES # NO echo -e "YES\c" # -e 开启转义 \c 不换行 echo "NO" # Output: # YESNO
- 输出重定向至文件:
echo "test" > test.txt
- 输出执行结果:
echo `pwd` # Output:(当前目录路径)
printf
printf 用于格式化输出字符串。
printf 命令的语法:
printf format-string [arguments...]
参数说明:
- format-string: 为格式控制字符串
- arguments: 为参数列表。
默认,printf 不会像 echo 一样自动添加换行符,如果需要换行可以手动添加 \n
。
变量
跟许多程序设计语言一样,你可以在 bash 中创建变量。
bash 中没有数据类型,bash 中的变量可以保存一个数字、一个字符、一个字符串等等。同时无需提前声明变量,给变量赋值会直接创建变量。
变量命名原则
- 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头。
- 中间不能有空格,可以使用下划线(_)。
- 不能使用标点符号。
- 不能使用 bash 里的关键字(可用 help 命令查看保留关键字)。
声明变量
访问变量的语法形式为:${var}
和 $var
。
变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界,所以推荐加花括号。
word="hello"
echo ${word}
# Output: hello
只读变量
使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变。
rword="hello"
echo ${rword}
readonly rword
# rword="bye" # 如果放开注释,执行时会报错
删除变量
使用 unset 命令可以删除变量。变量被删除后不能再次使用。unset 命令不能删除只读变量。
dword="hello" # 声明变量
echo ${dword} # 输出变量值
# Output: hello
unset dword # 删除变量
echo ${dword}
# Output: (空)
变量类型
- 局部变量 :局部变量是仅在某个脚本内部有效的变量。它们不能被其他的程序和脚本访问。
- 环境变量 :环境变量是对当前 shell 会话内所有的程序或脚本都可见的变量。创建它们跟创建局部变量类似,但使用的是
export
关键字,shell 脚本也可以定义环境变量。
常见的环境变量:
变量 | 描述 |
---|---|
$HOME
|
当前用户的用户目录 |
$PATH
|
用分号分隔的目录列表,shell 会到这些目录中查找命令 |
$PWD
|
当前工作目录 |
$RANDOM
|
0 到 32767 之间的整数 |
$UID
|
数值类型,当前用户的用户 ID |
$PS1
|
主要系统输入提示符 |
$PS2
|
次要系统输入提示符 |
字符串
单引号和双引号
shell 字符串可以用单引号 ''
,也可以用双引号 “”
,也可以不用引号。
- 单引号的特点
- 单引号里不识别变量
- 单引号里不能出现单独的单引号(使用转义符也不行),但可成对出现,作为字符串拼接使用。
- 双引号的特点
- 双引号里识别变量
- 双引号里可以出现转义字符
综上,推荐使用双引号。
拼接字符串
# 使用单引号拼接
name1='white'
str1='hello, '${name1}''
str2='hello, ${name1}'
echo ${str1}_${str2}
# Output:
# hello, white_hello, ${name1}
# 使用双引号拼接
name2="black"
str3="hello, "${name2}""
str4="hello, ${name2}"
echo ${str3}_${str4}
# Output:
# hello, black_hello, black
获取字符串长度
text="12345"
echo ${#text}
# Output:
# 5
截取子字符串
text="12345"
echo ${text:2:2}
# Output:
# 34
查找子字符串
text="hello"
echo `expr index "${text}" ll`
#查找 ll 子字符在 hello 字符串中的起始位置。
# Output:
# 3
数组
bash 只支持一维数组。数组下标从 0 开始,下标可以是整数或算术表达式,其值应大于或等于 0。
创建数组
# 创建数组的不同方式
nums=([2]=2 [0]=0 [1]=1)
colors=(red yellow "dark blue")
访问数组元素
- 访问数组的单个元素:
echo ${nums[1]}
# Output: 1
- 访问数组的所有元素:
echo ${colors[*]}
# Output: red yellow dark blue
echo ${colors[@]}
# Output: red yellow dark blue
- 访问数组的部分元素:
echo ${nums[@]:0:2}
# Output:
# 0 1
在上面的例子中,${array[@]}
扩展为整个数组,:0:2
取出了数组中从 0 开始,长度为 2 的元素。
访问数组长度
echo ${#nums[*]}
# Output:
# 3
向数组中添加元素
colors=(white "${colors[@]}" green black)
echo ${colors[@]}
# Output:
# white red yellow dark blue green black
上面的例子中,${colors[@]}
扩展为整个数组,并被置换到复合赋值语句中,接着,对数组colors
的赋值覆盖了它原来的值。
从数组中删除元素
用unset
命令来从数组中删除一个元素:
unset nums[0]
echo ${nums[@]}
# Output:
# 1 2
运算符
算术运算符
下表列出了常用的算术运算符,假定变量 x 为 10,变量 y 为 20:
运算符 | 说明 | 举例 |
---|---|---|
+ | 加法 | expr $x + $y 结果为 30。
|
- | 减法 | expr $x - $y 结果为 -10。
|
* | 乘法 | expr $x * $y 结果为 200。
|
/ | 除法 | expr $y / $x 结果为 2。
|
% | 取余 | expr $y % $x 结果为 0。
|
= | 赋值 | x=$y 将把变量 y 的值赋给 x。
|
== | 相等。用于比较两个数字,相同则返回 true。 | [ $x == $y ] 返回 false。
|
!= | 不相等。用于比较两个数字,不相同则返回 true。 | [ $x != $y ] 返回 true。
|
注意:条件表达式要放在方括号之间,并且要有空格,例如: [$x==$y]
是错误的,必须写成 [ $x == $y ]
。
关系运算符
关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
下表列出了常用的关系运算符,假定变量 x 为 10,变量 y 为 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。
|
布尔运算符
下表列出了常用的布尔运算符,假定变量 x 为 10,变量 y 为 20:
运算符 | 说明 | 举例 |
---|---|---|
!
|
非运算,表达式为 true 则返回 false,否则返回 true。 | [ ! false ] 返回 true。
|
-o
|
或运算,有一个表达式为 true 则返回 true。 | [ $a -lt 20 -o $b -gt 100 ] 返回 true。
|
-a
|
与运算,两个表达式都为 true 才返回 true。 | [ $a -lt 20 -a $b -gt 100 ] 返回 false。
|
逻辑运算符
以下介绍 Shell 的逻辑运算符,假定变量 x 为 10,变量 y 为 20:
运算符 | 说明 | 举例 |
---|---|---|
&&
|
逻辑的 AND | [[ ${x} -lt 100 && ${y} -gt 100 ]] 返回 false
|
||
|
逻辑的 OR | [[ ${x} -lt 100 || ${y} -gt 100 ]] 返回 true
|
字符串运算符
下表列出了常用的字符串运算符,假定变量 a 为 "abc",变量 b 为 "efg":
运算符 | 说明 | 举例 |
---|---|---|
=
|
检测两个字符串是否相等,相等返回 true。 | [ $a = $b ] 返回 false。
|
!=
|
检测两个字符串是否相等,不相等返回 true。 | [ $a != $b ] 返回 true。
|
-z
|
检测字符串长度是否为 0,为 0 返回 true。 | [ -z $a ] 返回 false。
|
-n
|
检测字符串长度是否为 0,不为 0 返回 true。 | [ -n $a ] 返回 true。
|
str
|
检测字符串是否为空,不为空返回 true。 | [ $a ] 返回 true。
|
文件测试运算符
文件测试运算符用于检测 Unix 文件的各种属性。
属性检测描述如下:
操作符 | 说明 | 举例 |
---|---|---|
-b file | 检测文件是否是块设备文件,如果是,则返回 true。 | [ -b $file ] 返回 false。
|
-c file | 检测文件是否是字符设备文件,如果是,则返回 true。 | [ -c $file ] 返回 false。
|
-d file | 检测文件是否是目录,如果是,则返回 true。 | [ -d $file ] 返回 false。
|
-f file | 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。 | [ -f $file ] 返回 true。
|
-g file | 检测文件是否设置了 SGID 位,如果是,则返回 true。 | [ -g $file ] 返回 false。
|
-k file | 检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。 | [ -k $file ] 返回 false。
|
-p file | 检测文件是否是有名管道,如果是,则返回 true。 | [ -p $file ] 返回 false。
|
-u file | 检测文件是否设置了 SUID 位,如果是,则返回 true。 | [ -u $file ] 返回 false。
|
-r file | 检测文件是否可读,如果是,则返回 true。 | [ -r $file ] 返回 true。
|
-w file | 检测文件是否可写,如果是,则返回 true。 | [ -w $file ] 返回 true。
|
-x file | 检测文件是否可执行,如果是,则返回 true。 | [ -x $file ] 返回 true。
|
-s file | 检测文件是否为空(文件大小是否大于 0),不为空返回 true。 | [ -s $file ] 返回 true。
|
-e file | 检测文件(包括目录)是否存在,如果是,则返回 true。 | [ -e $file ] 返回 true。
|
控制语句
条件语句
if
if
在使用上跟其它语言相同。如果中括号里的表达式为真,那么then
和fi
之间的代码会被执行。fi
标志着条件代码块的结束。
# 写成一行
if [[ 1 -eq 1 ]]; then echo "1 -eq 1 result is: true"; fi
# Output: 1 -eq 1 result is: true
# 写成多行
if [[ "abc" -eq "abc" ]]
then
echo ""abc" -eq "abc" result is: true"
fi
# Output: abc -eq abc result is: true
if else
同样,我们可以使用if..else
语句,例如:
if [[ 2 -ne 1 ]]; then
echo "true"
else
echo "false"
fi
# Output: true
if elif else
有些时候,当if..else
不能满足我们的要求。还可以使用if..elif..else
,例如:
x=10
y=20
if [[ ${x} > ${y} ]]; then
echo "${x} > ${y}"
elif [[ ${x} < ${y} ]]; then
echo "${x} < ${y}"
else
echo "${x} = ${y}"
fi
# Output: 10 < 20
case
如果你需要面对很多情况,分别要采取不同的措施,那么使用case
会比嵌套的if
更有用。使用case
来解决复杂的条件判断,例如:
exec
case ${oper} in
"+")
val=`expr ${x} + ${y}`
echo "${x} + ${y} = ${val}"
;;
"-")
val=`expr ${x} - ${y}`
echo "${x} - ${y} = ${val}"
;;
"*")
val=`expr ${x} \* ${y}`
echo "${x} * ${y} = ${val}"
;;
"/")
val=`expr ${x} / ${y}`
echo "${x} / ${y} = ${val}"
;;
*)
echo "Unknown oper!"
;;
esac
循环语句
Bash 中有四种循环:for
,while
,until
和select
。
for循环
for arg in elem1 elem2 ... elemN
do
### 语句
done
在每次循环的过程中,arg
依次被赋值为从elem1
到elemN
。这些值还可以是通配符或者大括号扩展。
我们还可以把for
循环写在一行,但这要求do
之前要有一个分号,例如:
for i in {1..5}; do echo $i; done
也可以像 C 语言那样使用for
,比如:
for (( i = 0; i < 10; i++ )); do
echo $i
done
当我们想对一个目录下的所有文件做同样的操作时,for
就很方便了。举个例子,如果我们想把所有的.bash
文件移动到script
文件夹中,我们的脚本可以这样写:
DIR=/home/xiaoming
for FILE in ${DIR}/*.sh; do
mv "$FILE" "${DIR}/scripts"
done
# 将 /home/xiaoming 目录下所有 sh 文件拷贝到 /home/xiaoming/scripts
while循环
while
循环检测一个条件,只要这个条件为 真,就执行一段命令。被检测的条件跟if..then
中使用的基元并无二异。因此一个while
循环看起来会是这样:
while [[ condition ]]
do
### 语句
done
跟for
循环一样,如果我们把do
和被检测的条件写到一行,那么必须要在do
之前加一个分号。
比如下面这个例子:
### 0到9之间每个数的平方
x=0
while [[ ${x} -lt 10 ]]; do
echo $((x * x))
x=$((x + 1))
done
# Output:
# 0
# 1
# 4
# 9
# 16
# 25
# 36
# 49
# 64
# 81
until循环
until
循环跟while
循环正好相反。它跟while
一样也需要检测一个测试条件,但不同的是,只要该条件为 假 就一直执行循环:
x=0
until [[ ${x} -ge 5 ]]; do
echo ${x}
x=`expr ${x} + 1`
done
# Output:
# 0
# 1
# 2
# 3
# 4
select in 循环
select in 循环用来增强交互性,它可以显示出带编号的菜单,用户输入不同的编号就可以选择不同的菜单,并执行不同的功能。
select in 是 Shell 独有的一种循环,非常适合终端(Terminal)这样的交互场景,C语言、C++、Java、Python、C# 等其它编程语言中是没有的。
Shell select in 循环的用法如下:
select variable in value_list
do
statements
done
variable 表示变量,value_list 表示取值列表,in 是 Shell 中的关键字。你看,select in 和 for in 的语法是多么地相似。
我们先来看一个 select in 循环的例子:
#!/bin/bash
echo "What is your favourite OS?"
select name in "Linux" "Windows" "Mac OS" "UNIX" "Android"
do
echo $name
done
echo "You have selected $name"
运行结果:
What is your favourite OS?
1) Linux
2) Windows
3) Mac OS
4) UNIX
5) Android
#? 4↙
You have selected UNIX
#? 1↙
You have selected Linux
#? 9↙
You have selected
#? 2↙
You have selected Windows
#?^D
#?
用来提示用户输入菜单编号;^D
表示按下 Ctrl+D 组合键,它的作用是结束 select in 循环。
运行到 select 语句后,取值列表 value_list 中的内容会以菜单的形式显示出来,用户输入菜单编号,就表示选中了某个值,这个值就会赋给变量 variable,然后再执行循环体中的 statements(do 和 done 之间的部分)。
每次循环时 select 都会要求用户输入菜单编号,并使用环境变量 PS3 的值作为提示符,PS3 的默认值为#?,修改 PS3 的值就可以修改提示符。
如果用户输入的菜单编号不在范围之内,例如上面我们输入的 9,那么就会给 variable 赋一个空值;如果用户输入一个空值(什么也不输入,直接回车),会重新显示一遍菜单。
注意,select 是无限循环(死循环),输入空值,或者输入的值无效,都不会结束循环,只有遇到 break 语句,或者按下 Ctrl+D 组合键才能结束循环。 完整实例 select in 通常和 case in 一起使用,在用户输入不同的编号时可以做出不同的反应。
修改上面的代码,加入 case in 语句:
#!/bin/bash
echo "What is your favourite OS?"
select name in "Linux" "Windows" "Mac OS" "UNIX" "Android"
do
case $name in
"Linux")
echo "Linux是一个类UNIX操作系统,它开源免费,运行在各种服务器设备和嵌入式设备。"
break
;;
"Windows")
echo "Windows是微软开发的个人电脑操作系统,它是闭源收费的。"
break
;;
"Mac OS")
echo "Mac OS是苹果公司基于UNIX开发的一款图形界面操作系统,只能运行与苹果提供的硬件之上。"
break
;;
"UNIX")
echo "UNIX是操作系统的开山鼻祖,现在已经逐渐退出历史舞台,只应用在特殊场合。"
break
;;
"Android")
echo "Android是由Google开发的手机操作系统,目前已经占据了70%的市场份额。"
break
;;
*)
echo "输入错误,请重新输入"
esac
done
用户只有输入正确的编号才会结束循环,如果输入错误,会要求重新输入。
运行结果1,输入正确选项:
What is your favourite OS?
1) Linux
2) Windows
3) Mac OS
4) UNIX
5) Android
#? 2
Windows是微软开发的个人电脑操作系统,它是闭源收费的。
运行结果2,输入错误选项:
What is your favourite OS?
1) Linux
2) Windows
3) Mac OS
4) UNIX
5) Android
#? 7
输入错误,请重新输入
#? 4
UNIX是操作系统的开山鼻祖,现在已经逐渐退出历史舞台,只应用在特殊场合。
运行结果3,输入空值:
What is your favourite OS?
1) Linux
2) Windows
3) Mac OS
4) UNIX
5) Android
#?
1) Linux
2) Windows
3) Mac OS
4) UNIX
5) Android
#? 3
Mac OS是苹果公司基于UNIX开发的一款图形界面操作系统,只能运行与苹果提供的硬件之上。
break和continue
如果想提前结束一个循环或跳过某次循环执行,可以使用 shell 的break
和continue
语句来实现。它们可以在任何循环中使用。
break
语句用来提前结束当前循环。
continue
语句用来跳过某次迭代。
# 查找 10 以内第一个能整除 2 和 3 的正整数
i=1
while [[ ${i} -lt 10 ]]; do
if [[ $((i % 3)) -eq 0 ]] && [[ $((i % 2)) -eq 0 ]]; then
echo ${i}
break;
fi
i=`expr ${i} + 1`
done
# Output: 6
# 打印10以内的奇数
for (( i = 0; i < 10; i ++ )); do
if [[ $((i % 2)) -eq 0 ]]; then
continue;
fi
echo ${i}
done
# Output:
# 1
# 3
# 5
# 7
# 9
函数
bash 函数定义语法如下:
[ function ] funname [()] {
action;
[return int;]
}
说明:
- 函数定义时,
function
关键字可有可无。- 函数返回值 - return 返回函数返回值,返回值类型只能为整数(0-255)。如果不加 return 语句,shell 默认将以最后一条命令的运行结果,作为函数返回值。
- 函数返回值在调用该函数后通过
$?
来获得。- 所有函数在使用前必须定义。这意味着必须将函数放在脚本开始部分,直至 shell 解释器首次发现它时,才可以使用。调用函数仅使用其函数名即可。
#!/usr/bin/env bash
calc(){
PS3="choose the oper: "
select oper in + - \* / # 生成操作符选择菜单
do
echo -n "enter first num: " && read x # 读取输入参数
echo -n "enter second num: " && read y # 读取输入参数
exec
case ${oper} in
"+")
return $((${x} + ${y}))
;;
"-")
return $((${x} - ${y}))
;;
"*")
return $((${x} * ${y}))
;;
"/")
return $((${x} / ${y}))
;;
*)
echo "${oper} is not support!"
return 0
;;
esac
break
done
}
calc
echo "the result is: $?" # $? 获取 calc 函数返回值
执行结果:
$ ./function-demo.sh
1) +
2) -
3) *
4) /
choose the oper: 3
enter first num: 10
enter second num: 10
the result is: 100
位置参数
位置参数是在调用一个函数并传给它参数时创建的变量。
位置参数变量表:
变量 | 描述 |
---|---|
$0
|
脚本名称 |
$1 … $9
|
第 1 个到第 9 个参数列表 |
${10} … ${N}
|
第 10 个到 N 个参数列表 |
$* or $@
|
除了$0 外的所有位置参数
|
$#
|
不包括$0 在内的位置参数的个数
|
$FUNCNAME
|
函数名称(仅在函数内部有值) |
#!/usr/bin/env bash
x=0
if [[ -n $1 ]]; then
echo "第一个参数为:$1"
x=$1
else
echo "第一个参数为空"
fi
y=0
if [[ -n $2 ]]; then
echo "第二个参数为:$2"
y=$2
else
echo "第二个参数为空"
fi
paramsFunction(){
echo "函数第一个入参:$1"
echo "函数第二个入参:$2"
}
paramsFunction ${x} ${y}
执行结果:
$ ./function-demo2.sh
第一个参数为空
第二个参数为空
函数第一个入参:0
函数第二个入参:0
$ ./function-demo2.sh 10 20
第一个参数为:10
第二个参数为:20
函数第一个入参:10
函数第二个入参:20
执行 ./variable-demo4.sh hello world
,然后在脚本中通过 $1
、$2
... 读取第 1 个参数、第 2 个参数。。。
函数处理参数
另外,还有几个特殊字符用来处理参数:
参数处理 | 说明 |
---|---|
$#
|
返回参数个数 |
$*
|
返回所有参数 |
?
|
脚本运行的当前进程 ID 号 |
$!
|
后台运行的最后一个进程的 ID 号 |
$@
|
返回所有参数 |
$-
|
返回 Shell 使用的当前选项,与 set 命令功能相同。 |
$?
|
函数返回值 |
runner() {
return 0
}
name=xiaoming
paramsFunction(){
echo "函数第一个入参:$1"
echo "函数第二个入参:$2"
echo "传递到脚本的参数个数:$#"
echo "所有参数:"
printf "+ %s\n" "$*"
echo "脚本运行的当前进程 ID 号:?"
echo "后台运行的最后一个进程的 ID 号:$!"
echo "所有参数:"
printf "+ %s\n" "$@"
echo "Shell 使用的当前选项:$-"
runner
echo "runner 函数的返回值:$?"
}
paramsFunction 1 "abc" "hello, \"xiaoming\""
# Output:
# 函数第一个入参:1
# 函数第二个入参:abc
# 传递到脚本的参数个数:3
# 所有参数:
# + 1 abc hello, "xiaoming"
# 脚本运行的当前进程 ID 号:26400
# 后台运行的最后一个进程的 ID 号:
# 所有参数:
# + 1
# + abc
# + hello, "xiaoming"
# Shell 使用的当前选项:hB
# runner 函数的返回值:0