• Shell 脚本常用语法总结


    计算机中 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(BashBrian Fox为 GNU计划 开发的免费软件,功能上是 Bourne shell 的超集,名字源自 born again 双关语,被 Linux、macOS 系统很多版本广泛使用
    1990年Z shell(ZshPaul 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
    
    • 1

    2,变量

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

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

    定义变量
    var1=hello					# 定义变量 var1(可以不加引号)
    var1="hello world"			# 修改变量 var1(包含空格时需添加引号)
    readonly var2="hello world"	# 定义只读变量 var2
    
    • 1
    • 2
    • 3
    引用变量
    echo ${var1}
    echo $var1		# 打印 var1(在引用无歧义时可以不加{})
    
    • 1
    • 2
    通过变量引用变量

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

    var1="hello"
    var2="var1"
    echo ${!var2}		# 注意添加了感叹号,输出:hello
    
    • 1
    • 2
    • 3

    3,字符串

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

    单引号与双引号
    var1="world"
    echo 'hello ${var1}'	# 输出:hello ${var1}
    echo "hello ${var1}"	# 输出:hello world
    
    • 1
    • 2
    • 3
    截取、删除和替换
    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    4,数组

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

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

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

    text="aa:bb:cc"
    array=(${text//:/ })
    
    • 1
    • 2
    修改数组
    array[3]="dd"		# 设置指定下标的值(超过数组长度的下标会新增)
    array[${#array[@]}]="ee"		# 添加单个元素
    array=("${array[@]}" "ff" "gg")	# 添加两个元素
    
    • 1
    • 2
    • 3
    查看数组
    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    5,函数

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

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

    function fun1() {
    	echo "第一个参数:$1,全部参数:[$@],参数个数:$#"
    	return 66
    }
    fun1 "aa" "bb"
    echo "函数返回值:$?"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    输出结果:

    第一个参数:aa,全部参数:[aa bb],参数个数:2
    函数返回值:66
    
    • 1
    • 2

    6,注释

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

    单行注释
    # echo "hello world"
    
    • 1
    多行注释
    1. 转化为 < 多行文本的形式,然后丢弃掉输入内容

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

      annotation() {
      ...
      }
      
      • 1
      • 2
      • 3

    二、流程控制

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

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

    for 循环语句
    # array=("aa" "bb" "cc")
    # for item in ${array[@]}; do
    
    for item in "aa" "bb" "cc"; do
        echo "item = $item"
    done
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    while 循环语句
    while [ $num1 -lt 10 ]; do
        echo "number = $num1"
        let "num1++"
    done
    
    • 1
    • 2
    • 3
    • 4

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    比较符号

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

    符号含义符号含义符号含义
    !非运算-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 中是否包含某个字符 (注:要用双中括号,单括号会报错)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    文件状态判断

    符号含义符号含义
    -e (exist) / -a文件存在-s文件存在且文件尺寸大于零
    -f (file)文件存在且为普通文件-d (directory)文件存在且为目录
    -L (link)文件存在且为链接文件-r (read)文件存在且可读
    -w (write)文件存在且可写-x (eXecute)文件存在且可执行
    if [ -s $file ];		// 判断文件存在且文件尺寸大于零
    if [ ! -e $file ];		// 判断文件不存在
    
    • 1
    • 2

    三、脚本交互参数

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

    默认传参

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

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

    其中 xxx.sh 的内容为:

    echo "第一个参数:${1}"		// 输出:aa
    echo "参数个数:${#}"		// 输出:3
    echo "全部参数:${@}"		// 输出:aa bb cc
    
    • 1
    • 2
    • 3
    getopts 解析传参

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

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

    其中 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 通过 while 循环逐个解析参数;
    • 代码(a:b:h):a、b字符后有冒号,意思是要携带值,h字符后无冒号表示不用额外值;
    运行时传参

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

    read -p "请输入 ok 继续执行: " inputCmd
    if [ 'ok' != "$inputCmd" ]; then
        echo "已取消,退出程序"
        exit
    fi
    
    • 1
    • 2
    • 3
    • 4
    • 5

    参考资料

  • 相关阅读:
    数理统计的基本概念(二)
    Python复习(持续更新)
    鸡卵清白蛋白修饰黄素单核苷酸(FMN-OVA),Flavin Mononucleotide-Ovalbumin的保存条件
    【vue,unapi】UniApp引入全局js实现全局方法,全局变量
    Day 251/300 《图解HTTP》读书笔记(三)
    SA8155 QNX 命令
    人车目标检测竞赛-赛题分享
    快速搭建SSM框架
    人工电销时代,你来不来 ,ai智能电话机器人
    mysql的DDL语言和DML语言
  • 原文地址:https://blog.csdn.net/u010134293/article/details/126912577