计算机中 Shell 术语最早是在 1964 年 Multics 操作系统中定义的,作用是提供人机交互的操作界面,它会解释执行用户输入命令并输出结果。对 Shell 通常的理解是 类Unix系统 上的命令行和批处理程序,可以看作是早期操作系统的桌面,像如今的图形化界面(文件资源管理器、任务管理器、控制面板)一样,可以操作文件、查看进程、配置系统、管理硬件等功能。图形化界面虽然能更直观的使用计算机,但无法使用没有界面的程序,且没有Shell脚本灵活的编程复用能力。Shell 通常基于内核API实现,演变出了很多版本,下列介绍目前最常用的版本:

首版名称开发者历史1971年Thompson shell(sh)Ken Thompson为 Unix V1 开发,简化了早期的 Multics shell,新增了如 输入(<),输出(>) 等特性1977年Bourne shell(sh)Stephen Bourne为 Unix V7 开发,给脚本添加了变量、循环等特性,是当今 shell 的基础,衍生了 csh/ksh/zsh/bash 等1989年Bourne-Again Shell(Bash)Brian Fox为 GNU计划 开发的免费软件,功能上是 Bourne shell 的超集,名字源自 born again 双关语,被 Linux、macOS 系统很多版本广泛使用1990年Z shell(Zsh)Paul Falstad是 csh 的子集,包含了 bash/ksh 部分特性,有热度很高的 Oh My Zsh 插件和社区,并从 2019年 起作为 macOS 默认 shell

Shell 本身是指一种应用程序,现在通常也指 Shell 脚本(shell script)。这篇文章主要介绍 Shell 脚本的语法,由于不同版本会有差异,本文采用的案例以覆盖最广泛的 Bash 为准,测试环境为 macOS 中的 Terminal。

一、类型定义

1,Shebang

