Shell入门看我吧

背景

之前写了系列的shell实战的文章,得到了挺多小伙伴的关注,遇到有些对shell不太熟悉小伙伴,所以有个想法写一篇shell入门的文章。
时间流逝,再回头去看看过去的东西,才会发现哪些东西比较重要,故撰此文,记录我在过去学习和使用shell语言过程中我个人觉得比较重要的部分,做一个小总结和分享。

文章中使用到的代码位置:https://gitee.com/dhar/YTTInjectedContentKit/tree/master/DevPods/InjectedContentKit/Example/injectContentShell

https://gitee.com/dhar/ShellLearning/tree/master/helloshell

文章内容结构如下:

  • 语法
    • 变量
    • 打印
    • 运算
    • 控制
    • 循环
    • 容器
    • 方法
  • 文件
    • 文件读取
    • 文件写入
  • sed流编辑
  • 模块
    • 工具模块
    • 流程模块
  • 输入和菜单
    • 获取输入
    • 菜单

语法

变量

变量的定义

定义一个变量和其他语言的类似,shell是弱类型语言所以不需要使用类型限定,并且变量可以修改类型,下面的例子定义了一个字符串类型的str变量,之后修改为数值类型

注意点:变量等号两边不能有空格出现

 

变量使用

变量可以赋值给另一个变量和打印,当变量被使用的时候需要在变量名称前面加上$符号,还有另一种方式是把变量名放在${}括号中使用,可以把命令执行结果作为右值赋值给变量

 

打印

因为shell没有单步调试和其他功能强大的IDE,所以打印功能就经常使用到,此外打印功能还可以当做函数的返回值,比return作为函数的返回值功能更强大,shell 使用echo打印,内容默认输出到控制台中,echo可以打印字符串、变量、以及字符串中嵌入变量的混个内容,echo有几重要的参数选项

  • -e 转义字符处理,比如\t显示为制表符而不是显示输出\t
  • -n 把文本字符串和命令输出显示在同一行中

 

运算

使用expr执行算术运算

注意点:*乘法运算符号需要转义

 

使用[]执行算术

[]执行算术比expr简单多了,并且*乘法运算符号不需要转义

 

控制

数值比较

控制使用if/else/fi语法,典型的数值比较如下

 

还可以使用下面的比较符号:

比较符 描述
n1 -eq n2 检查n1是否与n2相等
n1 -ge n2 检查n1是否大于或等于n2
n1 -gt n2 检查n1是否大于n2
n1 -le n2 检查n1是否小于或等于n2
n1 -lt n2 检查n1是否小于n2
n1 -ne n2 检查n1是否不等于n2

一个简单的9*9乘法口诀表的例子

 

字符串比较

比较符 描述
str1 = str2 检查str1是否和str2相同
str1 != str2 检查str1是否和str2不同
str1 < str2 检查str1是否比str2小
str1 > str2 检查str1是否比str2大
-n str1 检查str1的长度是否非0
-z str1 检查str1的长度是否为0

 

文件比较

比较符 描述
-d file 检查file是否存在并是一个目录
-e file 检查file是否存在
-f file 检查file是否存在并是一个文件
-r file 检查file是否存在并可读
-s file 检查file是否存在并非空
-w file 检查file是否存在并可写
-x file 检查file是否存在并可执行
-O file 检查file是否存在并属当前用户所有
-G file 检查file是否存在并且默认组与当前用户相同
file1 -nt file2 检查file1是否比file2新
file1 -ot file2 检查file1是否比file2旧

 

循环

C语言格式的for循环

 

for in 循环处理文件

data文件内容如下:

 

 

while 循环

while表示条件满足执行循环,比如下面的例子是9*9乘法口诀表中的一部分,表示i从1循环到9

 

until 循环

untilwhile相反,表示条件不满足执行循环,比如下面的例子是9*9乘法口诀表中的一部分,表示j从1循环到9

 

容器

数组的定义如下 declare -a array_name

注意:osx系统因为bash的版本太低,只能定义索引数组,在bash版本高于4.1的版本可以使用declare -A array_name定义关联数组

