Shell 环境
Shell 编程跟 JavaScript、php 编程一样,只要有一个能编写代码的文本编辑器和一个能解释执行的脚本解释器就可以了。
Linux 的 Shell 种类众多,常见的有:
- Bourne Shell(/usr/bin/sh或/bin/sh)
- Bourne Again Shell(/bin/bash)
- C Shell(/usr/bin/csh)
- K Shell(/usr/bin/ksh)
- Shell for Root(/sbin/sh)
……
本文关注的是 Bash,也就是 Bourne Again Shell,由于易用和免费,Bash 在日常工作中被广泛使用。同时,Bash 也是大多数Linux 系统默认的 Shell。
在一般情况下,人们并不区分 Bourne Shell 和 Bourne Again Shell,所以,像 #!/bin/sh,它同样也可以改为 #!/bin/bash。
eg:
1 | !/bin/bash |
#! 告诉系统其后路径所指定的程序即是解释此脚本文件的 Shell 程序。
运行 Shell 脚本
- 作为可执行程序
将上面的代码保存为 test.sh,并 cd 到相应目录:
1 | chmod +x ./test.sh #使脚本具有执行权限 |
注意,一定要写成 ./test.sh,而不是 test.sh,运行其它二进制的程序也一样,直接写 test.sh,linux 系统会去 PATH 里寻找有没有叫 test.sh 的,而只有 /bin, /sbin, /usr/bin,/usr/sbin 等在 PATH 里,你的当前目录通常不在 PATH 里,所以写成 test.sh 是会找不到命令的,要用 ./test.sh 告诉系统说,就在当前目录找
- 作为解释器参数
这种运行方式是,直接运行解释器,其参数就是 shell 脚本的文件名,如:
1 | /bin/sh test.sh |
这种方式运行的脚本,不需要在第一行指定解释器信息,写了也没用。
变量
注意,变量名和等号之间不能有空格,这可能和你熟悉的所有编程语言都不一样。
1 | your_name="runoob" |
除了显式地直接赋值,还可以用语句给变量赋值,如:
1 | for file in `ls /etc` |
以上语句将 /etc 下目录的文件名循环出来。
使用变量
使用一个定义过的变量,只要在变量名前面加美元符号即可,如:
1 | your_name="qinjx" |
变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界,比如下面这种情况:
1 | for skill in Ada Coffe Action Java; do |
如果不给skill变量加花括号,写成echo “I am good at $skillScript”,解释器就会把$skillScript当成一个变量(其值为空),代码执行结果就不是我们期望的样子了。
推荐给所有变量加上花括号,这是个好的编程习惯。
已定义的变量,可以被重新定义,如:
1 | your_name="tom" |
这样写是合法的,但注意,第二次赋值的时候不能写$your_name=”alibaba”,使用变量的时候才加美元符($)。
只读变量
使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变。
下面的例子尝试更改只读变量,结果报错:
1 | !/bin/bash |
脚本运行结果如下:
1 | /bin/sh: NAME: This variable is read only. |
删除变量
使用 unset 命令可以删除变量。语法:unset variable_name
变量被删除后不能再次使用。unset 命令不能删除只读变量。
eg:
1 | !/bin/sh |
以上实例执行将没有任何输出。
变量类型
Shell 支持不同类型的变量,其中一些主要的类型包括:
字符串变量: 在 Shell中,变量通常被视为字符串。
你可以使用单引号 ‘ 或双引号 “ 来定义字符串,例如:
1 | my_string='Hello, World!' |
整数变量: 在一些Shell中,你可以使用 declare 或 typeset 命令来声明整数变量。
这样的变量只包含整数值,例如:
1 | declare -i my_integer=42 |
这样的声明告诉 Shell 将 my_integer 视为整数,如果尝试将非整数值赋给它,Shell会尝试将其转换为整数。
数组变量: Shell 也支持数组,允许你在一个变量中存储多个值。
数组可以是整数索引数组或关联数组,以下是一个简单的整数索引数组的例子:
1 | my_array=(1 2 3 4 5) |
或者关联数组
1 | declare -A associative_array |
环境变量: 这些是由操作系统或用户设置的特殊变量,用于配置 Shell 的行为和影响其执行环境。
例如,PATH 变量包含了操作系统搜索可执行文件的路径:
1 | echo $PATH |
特殊变量: 有一些特殊变量在 Shell 中具有特殊含义,例如 $0 表示脚本的名称,$1, $2, 等表示脚本的参数。
$#表示传递给脚本的参数数量,$? 表示上一个命令的退出状态等。
字符串
字符串是shell编程中最常用最有用的数据类型(除了数字和字符串,也没啥其它类型好用了),字符串可以用单引号,也可以用双引号,也可以不用引号。
单引号字符串的限制:
- 单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的;
- 单引号字符串中不能出现单独一个的单引号(对单引号使用转义符后也不行),但可成对出现,作为字符串拼接使用。
双引号的优点:
- 双引号里可以有变量
- 双引号里可以出现转义字符
1 | your_name="runoob" |
输出结果:
1 | Hello, I know you are "runoob"! |
拼接字符串
1 | your_name="runoob" |
输出结果:
1 | hello, runoob ! hello, runoob ! |
字符串常用方法
1 | 获取长度 |
数组
定义:数组名=(值1 值2 … 值n)
1 | array_name=(value0 value1 value2 value3) |
读取数组:${数组名[下标]};使用 @ 符号可以获取数组中的所有元素,例如:echo ${array_name[@]}
关联数组
理解为map
关联数组使用 declare 命令来声明,语法格式:declare -A array_name
-A选项就是用于声明一个关联数组。
关联数组的键是唯一的。
以下实例我们创建一个关联数组 site,并创建不同的键值:
1 | declare -A site=(["google"]="www.google.com" ["runoob"]="www.runoob.com" ["taobao"]="www.taobao.com") |
访问关联数组元素可以使用指定的键,格式:array_name["index"]
eg:
1 | declare -A site |
获取数组中的所有元素
使用 @ 或 * 可以获取数组中的所有元素,例如:
1 | !/bin/bash |
执行脚本,输出结果如下所示:
1 | chmod +x test.sh |
在数组前加一个感叹号 ! 可以获取数组的所有键,例如:
1 | declare -A site |
输出:
1 | 数组的键为: google runoob taobao |
获取数组的长度,例如
1 | !/bin/bash |
执行脚本,输出结果:
1 | chmod +x test.sh |
注释
单行使用 #
多行如下:
1 | :<<EOF |
传递参数
1 | !/bin/bash |
为脚本设置可执行权限,并执行脚本,输出结果如下所示:
1 | chmod +x test.sh |
另外,还有几个特殊字符用来处理参数:
| 参数处理 | 说明 |
|---|---|
$# |
传递到脚本的参数个数 |
$* |
以一个单字符串显示所有向脚本传递的参数。如 "$*" 用「”」括起来的情况,以 "$1$2 ... $n" 的形式输出所有参数。 |
$$ |
脚本运行的当前进程 ID 号 |
$! |
后台运行的最后一个进程的 ID 号 |
$@ |
与 $* 相同,但是使用时加引号,并在引号中返回每个参数。如 "$@" 用「”」括起来的情况,以 "$1" "$2" ... "$n" 的形式输出所有参数。 |
$- |
显示 Shell 使用的当前选项,与 set 命令功能相同。 |
$? |
显示最后命令的退出状态。0 表示没有错误,其他任何值表明有错误。 |
基本运算符
原生bash不支持简单的数学运算,但是可以通过其他命令来实现,例如 awk 和 expr,expr 最常用。
expr 是一款表达式计算工具,使用它能完成表达式的求值操作。
例如,两个数相加(注意使用的是反引号 ` 而不是单引号 ‘):
1 | !/bin/bash |
注意:
- 表达式和运算符之间要有空格,例如 2+2 是不对的,必须写成 2 + 2,这与我们熟悉的大多数编程语言不一样。
完整的表达式要被包含,注意这个字符不是常用的单引号,在 Esc 键下边。
算术运算符
| 运算符 | 说明 | 举例 |
|---|---|---|
+ |
加法 | 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 ]
关系运算符
关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
| 运算符 | 说明 | 举例 |
|---|---|---|
-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 |
布尔运算符
| 运算符 | 说明 | 举例 |
|---|---|---|
! |
非运算,表达式为 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 |
逻辑运算符
| 运算符 | 说明 | 举例 |
|---|---|---|
&& |
逻辑的 AND | [[ $a -lt 100 && $b -gt 100 ]] 返回 false |
|| |
逻辑的 OR | [[ $a -lt 100 || $b -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 |
$ |
检测字符串是否不为空,不为空返回 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 |
| 其他检查符: |
- -S: 判断某文件是否 socket。
- -L: 检测文件是否存在并且是一个符号链接。
自增和自减操作符
尽管 Shell 本身没有像 C、C++ 或 Java 那样的 ++ 和 – 操作符,但可以通过其他方式实现相同的功能。以下是一些常见的方法:
使用 let 命令let 命令允许对整数进行算术运算。
1 | !/bin/bash |
使用 $(( )) 进行算术运算$(( ))语法也是进行算术运算的一种方式。
1 | !/bin/bash |
其他还有:
- 使用 expr 命令
- 使用 (( )) 进行算术运算
echo
命令格式:echo [选项] [字符串]
常用选项:
- -n 选项:不换行输出
- -e 选项:启用转义字符解释
高级用法
- 输出到文件
使用重定向将输出保存到文件:
1 | echo "This will be saved to file" > output.txt |
- 输出命令执行结果
使用命令替换输出命令结果:
1 | echo "Today is $(date)" |
printf
它源自 C 语言的 printf() 函数,printf 由 POSIX 标准所定义,因此使用 printf 的脚本比使用 echo 移植性好。
默认的 printf 不会像 echo 自动添加换行符,我们可以手动添加 \n。
为什么使用printf?
- 格式控制:可以指定字段宽度、精度和对齐方式
- 类型安全:不同类型的数据(整数、浮点数、字符串等)有对应的格式说明符
- 可移植性:行为在不同系统和Shell中更加一致
- 复杂输出:适合生成表格、报表等结构化输出
基本语法:printf format-string [arguments...]
参数说明:
- **format-string:**包含普通字符和格式说明符的字符串
- **arguments…:**与格式说明符对应的变量或值
格式说明符由 % 字符开始,后跟一个或多个字符,用于指定输出的格式。常用的格式说明符包括: - %s:字符串
- %d:十进制整数
- %f:浮点数
- %c:字符
- %x:十六进制数
- %o:八进制数
- %b:二进制数
- %e:科学计数法表示的浮点数
eg:
1 | 整数 |
输出
1 | Decimal: 255 |
test命令
test 命令是 Shell 内置的条件判断工具,用于评估表达式并返回布尔值(真/假),它通常与 if 语句结合使用,是 Shell 脚本中实现逻辑控制的基础。
Shell 中的 test 命令用于检查某个条件是否成立,它可以进行数值、字符和文件三个方面的测试。
语法格式
1 | test EXPRESSION |
文件测试操作
test 命令最常用于检查文件属性,以下是常用文件测试选项:
| 操作符 | 描述 | 示例 |
|---|---|---|
-e |
文件是否存在 | [ -e file.txt ] |
-f |
是普通文件 | [ -f /path/to/file ] |
-d |
是目录 | [ -d /path/to/dir ] |
-r |
可读 | [ -r file.txt ] |
-w |
可写 | [ -w file.txt ] |
-x |
可执行 | [ -x script.sh ] |
-s |
文件大小 >0 | [ -s logfile ] |
-L |
是符号链接 | [ -L symlink ] |
示例脚本:
1 | !/bin/bash |
输出结果:
1 | /etc/passwd 存在 |
字符串比较
test 提供了多种字符串比较方式:
| 操作符 | 描述 | 示例 |
|---|---|---|
-z STRING |
字符串为空 | [ -z "$var" ] |
-n STRING |
字符串非空 | [ -n "$var" ] |
STRING1 = STRING2 |
字符串相等 | [ "$var1" = "$var2" ] |
STRING1 != STRING2 |
字符串不等 | [ "$var1" != "$var2" ] |
重要提示:字符串变量应该总是用双引号括起来,防止空变量导致语法错误。
示例:
1 | !/bin/bash |
执行后,我们在终端输入 runoob,输出结果类似如下:
1 | 输入用户名: runoob |
数值比较
对于数值比较,test 使用不同的操作符:
| 操作符 | 描述 | 示例 |
|---|---|---|
-eq |
等于 | [ "$a" -eq "$b" ] |
-ne |
不等于 | [ "$a" -ne "$b" ] |
-gt |
大于 | [ "$a" -gt "$b" ] |
-ge |
大于或等于 | [ "$a" -ge "$b" ] |
-lt |
小于 | [ "$a" -lt "$b" ] |
-le |
小于或等于 | [ "$a" -le "$b" ] |
| 示例: |
1 | !/bin/bash |
执行后,我们在终端输入 12,输出结果类似如下:
1 | 输入年龄: 12 |
流程控制
if else
if 语句语法格式:
1 | if condition |
写成一行(适用于终端命令提示符):if [ $(ps -ef | grep -c "ssh") -gt 1 ]; then echo "true"; fi
if else 语法格式:
1 | if condition |
if else-if else 语法格式:
1 | if condition1 |
if else 的 […] 判断语句中大于使用
-gt,小于使用-lt。
如果使用 ((…)) 作为判断语句,大于和小于可以直接使用 > 和 <。
for
语法:
1 | for var in item1 item2 ... itemN |
也可以写成一行:for var in item1 item2 ... itemN; do command1; command2… done;
while
语法格式:
1 | while condition |
eg:
1 | !/bin/bash |
while循环可用于读取键盘信息。下面的例子中,输入信息被设置为变量FILM,按
1 | echo '按下 <CTRL-D> 退出' |
输出:
1 | 按下 <CTRL-D> 退出 |
until 循环 和 while 的处理方式刚好相反
case … esac
与其他语言中的 switch … case 语句类似
语法如下:
1 | case 值 in |
eg:
1 | echo '输入 1 到 4 之间的数字:' |
输出:
1 | 输入 1 到 4 之间的数字: |
支持字符串
跳出循环
Shell 使用两个命令来实现该功能:break 和 continue。
break 命令
break 命令允许跳出所有循环(终止执行后面的所有循环)
continue
continue 命令与 break 命令类似,只有一点差别,它不会跳出所有循环,仅仅跳出当前循环。
函数
shell中函数的定义格式如下:
1 | [ function ] funname [()] |
说明:
- 1、可以带 function fun() 定义,也可以直接 fun() 定义,不带任何参数。
- 2、参数返回,可以显示加:return 返回,如果不加,将以最后一条命令运行结果,作为返回值。 return 后跟数值 n(0-255).
eg:
1 | !/bin/bash |
带有return语句的函数
1 | !/bin/bash |
函数返回值在调用该函数后通过 $? 来获得
注意:所有函数在使用前必须定义。这意味着必须将函数放在脚本开始部分,直至shell解释器首次发现它时,才可以使用。调用函数仅使用其函数名即可。
return 语句只能返回一个介于 0 到 255 之间的整数。要解决这个问题,您可以修改 return 语句,直接使用 echo 输出和而不是使用 return
函数参数
eg:
1 | !/bin/bash |
注意,$10 不能获取第十个参数,获取第十个参数需要${10}。当n>=10时,需要使用${n}来获取参数。
输入&输出重定向
重定向命令列表如下:
输出重定向
1 | command1 > file1 |
eg:
执行下面的 who 命令,它将命令的完整的输出重定向在用户文件中(users):
1 | who > users |
执行后,并没有在终端输出信息,这是因为输出已被从默认的标准输出设备(终端)重定向到指定的文件。
你可以使用 cat 命令查看文件内容:
1 | cat users |
输出重定向回覆盖文件内容,请看下面的例子:
1 | echo "菜鸟教程:www.runoob.com" > users |
输入重定向
和输出重定向一样,Unix 命令也可以从文件获取输入,语法为:
1 | command1 < file1 |
这样,本来需要从键盘获取输入的命令会转移到文件读取内容。
注意:输出重定向是大于号(>),输入重定向是小于号(<)。
重定向深入讲解
一般情况下,每个 Unix/Linux 命令运行时都会打开三个文件:
- 标准输入文件(stdin):stdin的文件描述符为
0,Unix程序默认从stdin读取数据。 - 标准输出文件(stdout):stdout 的文件描述符为
1,Unix程序默认向stdout输出数据。 - 标准错误文件(stderr):stderr的文件描述符为
2,Unix程序会向stderr流中写入错误信息。
默认情况下,command > file 将 stdout 重定向到 file,command < file 将stdin 重定向到 file。
如果希望 stderr 重定向到 file,可以这样写:$ command 2>file
如果希望 stderr 追加到 file 文件末尾,可以这样写:$ command 2>>file2 表示标准错误文件(stderr)。
如果希望将 stdout 和 stderr 合并后重定向到 file,可以这样写:
1 | command > file 2>&1 |
如果希望对 stdin 和 stdout 都重定向,可以这样写:
1 | command < file1 >file2 # command 命令将 stdin 重定向到 file1,将 stdout 重定向到 file2。 |
/dev/null 文件
如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null:
1 | command > /dev/null |
/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容,那么什么也读不到。但是 /dev/null 文件非常有用,将命令的输出重定向到它,会起到”禁止输出”的效果。
如果希望屏蔽 stdout 和 stderr,可以这样写:
1 | command > /dev/null 2>&1 |
注意:0 是标准输入(STDIN),1 是标准输出(STDOUT),2 是标准错误输出(STDERR)。
这里的 2 和 > 之间不可以有空格,2> 是一体的时候才表示错误输出。
文件包含
和其他语言一样,Shell 也可以包含外部脚本。这样可以很方便的封装一些公用的代码作为一个独立的文件。
Shell 文件包含的语法格式如下:
1 | . filename # 注意点号(.)和文件名中间有一空格 |
eg:
创建两个 shell 脚本文件。
test1.sh 代码如下:
1 | !/bin/bash |
test2.sh 代码如下:
1 | !/bin/bash |
接下来,我们为 test2.sh 添加可执行权限并执行:
1 | chmod +x test2.sh |
注:被包含的文件 test1.sh 不需要可执行权限。