Shebang 通常是类Unix系统上脚本的第一行,作用是在执行脚本文件时指明用哪个解释器,比如用 Bash(#!/bin/bash)、Python(#!/usr/bin/python),在 #! 标识后面跟的是解释器的绝对路径。如果脚本中不写这一行,会采用默认的 $SHELL 解释器执行。

#!/bin/bash

2,变量

变量定义时不需要声明类型,使用 name=value 格式,值属于弱类型等同于字符串。变量命名规则跟主流编程语言差不多,需注意等号 (=) 间不要有空格!!!。

自定义变量使用 unset xxx 清除,另外查看环境变量用 env 命令,查看所有变量用 declare -p 命令。

定义变量

var1=hello # 定义变量 var1(可以不加引号)

var1="hello world" # 修改变量 var1(包含空格时需添加引号)

readonly var2="hello world" # 定义只读变量 var2

引用变量

echo ${var1}

echo $var1 # 打印 var1(在引用无歧义时可以不加{})

通过变量引用变量

使用变量值作为要引用变量的名称,类似于通过变量名称反射它的值,如:

var1="hello"

var2="var1"

echo ${!var2} # 注意添加了感叹号,输出:hello

3,字符串

单引号中任何字符都会原样输出且不能有变量:

单引号与双引号

var1="world"

echo 'hello ${var1}' # 输出:hello ${var1}

echo "hello ${var1}" # 输出:hello world

截取、删除和替换

filepath="app/src/main.c"

echo ${#filepath} # 打印字符串长度,输出:14

echo ${filepath:3} # 从第3位开始(Index从0开始),截取到最后,输出:/src/main.c

echo ${filepath:3:4} # 从第3位开始,截取4个字符,输出:/src

echo ${filepath:0:${#filepath}-6} # 删除字符串末尾的6个字符,输出:app/src/

echo ${filepath#*/} # 删除左侧匹配(*/)到的字符串(app/),输出:src/main.c

echo ${filepath##*/} # 删除左侧贪婪匹配(*/)到的字符串(app/src/),输出:main.c

echo ${filepath%/*} # 删除右侧匹配(/*)到的字符串(/main.c),输出:app/src

echo ${filepath%%/*} # 删除右侧贪婪匹配(/*)到的字符串(/src/main.c),输出:app

echo ${filepath/app/sdk} # 将第一个 app 字符串替换为 sdk,输出:sdk/src/main.c

echo ${filepath//c/cpp} # 将所有 c 字符串替换为 cpp,输出:app/srcpp/main.cpp

4,数组

数组用括号 () 表达,元素间用空格 ( ) 分隔,下面只介绍 索引数组。此外还有用字符串作为下标的 关联数组,删除数组或数组元素都可以用 unset。

定义数组

array=("aa" "bb" "cc") # 创建三个数组元素

或:使用字符串的处理结果创建数组

text="aa:bb:cc"

array=(${text//:/ })

修改数组

array[3]="dd" # 设置指定下标的值(超过数组长度的下标会新增)

array[${#array[@]}]="ee" # 添加单个元素

array=("${array[@]}" "ff" "gg") # 添加两个元素

查看数组

echo ${array[0]} # 输出第一个元素,结果:aa

echo ${array[${#array[@]}-1]} # 输出最后一个元素,结果:gg

echo ${array[@]} # 输出全部元素,结果:aa bb cc dd ee ff gg

echo ${#array[@]} # 输出数组长度,结果:7

echo ${!array[@]} # 输出数组下标,结果:0 1 2 3 4 5 6

5,函数

函数定义

function name() {

return 0

}

function:函数修饰符(可选)name():函数名称,括号内不支持写参数名return 0:返回值,仅限数值0-255,0表示成功(可选)

函数参数

函数参数是数组形式,在函数内引用参数从 $1 开始,查看全部参数用 $@,获取参数跟查看数组的语法基本一致。示例如下:

function fun1() {

echo "第一个参数:$1,全部参数:[$@],参数个数:$#"

return 66

}

fun1 "aa" "bb"

echo "函数返回值:$?"

输出结果:

第一个参数:aa,全部参数:[aa bb],参数个数:2

函数返回值:66

6,注释

语法中提供了特殊字符 # 作为单行注释标识符,但多行注释并未提供,下面贴出其他同学的解法。

单行注释

# echo "hello world"

多行注释

转化为 <

...

EOF

这里的 : 是空命令,相当于丢弃了后面的输入,如果换成 cat 是打印注释的内容。问题不足:如果内容包含反引号 `,反引号中的内容会被执行,如 `touch ~/Downloads/test.log`,依旧会在下载目录创建文件。 转成函数定义,不调用就不会执行 annotation() {

...

}

二、流程控制

1,逻辑分支

if/else 语句

if [ $num1 -gt 100 ]; then

echo "number > 100"

elif [ $num1 -lt 50 ]; then

echo "number < 50"

else

echo "50 <= number <= 100"

fi

判断变量 num1 的数字范围,-gt 是大于,-lt 是小于。

for 循环语句

# array=("aa" "bb" "cc")

# for item in ${array[@]}; do

for item in "aa" "bb" "cc"; do

echo "item = $item"

done

for 循环数组,可以先定义数组再引用,或者直接在循环语句中定义。

while 循环语句

while [ $num1 -lt 10 ]; do

echo "number = $num1"

let "num1++"

done

while 当条件为 true 时一直循环,若 num1=1 输出结果是 1 到 9,let 命令用于执行表达式。

switch 选择语句

case $num1 in

1|2)

echo 'number = 1 or 2'

;;

3)

echo 'number = 3'

;;

*)

echo 'number other'

;;

esac

在 shell 中选择语句的定义是 case ... esac,多个可选值用 | 分割,默认值用 * 表示。

2,条件判断

比较数值关系或检测文件状态,用于 if 语句后面的条件时,需要使用关键字修饰,常有这几种写法:

test 关键字,判断语句后的表达式为 true/false;[ ] 单个中括号,基本等同 test 关键字(注意括号内要有空格);[[ ]] 双中括号,是 [] 写法的升级版,是在 bash 中引入,支持了 &&、|| 和 ! 逻辑运算符,和字符串的正则表达式;

以下三种写法是等同的:

if test $num -ge 50 -a $num -le 100; then

# if [ $num -ge 50 -a $num -le 100 ]; then

# if [[ $num -ge 50 && $num -le 100 ]]; then

echo "50 <= number <= 100"

fi

比较符号

布尔运算符:用于连接多条表达式

符号含义符号含义符号含义!非运算-o (or)或运算(|)-a (and)与运算(&)

数值比较

符号含义符号含义-eq (equal)等于(=)-ne (not equal)不等于(!=)-gt (greater than)大于(>)-ge (greater than or equal)大于等于(>=)-lt (less than)小于(<)-le (less than or equal)小于等于(<=)

字符串比较

符号含义符号含义== / =字符串相同!=字符串不相同-z字符串为空(长度为0)-n / $xxx字符串非空(长度大于0)>字符串的字典顺序先后=~字符串包含

if [ $string1 == 'xxx' ]; // 判断字符串相同(也可以用单个等号 (=))

if [ -z "$string1" ]; // 判断字符串为空(建议变量用双引号 ("") 包裹,避免变量未定义时出错)

if [ -n "$string1" ]; // 判断字符串不为空(也可以直接写变量,如 if [ $string1 ];)

if [[ "$string1" > "$string2" ]]; // 判断字符串 string1 排序在 string2 之后(注:要用双中括号,单中括号是ASCII排序)

if [[ "$string1" =~ "xxx" ]]; // 判断字符串 string1 中是否包含某个字符 (注:要用双中括号,单括号会报错)

文件状态判断

符号含义符号含义-e (exist) / -a文件存在-s文件存在且文件尺寸大于零-f (file)文件存在且为普通文件-d (directory)文件存在且为目录-L (link)文件存在且为链接文件-r (read)文件存在且可读-w (write)文件存在且可写-x (eXecute)文件存在且可执行

if [ -s $file ]; // 判断文件存在且文件尺寸大于零

if [ ! -e $file ]; // 判断文件不存在

三、脚本交互参数

在调用脚本前需要先添加执行权限(chmod +x file),相对路径时要以点开头(./),最终会使用脚本中 Shebang 定义的解释器执行。我通常使用解释器加文件的方式运行(bash xxx.sh),这样可以省去添加执行权限。

默认传参

在执行脚本后面追加的字符串,会被当作参数全部传入,如执行:

bash xxx.sh "aa" "bb" "cc"

其中 xxx.sh 的内容为:

echo "第一个参数:${1}" // 输出:aa

echo "参数个数:${#}" // 输出:3

echo "全部参数:${@}" // 输出:aa bb cc

getopts 解析传参

当要传多个参数时,更友好的做法是用(-key value)形式,系统内置了 getopts 命令来解析这种参数,不过它只支持单字符选项。要多字符选项请参照 getopt 命令,或自行实现。下列命令执行后,会输出a、b的值:

bash xxx.sh -a "111" -b "222"

其中 xxx.sh 的内容为:

while getopts a:b:h ops; do

case ${ops} in

a)

echo "a value = ${OPTARG}"

;;

b)

echo "b value = ${OPTARG}"

;;

h)

echo "help : "

echo "-a xxx"

echo "-b xxx"

echo "-h help"

exit

;;

*)

echo "unknow params"

exit

;;

esac

done

通过 while 循环逐个解析参数;代码(a:b:h):a、b字符后有冒号,意思是要携带值,h字符后无冒号表示不用额外值;

运行时传参

在脚本运行中与用户交互,读取用户输入信息通过 read 命令,示例如下:

read -p "请输入 ok 继续执行: " inputCmd

if [ 'ok' != "$inputCmd" ]; then

echo "已取消,退出程序"

exit

fi

参考资料

Bash手册: http://www.gnu.org/software/bash/manual/bash.html菜鸟联盟: https://www.runoob.com/linux/linux-shell.htmlshell数组详解:https://blog.csdn.net/anqixiang/article/details/114415491UNIX考古记:一个“遗失”的SHELL: https://coolshell.cn/articles/9410.htmlEvolution of shells in Linux: https://developer.ibm.com/tutorials/l-linux-shells/

相关链接

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。