以下的代码片定义一个数组,用于保存配置文件中的内容,然后使用for循环遍历数组内容输出到控制台。

  • config_content_array[$cfg_line_count]=$line 把内容保存到数组
  • ${#config_content_array[@]} 获取数组的元素个数
  • config_content=${config_content_array[i]}; 读取数组第i个元素

 

方法

方法的定义有两种方式

  • function func1 { #这里定义方法体 }
  • func2() { #这里定义方法体 }

方法返回值的处理有三种方式

  • return 最大返回256,表示结果码,有特殊含义,并且只能返回数值
  • echo 返回
  • 保存在全局变量中

方法的参数

  • 参数的传递添加在方法之后,多个使用空格分割
  • 参数的获取使用$1,$2以此类推,特别地$0表示文件名、$#表示参数的个数

 

文件

文件读取

文件的读取可以使用cat命令结合for in循环处理

注意:$IFS是文件循环处理的分隔符,按按行处理数据需要把该值设置为$'\n',处理完成之后恢复旧值

 

目录的读取

文件的读取可以使用ls命令结合for in循环处理

 

文件写入

使用输出重定向把内容输出到文件

  • >输出重定向符号先清空文件然后把内容写入到文件中
  • >>输出重定向符号把内容追加写入到文件中

此外可以结合其他命令实现排序、去重功能

  • sort命令对文件内容以行作为单位排序
  • uniq命令对文件内容进行去重,以行为单位,一般需要结合sort命令使用

 

sed流编辑

Sed流编辑结合正则表达式可以方便的对文本文件进行查询、修改、删除、增加等操作

注意:osx系统自带的sed命令和标准的gnu-sed使用方式上有些差别,所以以下篇幅所谈论到的sed都是标准的gnu-sed,下面的这个脚本用于判断系统是否安装了gnu-sed,如果没有会自动进行安装,完成之后需要用户执行显示的命令配置下即可。

 

sed命令功能繁多,所以这里只讲讲我在实战中使用到的,首先了解小sed命令的结构

sed -param operation/pattern/replacement/flags

sed param

  • -e script 在处理输入时,将script中指定的命令添加到已有的命令中
  • -f file 在处理输入时,将file中指定的命令添加到已有的命令中
  • -n 不产生命令输出,使用print命令来完成输出
  • -i 把修改写入到原文件中

sed operation

  • s 替换
  • a 追加
  • d 删除

sed pattern/replacement

查找对应的模式和匹配模式的替换内容

sed flag

有4种可用的替换标记:

  • 数字,表明新文本将替换第几处模式匹配的地方;
  • g,表明新文本将会替换所有匹配的文本;
  • p,表明原先行的内容要打印出来;
  • w file,将替换的结果写到文件中。

sed添加内容

以下是injectContentShell#injectedContentShell.sh脚本文件中的代码片段,使用a操作吧内容添加到方法的开头

  • /^- \(.*\){$/这部分是pattern,匹配OC中方法的开始
  • a\ '"$injected_content"'这部分是operation,注意其中插入内容的变量要使用双引号和单引号包含处理

 

sed删除内容

以下是injectContentShell#injectedContentShell.sh脚本文件中的代码片段,使用d操作删除内容

 

sed修改替换内容

以下是injectContentShell#RenameClasses.sh脚本文件中的代码片段,使用s操作替换内容,有以下几个要点

  • s/'"${original_class_name}"'/'"${result_class_name}"'/g,使用s操作,注意patternreplacement中变量的处理方式,使用双引号、单引号双重包含,使用flagg表示全部替换
  • grep ${original_class_name} -rl ${pbxproj_dir}grep命令查找${pbxproj_dir}文件夹下所有出现${original_class_name}内容的文件,-r选项表示递归查找,-l选项表示只显示匹配到的文件,返回的结果可能是多个的。

 

模块

shell是面向过程的语言,不具备面向对象的特性,shell可以把部分功能独立分离出来,放在单独的脚本文件中,其他模块可以导入该脚本文件,使用其中的功能,这就是shell的伪面向对象

工具模块

工具模块是包含了工具方法的模块,比如数学计算可以放在一个单独的文件中独立为一个模块,其他需要使用到的地方引入这个模块,使用其中定义的方法即可

Math.sh保存了一些数学计算函数

 

其他模块使用. ./Math.sh包含这个模块,可以调用其中定义的power方法

注意:. ./Math.sh.source的简写,这里也可以写成source ./Math.sh

 

流程模块

流程模块是包含了一些列操作的模块,可以向该模块传递参数,也可以有返回值。流程模块中有两个地方比较特别,一个是流程模块本身参数的处理方式和外部调用流程模块传入参数的方式

流程模块处理参数

流程模块处理参数使用getopts命令实现,getopts optionstring opt其中optionstring格式:i:o:io之后的:表示指定i选项和o选项需要有参数,第一个:表示忽略错误,使用case分支处理参数选项对应的参数值。

 

参数的传递

参数的传递和使用命令行的选项类似,可以在选项后面添加该选项的参数值

 

下面定义的是一个流程模块的脚本文件,是injectContentShell#GetAndStoreClasses.sh脚本文件中的代码片段,实现了生成重命名的类的配置脚本功能,可以传递两个参数。

 

在另一个模块中使用流程模块

 

输入和菜单

获取输入

下面是一个循环的输入和检测输入是否是合法目录的例子,是injectContentShell#FileUtil.sh脚本文件中的代码片段

  • echo -n "请输入目录: "是输入的提示,-n表示不换行,用户的输入跟随在提示后面
  • read path把用户的输入内容保存在变量path中

 

菜单

在脚本中可能会有使用菜单选项进行交互的场景,有以下几个要点

  • read -n 1 option命令中用了-n选项来限制只读取一个字符。这样用户只需要输入一个数字,也不用按回车键,输入的内容保存在option变量中
  • clear命令是用来清空命令行的屏幕的
  • echo -e -e 选项用来处理转义字符
  • echo -en -n 选项让光标处于同一行,用户的输入会显示在同一行
  • 使用while循环获取用户的输入,在while循环中使用case分支处理不同的操作

以下脚本是injectContentShell#injectedContentShell.sh文件中的一部分