因为 bash 是 Linux 标准默认的 shell 解释器,能够说 bash 是 shell 编程的基础。php
本文主要介绍 bash 的语法,对于 linux 指令不作任何介绍。html
:notebook: 本文已归档到:「blog」 :keyboard: 本文的源码已归档到 linux-tutorialjava
███████╗██╗ ██╗███████╗██╗ ██╗
██╔════╝██║ ██║██╔════╝██║ ██║
███████╗███████║█████╗ ██║ ██║
╚════██║██╔══██║██╔══╝ ██║ ██║
███████║██║ ██║███████╗███████╗███████╗
复制代码
Ken Thompson 的 sh 是第一种 Unix Shell,Windows Explorer 是一个典型的图形界面 Shell。python
Shell 脚本(shell script),是一种为 shell 编写的脚本程序,通常文件后缀为 .sh
。linux
业界所说的 shell 一般都是指 shell 脚本,但 shell 和 shell script 是两个不一样的概念。git
Shell 编程跟 java、php 编程同样,只要有一个能编写代码的文本编辑器和一个能解释执行的脚本解释器就能够了。github
Shell 的解释器种类众多,常见的有:shell
在 shell 脚本,#!
告诉系统其后路径所指定的程序便是解释此脚本文件的 Shell 解释器。#!
被称做shebang(也称为 Hashbang )。npm
因此,你应该会在 shell 中,见到诸如如下的注释:编程
#!/bin/sh 复制代码
#!/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
复制代码
这种方式要求脚本文件的第一行必须指明运行该脚本的程序,好比:
:keyboard: 『示例源码』 helloworld.sh
#!/usr/bin/env bash
echo "Hello, world!"
复制代码
上面的例子中,咱们使用了一个颇有用的命令echo
来输出字符串到屏幕上。
前面虽然两次提到了#!
,可是本着重要的事情说三遍的精神,这里再强调一遍:
在 shell 脚本,#!
告诉系统其后路径所指定的程序便是解释此脚本文件的 Shell 解释器。#!
被称做shebang(也称为 Hashbang )。
#!
决定了脚本能够像一个独立的可执行文件同样执行,而不用在终端以前输入sh
, bash
, python
, php
等。
# 如下两种方式均可以指定 shell 解释器为 bash,第二种方式更好
#!/bin/bash
#!/usr/bin/env bash 复制代码
注释能够说明你的代码是什么做用,以及为何这样写。
shell 语法中,注释是特殊的语句,会被 shell 解释器忽略。
#
开头,到行尾结束。:<<EOF
开头,到 EOF
结束。:keyboard: 『示例源码』 comment-demo.sh
#--------------------------------------------
# shell 注释示例
# author:zp
#--------------------------------------------
# echo '这是单行注释'
########## 这是分割线 ##########
:<<EOF
echo '这是多行注释'
echo '这是多行注释'
echo '这是多行注释'
EOF
复制代码
echo 用于字符串的输出。
输出普通字符串:
echo "hello, world"
# Output: hello, world
复制代码
输出含变量的字符串:
echo "hello, \"zp\""
# Output: hello, "zp"
复制代码
输出含变量的字符串:
name=zp
echo "hello, \"${name}\""
# Output: hello, "zp"
复制代码
输出含换行符的字符串:
# 输出含换行符的字符串
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:(当前目录路径)
复制代码
:keyboard: 『示例源码』 echo-demo.sh
printf 用于格式化输出字符串。
默认,printf 不会像 echo 同样自动添加换行符,若是须要换行能够手动添加 \n
。
:keyboard: 『示例源码』 printf-demo.sh
# 单引号
printf '%d %s\n' 1 "abc"
# Output:1 abc
# 双引号
printf "%d %s\n" 1 "abc"
# Output:1 abc
# 无引号
printf %s abcdef
# Output: abcdef(并不会换行)
# 格式只指定了一个参数,但多出的参数仍然会按照该格式输出
printf "%s\n" abc def
# Output:
# abc
# def
printf "%s %s %s\n" a b c d e f g h i j
# Output:
# a b c
# d e f
# g h i
# j
# 若是没有参数,那么 %s 用 NULL 代替,%d 用 0 代替
printf "%s and %d \n"
# Output:
# and 0
# 格式化输出
printf "%-10s %-8s %-4s\n" 姓名 性别 体重kg
printf "%-10s %-8s %-4.2f\n" 郭靖 男 66.1234
printf "%-10s %-8s %-4.2f\n" 杨过 男 48.6543
printf "%-10s %-8s %-4.2f\n" 郭芙 女 47.9876
# Output:
# 姓名 性别 体重kg
# 郭靖 男 66.12
# 杨过 男 48.65
# 郭芙 女 47.99
复制代码
序列 | 说明 |
---|---|
\a |
警告字符,一般为 ASCII 的 BEL 字符 |
\b |
后退 |
\c |
抑制(不显示)输出结果中任何结尾的换行字符(只在%b 格式指示符控制下的参数字符串中有效),并且,任何留在参数里的字符、任何接下来的参数以及任何留在格式字符串中的字符,都被忽略 |
\f |
换页(formfeed) |
\n |
换行 |
\r |
回车(Carriage return) |
\t |
水平制表符 |
\v |
垂直制表符 |
\\ |
一个字面上的反斜杠字符 |
\ddd |
表示 1 到 3 位数八进制值的字符。仅在格式字符串中有效 |
\0ddd |
表示 1 到 3 位的八进制值字符 |
跟许多程序设计语言同样,你能够在 bash 中建立变量。
Bash 中没有数据类型,bash 中的变量能够保存一个数字、一个字符、一个字符串等等。同时无需提早声明变量,给变量赋值会直接建立变量。
访问变量的语法形式为:${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: (空)
复制代码
export
关键字,shell 脚本也能够定义环境变量。常见的环境变量:
变量 | 描述 |
---|---|
$HOME |
当前用户的用户目录 |
$PATH |
用分号分隔的目录列表,shell 会到这些目录中查找命令 |
$PWD |
当前工做目录 |
$RANDOM |
0 到 32767 之间的整数 |
$UID |
数值类型,当前用户的用户 ID |
$PS1 |
主要系统输入提示符 |
$PS2 |
次要系统输入提示符 |
这里 有一张更全面的 Bash 环境变量列表。
⌨️ 『示例源码』 variable-demo.sh
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
复制代码
从第 3 个字符开始,截取 2 个字符
#!/usr/bin/env bash
text="hello"
echo `expr index "${text}" ll`
# Execute: ./str-demo5.sh
# Output:
# 3
复制代码
查找 ll
子字符在 hello
字符串中的起始位置。
⌨️ 『示例源码』 string-demo.sh
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
复制代码
上面两行有很重要(也很微妙)的区别:
为了将数组中每一个元素单独一行输出,咱们用 printf
命令:
printf "+ %s\n" ${colors[*]}
# Output:
# + red
# + yellow
# + dark
# + blue
复制代码
为何dark
和blue
各占了一行?尝试用引号包起来:
printf "+ %s\n" "${colors[*]}"
# Output:
# + red yellow dark blue
复制代码
如今全部的元素都在一行输出 —— 这不是咱们想要的!让咱们试试${colors[@]}
printf "+ %s\n" "${colors[@]}"
# Output:
# + red
# + yellow
# + dark blue
复制代码
在引号内,${colors[@]}
将数组中的每一个元素扩展为一个单独的参数;数组元素中的空格得以保留。
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
复制代码
:keyboard: 『示例源码』 array-demo.sh
下表列出了经常使用的算术运算符,假定变量 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 ]
。
:keyboard: 『示例源码』 operator-demo.sh
x=10
y=20
echo "x=${x}, y=${y}"
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 ${y} / ${x}`
echo "${y} / ${x} = $val"
val=`expr ${y} % ${x}`
echo "${y} % ${x} = $val"
if [[ ${x} == ${y} ]]
then
echo "${x} = ${y}"
fi
if [[ ${x} != ${y} ]]
then
echo "${x} != ${y}"
fi
# Execute: ./operator-demo.sh
# Output:
# x=10, y=20
# 10 + 20 = 30
# 10 - 20 = -10
# 10 * 20 = 200
# 20 / 10 = 2
# 20 % 10 = 0
# 10 != 20
复制代码
关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
下表列出了经常使用的关系运算符,假定变量 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。 |
:keyboard: 『示例源码』 operator-demo2.sh
x=10
y=20
echo "x=${x}, y=${y}"
if [[ ${x} -eq ${y} ]]; then
echo "${x} -eq ${y} : x 等于 y"
else
echo "${x} -eq ${y}: x 不等于 y"
fi
if [[ ${x} -ne ${y} ]]; then
echo "${x} -ne ${y}: x 不等于 y"
else
echo "${x} -ne ${y}: x 等于 y"
fi
if [[ ${x} -gt ${y} ]]; then
echo "${x} -gt ${y}: x 大于 y"
else
echo "${x} -gt ${y}: x 不大于 y"
fi
if [[ ${x} -lt ${y} ]]; then
echo "${x} -lt ${y}: x 小于 y"
else
echo "${x} -lt ${y}: x 不小于 y"
fi
if [[ ${x} -ge ${y} ]]; then
echo "${x} -ge ${y}: x 大于或等于 y"
else
echo "${x} -ge ${y}: x 小于 y"
fi
if [[ ${x} -le ${y} ]]; then
echo "${x} -le ${y}: x 小于或等于 y"
else
echo "${x} -le ${y}: x 大于 y"
fi
# Execute: ./operator-demo2.sh
# Output:
# x=10, y=20
# 10 -eq 20: x 不等于 y
# 10 -ne 20: x 不等于 y
# 10 -gt 20: x 不大于 y
# 10 -lt 20: x 小于 y
# 10 -ge 20: x 小于 y
# 10 -le 20: x 小于或等于 y
复制代码
下表列出了经常使用的布尔运算符,假定变量 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。 |
:keyboard: 『示例源码』 operator-demo3.sh
x=10
y=20
echo "x=${x}, y=${y}"
if [[ ${x} != ${y} ]]; then
echo "${x} != ${y} : x 不等于 y"
else
echo "${x} != ${y}: x 等于 y"
fi
if [[ ${x} -lt 100 && ${y} -gt 15 ]]; then
echo "${x} 小于 100 且 ${y} 大于 15 : 返回 true"
else
echo "${x} 小于 100 且 ${y} 大于 15 : 返回 false"
fi
if [[ ${x} -lt 100 || ${y} -gt 100 ]]; then
echo "${x} 小于 100 或 ${y} 大于 100 : 返回 true"
else
echo "${x} 小于 100 或 ${y} 大于 100 : 返回 false"
fi
if [[ ${x} -lt 5 || ${y} -gt 100 ]]; then
echo "${x} 小于 5 或 ${y} 大于 100 : 返回 true"
else
echo "${x} 小于 5 或 ${y} 大于 100 : 返回 false"
fi
# Execute: ./operator-demo3.sh
# Output:
# x=10, y=20
# 10 != 20 : x 不等于 y
# 10 小于 100 且 20 大于 15 : 返回 true
# 10 小于 100 或 20 大于 100 : 返回 true
# 10 小于 5 或 20 大于 100 : 返回 false
复制代码
如下介绍 Shell 的逻辑运算符,假定变量 x 为 10,变量 y 为 20:
运算符 | 说明 | 举例 |
---|---|---|
&& |
逻辑的 AND | [[ ${x} -lt 100 && ${y} -gt 100 ]] 返回 false |
|| |
逻辑的 OR | [[ ${x} -lt 100 || ${y} -gt 100 ]] 返回 true |
:keyboard: 『示例源码』 operator-demo4.sh
x=10
y=20
echo "x=${x}, y=${y}"
if [[ ${x} -lt 100 && ${y} -gt 100 ]]
then
echo "${x} -lt 100 && ${y} -gt 100 返回 true"
else
echo "${x} -lt 100 && ${y} -gt 100 返回 false"
fi
if [[ ${x} -lt 100 || ${y} -gt 100 ]]
then
echo "${x} -lt 100 || ${y} -gt 100 返回 true"
else
echo "${x} -lt 100 || ${y} -gt 100 返回 false"
fi
# Execute: ./operator-demo4.sh
# Output:
# x=10, y=20
# 10 -lt 100 && 20 -gt 100 返回 false
# 10 -lt 100 || 20 -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。 |
:keyboard: 『示例源码』 operator-demo5.sh
x="abc"
y="xyz"
echo "x=${x}, y=${y}"
if [[ ${x} = ${y} ]]; then
echo "${x} = ${y} : x 等于 y"
else
echo "${x} = ${y}: x 不等于 y"
fi
if [[ ${x} != ${y} ]]; then
echo "${x} != ${y} : x 不等于 y"
else
echo "${x} != ${y}: x 等于 y"
fi
if [[ -z ${x} ]]; then
echo "-z ${x} : 字符串长度为 0"
else
echo "-z ${x} : 字符串长度不为 0"
fi
if [[ -n "${x}" ]]; then
echo "-n ${x} : 字符串长度不为 0"
else
echo "-n ${x} : 字符串长度为 0"
fi
if [[ ${x} ]]; then
echo "${x} : 字符串不为空"
else
echo "${x} : 字符串为空"
fi
# Execute: ./operator-demo5.sh
# Output:
# x=abc, y=xyz
# abc = xyz: x 不等于 y
# abc != xyz : x 不等于 y
# -z abc : 字符串长度不为 0
# -n abc : 字符串长度不为 0
# abc : 字符串不为空
复制代码
文件测试运算符用于检测 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。 |
:keyboard: 『示例源码』 operator-demo6.sh
file="/etc/hosts"
if [[ -r ${file} ]]; then
echo "${file} 文件可读"
else
echo "${file} 文件不可读"
fi
if [[ -w ${file} ]]; then
echo "${file} 文件可写"
else
echo "${file} 文件不可写"
fi
if [[ -x ${file} ]]; then
echo "${file} 文件可执行"
else
echo "${file} 文件不可执行"
fi
if [[ -f ${file} ]]; then
echo "${file} 文件为普通文件"
else
echo "${file} 文件为特殊文件"
fi
if [[ -d ${file} ]]; then
echo "${file} 文件是个目录"
else
echo "${file} 文件不是个目录"
fi
if [[ -s ${file} ]]; then
echo "${file} 文件不为空"
else
echo "${file} 文件为空"
fi
if [[ -e ${file} ]]; then
echo "${file} 文件存在"
else
echo "${file} 文件不存在"
fi
# Execute: ./operator-demo6.sh
# Output:(根据文件的实际状况,输出结果可能不一样)
# /etc/hosts 文件可读
# /etc/hosts 文件可写
# /etc/hosts 文件不可执行
# /etc/hosts 文件为普通文件
# /etc/hosts 文件不是个目录
# /etc/hosts 文件不为空
# /etc/hosts 文件存在
复制代码
跟其它程序设计语言同样,Bash 中的条件语句让咱们能够决定一个操做是否被执行。结果取决于一个包在[[ ]]
里的表达式。
由[[ ]]
(sh
中是[ ]
)包起来的表达式被称做 检测命令 或 基元。这些表达式帮助咱们检测一个条件的结果。这里能够找到有关bash 中单双中括号区别的答案。
共有两个不一样的条件表达式:if
和case
。
if
(1)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
复制代码
(2)if else
语句
一样,咱们可使用if..else
语句,例如:
if [[ 2 -ne 1 ]]; then
echo "true"
else
echo "false"
fi
# Output: true
复制代码
(3)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
复制代码
:keyboard: 『示例源码』 if-demo.sh
case
若是你须要面对不少状况,分别要采起不一样的措施,那么使用case
会比嵌套的if
更有用。使用case
来解决复杂的条件判断,看起来像下面这样:
:keyboard: 『示例源码』 case-demo.sh
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 中的循环也是只要控制条件为真就一直迭代执行的代码块。
Bash 中有四种循环:for
,while
,until
和select
。
for
循环for
与它在 C 语言中的姊妹很是像。看起来是这样:
for arg in elem1 elem2 ... elemN
do
### 语句
done
复制代码
在每次循环的过程当中,arg
依次被赋值为从elem1
到elemN
。这些值还能够是通配符或者大括号扩展。
固然,咱们还能够把for
循环写在一行,但这要求do
以前要有一个分号,就像下面这样:
for i in {1..5}; do echo $i; done
复制代码
还有,若是你以为for..in..do
对你来讲有点奇怪,那么你也能够像 C 语言那样使用for
,好比:
for (( i = 0; i < 10; i++ )); do
echo $i
done
复制代码
当咱们想对一个目录下的全部文件作一样的操做时,for
就很方便了。举个例子,若是咱们想把全部的.bash
文件移动到script
文件夹中,并给它们可执行权限,咱们的脚本能够这样写:
DIR=/home/zp
for FILE in ${DIR}/*.sh; do
mv "$FILE" "${DIR}/scripts"
done
# 将 /home/zp 目录下全部 sh 文件拷贝到 /home/zp/scripts
复制代码
:keyboard: 『示例源码』 for-demo.sh
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
复制代码
:keyboard: 『示例源码』 while-demo.sh
until
循环until
循环跟while
循环正好相反。它跟while
同样也须要检测一个测试条件,但不一样的是,只要该条件为 假 就一直执行循环:
x=0
until [[ ${x} -ge 5 ]]; do
echo ${x}
x=`expr ${x} + 1`
done
# Output:
# 0
# 1
# 2
# 3
# 4
复制代码
:keyboard: 『示例源码』 until-demo.sh
select
循环select
循环帮助咱们组织一个用户菜单。它的语法几乎跟for
循环一致:
select answer in elem1 elem2 ... elemN
do
### 语句
done
复制代码
select
会打印elem1..elemN
以及它们的序列号到屏幕上,以后会提示用户输入。一般看到的是$?
(PS3
变量)。用户的选择结果会被保存到answer
中。若是answer
是一个在1..N
之间的数字,那么语句
会被执行,紧接着会进行下一次迭代 —— 若是不想这样的话咱们可使用break
语句。
一个可能的实例可能会是这样:
#!/usr/bin/env bash
PS3="Choose the package manager: "
select ITEM in bower npm gem pip
do
echo -n "Enter the package name: " && read PACKAGE
case ${ITEM} in
bower) bower install ${PACKAGE} ;;
npm) npm install ${PACKAGE} ;;
gem) gem install ${PACKAGE} ;;
pip) pip install ${PACKAGE} ;;
esac
break # 避免无限循环
done
复制代码
这个例子,先询问用户他想使用什么包管理器。接着,又询问了想安装什么包,最后执行安装操做。
运行这个脚本,会获得以下输出:
$ ./my_script
1) bower
2) npm
3) gem
4) pip
Choose the package manager: 2
Enter the package name: gitbook-cli
复制代码
:keyboard: 『示例源码』 select-demo.sh
break
和 continue
若是想提早结束一个循环或跳过某次循环执行,可使用 shell 的break
和continue
语句来实现。它们能够在任何循环中使用。
break
语句用来提早结束当前循环。
continue
语句用来跳过某次迭代。
:keyboard: 『示例源码』 break-demo.sh
# 查找 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
复制代码
:keyboard: 『示例源码』 continue-demo.sh
# 打印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;]
}
复制代码
:bulb: 说明:
- 函数定义时,
function
关键字无关紧要。- 函数返回值 - return 返回函数返回值,返回值类型只能为整数(0-255)。若是不加 return 语句,shell 默认将以最后一条命令的运行结果,做为函数返回值。
- 函数返回值在调用该函数后经过
$?
来得到。- 全部函数在使用前必须定义。这意味着必须将函数放在脚本开始部分,直至 shell 解释器首次发现它时,才可使用。调用函数仅使用其函数名便可。
:keyboard: 『示例源码』 function-demo.sh
#!/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 |
函数名称(仅在函数内部有值) |
:keyboard: 『示例源码』 function-demo2.sh
#!/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 命令功能相同。 |
$? |
函数返回值 |
:keyboard: 『示例源码』 function-demo3.sh
runner() {
return 0
}
name=zp
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, \"zp\""
# Output:
# 函数第一个入参:1
# 函数第二个入参:abc
# 传递到脚本的参数个数:3
# 全部参数:
# + 1 abc hello, "zp"
# 脚本运行的当前进程 ID 号:26400
# 后台运行的最后一个进程的 ID 号:
# 全部参数:
# + 1
# + abc
# + hello, "zp"
# Shell 使用的当前选项:hB
# runner 函数的返回值:0
复制代码
扩展 发生在一行命令被分红一个个的 记号(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 |
stdin |
标准输入 |
1 |
stdout |
标准输出 |
2 |
stderr |
标准错误输出 |
重定向让咱们能够控制一个命令的输入来自哪里,输出结果到什么地方。这些运算符在控制流的重定向时会被用到:
Operator | Description |
---|---|
> |
重定向输出 |
&> |
重定向输出和错误输出 |
&>> |
以附加的形式重定向输出和错误输出 |
< |
重定向输入 |
<< |
Here 文档 语法 |
<<< |
Here 字符串 |
如下是一些使用重定向的例子:
### ls的结果将会被写到list.txt中
ls -l > list.txt
### 将输出附加到list.txt中
ls -a >> list.txt
### 全部的错误信息会被写到errors.txt中
grep da * 2> errors.txt
### 从errors.txt中读取输入
less < errors.txt
复制代码
/dev/null
文件若是但愿执行某个命令,但又不但愿在屏幕上显示输出结果,那么能够将输出重定向到 /dev/null:
$ command > /dev/null
复制代码
/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;若是尝试从该文件读取内容,那么什么也读不到。可是 /dev/null 文件很是有用,将命令的输出重定向到它,会起到"禁止输出"的效果。
若是但愿屏蔽 stdout 和 stderr,能够这样写:
$ command > /dev/null 2>&1
复制代码
shell 提供了用于 debug 脚本的工具。
若是想采用 debug 模式运行某脚本,能够在其 shebang 中使用一个特殊的选项:
#!/bin/bash options
复制代码
options 是一些能够改变 shell 行为的选项。下表是一些可能对你有用的选项:
Short | Name | Description |
---|---|---|
-f |
noglob | 禁止文件名展开(globbing) |
-i |
interactive | 让脚本以 交互 模式运行 |
-n |
noexec | 读取命令,但不执行(语法检查) |
-t |
— | 执行完第一条命令后退出 |
-v |
verbose | 在执行每条命令前,向stderr 输出该命令 |
-x |
xtrace | 在执行每条命令前,向stderr 输出该命令以及该命令的扩展参数 |
举个例子,若是咱们在脚本中指定了-x
例如:
#!/bin/bash -x
for (( i = 0; i < 3; i++ )); do
echo $i
done
复制代码
这会向stdout
打印出变量的值和一些其它有用的信息:
$ ./my_script
+ (( i = 0 ))
+ (( i < 3 ))
+ echo 0
0
+ (( i++ ))
+ (( i < 3 ))
+ echo 1
1
+ (( i++ ))
+ (( i < 3 ))
+ echo 2
2
+ (( i++ ))
+ (( i < 3 ))
复制代码
有时咱们值须要 debug 脚本的一部分。这种状况下,使用set
命令会很方便。这个命令能够启用或禁用选项。使用-
启用选项,+
禁用选项:
:keyboard: 『示例源码』 debug-demo.sh
# 开启 debug
set -x
for (( i = 0; i < 3; i++ )); do
printf ${i}
done
# 关闭 debug
set +x
# Output:
# + (( i = 0 ))
# + (( i < 3 ))
# + printf 0
# 0+ (( i++ ))
# + (( i < 3 ))
# + printf 1
# 1+ (( i++ ))
# + (( i < 3 ))
# + printf 2
# 2+ (( i++ ))
# + (( i < 3 ))
# + set +x
for i in {1..5}; do printf ${i}; done
printf "\n"
# Output: 12345
复制代码
:notebook: 本文已归档到:「blog」
最后,Stack Overflow 上 bash 标签下有不少你能够学习的问题,当你遇到问题时,也是一个提问的好地方。