shell 和 shell 脚本
Shell 是指一种应用程序,用户通过它可以访问操作系统内核的服务,如上图所示。而 Shell脚本(shell script)则是一种为 Shell 编写的脚本程序。
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)等等。最常用的就是 Bourne Shell 和 Bourne Again Shell。
脚本执行
运行脚本的方法有两种:一种是作为可执行程序执行,一种是作为解释器参数执行。示例如下,下面是一个输出 Hello World ! 的脚本。
shell
#!/bin/bash
echo "Hello World !"
可以看到在脚本的最前面,都有一个 #!
, 它是一个约定的标记,它的作用是告诉系统使用哪一种 Shell 来执行脚本。
- 作为可执行程序执行,会使用
#!
声明的shell来运行脚本
shell
chmod +x ./test.sh #使脚本具有执行权限
./test.sh #执行脚本
- 作为解释器参数执行,直接使用 /bin/sh 运行,会忽略
#!
的声明
shell
/bin/sh test.sh
如果想要快速验证,可以直接在菜鸟编辑器上运行自己的脚本
变量
变量声明和删除
shell 中,变量的声明和删除的示例如下所示:(其他 # 在 shell 脚本中用于注释)
shell
# 声明变量,注意变量名和等号之间不能有空格
your_name="小墙程序员"
# 引用变量,echo 函数的作用是向窗口输出文本,类似于 print
echo $your_name
# 声明只读变量
readonly your_name
# 删除变量,注意不能删除只读变量
unset variable_name
数据类型
在 shell 脚本中一般有四种变量类型,分别是字符串变量、数组变量、环境变量、特殊变量。
- 字符串变量
在 shell 脚本中,字符串可以用单引号,也可以用双引号,也可以不用引号。代码示例如下:
shell
# 不建议使用,特殊字符如空格等可能导致解析出问题
your_name=小墙程序员
# 单引号声明的字符串,类似于 kotlin 的三引号的字符串,里面不需要转义
# 同时也不能引用 $variable 变量
your_name='小墙程序员'
# 与'' 正相反,可以引用 $variable 变量,特殊字符需要转义
your_name="小墙程序员"
在 shell 脚本中,也支持获取字符串长度、字符串拼接等操作,代码示例如下:
shell
your_name='12345678'
# 获取字符串长度,8
echo ${#your_name}
# 提取子字符串,结果为:2345
echo ${your_name:1:4}
- 数组变量
shell
# 定义数组,使用空格来分隔
# 数组下标由 0 开始
array_name=('value0' 'value1' 'value2' 'value3')
# 赋值数组单个位置
array_name[0]='value4'
# 获取数组指定位置的值
echo ${array_name[0]}
# 获取数组中所有的元素
echo ${array_name[@]}
# 获取数组的长度
echo ${#array_name[@]}
- 环境变量
环境变量是由操作系统或用户设置的特殊变量,用于配置 Shell 的行为和影响其执行环境。我们可以通过 $ 符号直接获取。常见的环境变量有:
go
`BASHPID`:Bash 进程的进程 ID。
`BASHOPTS`:当前 Shell 的参数,可以用`shopt`命令修改。
`EDITOR`:默认的文本编辑器。
`HOME`:用户的主目录。
`HOST`:当前主机的名称。
`LANG`:字符集以及语言编码,比如`zh_CN.UTF-8`。
`PATH`:由冒号分开的目录列表,当输入可执行程序名后,会搜索这个目录列表。
`PS1`:Shell 提示符。
`PS2`: 输入多行命令时,次要的 Shell 提示符。
`PWD`:当前工作目录。
`RANDOM`:返回一个0到32767之间的随机数。
`SHELL`:Shell 的名字。
`TERM`:终端类型名,即终端仿真器所用的协议。
`UID`:当前用户的 ID 编号。
`USER`:当前用户的用户名。
-
特殊变量
ruby**`$?`** :用于获取上一个命令的退出码。若返回值为`0`,表明上一个命令执行成功;非零值则表示执行失败。例如,执行`ls doesnotexist`命令后,因文件不存在报错,此时`$?`的值为`1`,说明该命令执行失败。 **`$$`** :代表当前 Shell 的进程 ID **`$_`** :存储上一个命令的最后一个参数。例如执行`grep dictionary xxx`后,`$ _`的值为`xxx`。 **`$!`** :返回最近一个后台执行的异步命令的进程 ID **`$0`**:在命令行直接执行时表示当前 Shell 的名称,在脚本中执行时表示脚本名。**$1**、**$2** 等表示脚本的参数 **`$-`**:体现当前 Shell 的启动参数。 **`$#`**: 传递到脚本的参数个数 **`$*`** :以一个单字符串显示所有向脚本传递的参数 **`$@`** :与$*相同,不同点是在脚本运行时写了三个参数 1、2、3,则 " * " 等价于 "1 2 3"(传递了一个参数),而 "@" 等价于 "1" "2" "3"(传递了三个参数)
部分shell支持整数变量,可以使用declare 或 typeset 命令来声明整数变量
多行注释
在shell中支持多种格式的多行注释,代码示例如下:
shell
# 使用 Here 文档来多行注释
:<<EOF
注释内容...
注释内容...
注释内容...
EOF
# : + 空格 + 单引号 表示多行注释
: '
这是注释的部分。
可以有多行内容。
'
算术运算
shell 脚本不能直接进行算术运算。需要使用 (()) 语法或者 expr 命令
(()) 语法
代码示例如下:
shell
# (()) 语法不会返回值,需要使用$来获取术运算的结果
echo $((2 + 2))
# 支持嵌套,** 表示指数
echo $(((5**2) * 3))
# 只能计算整数,否则会报错
echo $((1.5 + 1))
# $[...]是以前的语法,也可以做整数运算,不建议使用
echo $[2+2]
注意:如果在
$((...))
里面使用字符串,Bash 会认为那是一个变量名。如果不存在同名变量,Bash 就会将其作为空值,因此不会报错
除了算术运算外,(()) 语法还可以进行逻辑运算、赋值运算
shell
# 逻辑运算,如果逻辑表达式为真,返回1,否则返回0
echo $((3 > 2))
# 赋值运算
foo=5
echo $((foo*=2))
expr 命令
shell
foo=3
expr $foo + 2
expr
命令支持算术运算,可以不使用((...))
语法。需要注意 expr
命令也不支持非整数参数。
方法
shell 的函数定义格式如下所示,可以看到它的格式和我们使用 kotlin、js等编程语言类似,但是还是有很大的不同。
shell
[ function ] funname [()]
{
action;
[return int;]
}
- 不同点1:funname() 不能带任何参数
在shell函数声明中,不能带任何的参数。而是使用 <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 , 1, </math>1,2,...,$n 来获取参数。代码示例如下:
shell
#!/bin/bash
funWithParam(){
echo "第一个参数为 $1 !"
echo "第二个参数为 $2 !"
# 当 n >= 10 时,需要使用 ${n},否则会获取失败
echo "第十个参数为 $10 !"
echo "第十个参数为 ${10} !"
echo "第十一个参数为 ${11} !"
echo "参数总数有 $# 个!"
echo "作为一个字符串输出所有参数 $* !"
}
# 注意:所有函数在使用前必须定义
funWithParam 1 2 3 4 5 6 7 8 9 34 73
- 不同点2:return返回值,只能为
[0, 255]
之间的整数
代码示例如下:
shell
# return 返回
funWithReturn(){
return 1
}
funWithReturn
# 不加return,将以最后一条命令运行结果作为返回值
demoFun(){
echo "no return"
}
demoFun
- 不同点3:使用 $? 来获取返回值
代码示例如下:
shell
#!/bin/bash
funWithReturn(){
return 5
}
funWithReturn
echo " $? "
逻辑判断与控制
if
- if 语法
shell
# if 语法
if condition
then
command1
command2
...
commandN
fi
# 写成一行,需要分号分隔,适用于终端命令提示符
if condition; then command1; fi
代码示例如下:
shell
if true
then
echo 'hello world'
fi
- if-else 语法
shell
if condition
then
command1
command2
...
commandN
else
command
fi
- if else-if else 语法
shell
if condition1
then
command1
elif condition2
then
command2
else
commandN
fi
- test 命令
if
结构的判断条件,一般使用test
命令,如下代码所示:
shell
# 写法一
test expression
# 写法二
[ expression ]
# 写法三,支持正则判断
[[ expression ]]
代码示例如下:
shell
# 写法一
if test -e test.txt # -e 表示检查文件是否存在,如果存在则返回 true
then
echo "Found test.txt"
fi
# 写法二
if [ "$name" == "小墙程序员" ]
then
echo "current user is root"
fi
# 写法三
if [[ -e /tmp/foo.txt ]] ; then
echo "Found test.txt"
fi
for 循环
shell 脚本的 for 循环的语法如下所示:
shell
for var in item1 item2 ... itemN
do
command1
command2
...
commandN
done
代码示例如下:
shell
for loop in 1 2 3 4 5
do
echo "The value is: $loop"
done
如果想要使用 for 表示无限循环,可以使用 for (( ; ; ))
while
shell 脚本的 while 循环的语法如下:
shell
while condition
do
command
done
代码示例如下:
shell
#!/bin/bash
int=1
while(( $int<=5 ))
do
echo $int
let "int++"
done
until
shell 支持 until 循环,它和 while 正好相反。当条件语句为 true 是,until 循环退出。语法如下所示:
shell
until condition
do
command
done
代码示例如下:
shell
#!/bin/bash
a=0
until [ ! $a -lt 10 ]
do
echo $a
a=`expr $a + 1`
done
case
在 shell 脚本中,使用 case 来代替其他语言中 switch 的作用。语法格式如下:
shell
case 值 in
模式1)
command1
command2
...
commandN
;;
模式2)
command1
command2
...
commandN
;;
esac
代码示例如下:
shell
echo '输入 1 到 4 之间的数字:'
echo '你输入的数字为:'
read aNum # 读取键盘的输入,并赋值给 aNum
case $aNum in
1) echo '你选择了 1'
;;
2) echo '你选择了 2'
;;
3) echo '你选择了 3'
;;
4) echo '你选择了 4'
;;
*) echo '你没有输入 1 到 4 之间的数字'
;;
esac
break 和 continue
如果你需要跳出循环,shell 脚本也支持 break 跳出所有的循环,以及 continue 跳出当前的循环。
输入输出
标准文件
一般情况下,每个 shell 命令运行时都会打开三个标准文件,分别是:
- 标准输入文件(stdin):stdin的文件描述符为0,Unix程序默认从stdin读取数据。
- 标准输出文件(stdout):stdout 的文件描述符为1,Unix程序默认向stdout输出数据。
- 标准错误文件(stderr):stderr的文件描述符为2,Unix程序会向stderr流中写入错误信息。
重定向
一般来说,shell 脚本执行后的结果都是会显示在终端,如果你想要改变显示的位置,可以选择重定向命令。
command > file
将输出重定向到 file,默认是将标准输出重定向到指定filecommand < file
将输入重定向到 file,默认是将标准输入重定向到指定 filecommand >> file
将输出以追加的方式重定向到 file,默认是将标准输出重定向到指定file
如果你不想使用默认的方式,可以直接通过增加文件描述符的形式来设置,代码示例如下:
shell
# 设置为标准错误输出
command 2>file
# 将 stdout 和 stderr 合并后重定向到 file
command >> file 2>&1
# 将 stdin 重定向到 file1,将 stdout 重定向到 file2
command < file1 >file2
如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null;/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃.
文件引用
如果我们需要引用其他的脚本文件,可以使用 .
或者 source
关键字。代码示例如下:
shell
. filename # 注意点号(.)和文件名中间有一空格
source filename