1.shell脚本基础
1.1 shell环境
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)
……
Mac系统默认的是zsh环境,ubuntu下默认的shell环境是bash环境,可以通过执行命令查看:
# ubuntu终端xq@ubuntu:~$ echo $SHELL/bin/bash# mac电脑终端➜ ~ echo $SHELL/bin/zsh
一般系统都会内置几种shell,可以通过命令chsh
修改系统默认的shell环境,在下次打开终端生效,修改示例如下:
# 查看系统中已安装的shell类型xq@ubuntu:~$ cat etc/shells# etc/shells: valid login shells/bin/sh/bin/bash/usr/bin/bash/bin/rbash/usr/bin/rbash/bin/dash/usr/bin/dash/bin/zsh/usr/bin/zsh# 切换默认的shell环境到zshxq@ubuntu:~$ chsh -s bin/zsh# 切换默认的shell环境到bashxq@ubuntu:~$ chsh -s bin/bash
shell配置文件
我们开发环境用的最多的就是bash和zsh了,下面就介绍这两个shell为例介绍对应的配置文件。
Bash的配置文件
/etc/profile:为系统的每个用户设置环境信息,当用户第一次登录时会执行该文件里的命令。默认会直接调用/etc/bashrc。该文件的改动需要重启才能生效。
/etc/bashrc:为每一个运行bash shell的用户执行此文件。当bash shell被打开时,会读取并执行该文件中的命令。所以修改该文件后,重新打开Shell即可生效。
~/.bash_profile:和/etc/profile类似,但是只对当前用户生效。
~/.bashrc:和/etc/bashrc类似,但是只对当前用户生效。
~/.bash_logout:当每次退出bash shell时,执行该文件。
~/.bash_history:保存了历史命令。在Shell为Bash时,每次敲击命令时,都会保存在这个文件里
加载顺序:/etc/profile、/etc/bashrc、~/.bash_profile、~/.bashrc
Zsh的配置文件
~/.zshenv:存放的环境变量配置项在任何场景下都能被读取,这里通常把$PATH等变量写在这里,这样无论是在交互shell,或者运行程序都会读取此文件。个人理解对标Bash的profile。
~/.zprofile:和.zlogin类似,但是是在.zshrc之前加载。
~/.zshrc:在交互式shell中会读取并执行该文件,包含设置别名、函数、选项、键绑定等命令。对标bashrc。
~/.zlogin:在login shell的时候读取。
~/.zlogout:退出终端的时候读取,用于做一些清理工作。对标bash_logout。
~/.zsh_history:保存了历史命令。在Shell为Zsh时,每次敲击命令时,都会保存在这个文件里。
加载顺序:zshenv、zprofile、zshrc、zlogin
1.2 shell脚本
由于bash脚本使用场景更广泛,如android源码build目录中各种shell脚本,下面就以bash脚本为例进行讲述。
shell没有官方的ide支持,可以通过vscode + shellman插件来实现高效编写。shellman插件有进行命令与模版提示的功能。
脚本文件一般以.sh
结尾(扩展名并不影响脚本执行),文件第一行指定运行的shell解释器类型。
#! 是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种 Shell。
#!/bin/bashecho "Hello World !"
还有一种指定shell解释器的写法是通过env来声明
#!/usr/bin/env bashecho "Hello World !"
运行方式
作为可执行程序
以脚本中声明的shell解释器运行,示例如下:
chmod +x ./test.sh #使脚本具有执行权限./test.sh #执行脚本
作为解释器参数
这种运行方式是,直接运行解释器,其参数就是 shell 脚本的文件名,会忽略脚本中指定的解释器信息
如:
# 以zsh解析运行zsh test.sh# 以bash解析运行bash test.sh
调试
shell脚本不能像android studio里面可以打断点方式调试,但是可以通过特定方式把执行过程都打印出来来排查问题。
对整个脚本加调试可以通过在指定解释器的地方添加-x
参数
#!/usr/bin/env bash -xecho "Hello World !"
或者
bash -x test.sh
如果是针对特定的代码块加调试可以通过下面的方式实现:
set -x调试内容set +x
示例代码如下:
set -xecho "hello"echo "world"set +x# 输出结果,带+的为执行过程打印+ echo hellohello+ echo worldworld+ set +x
1.3 注释
单行以 # 开头的行就是注释,会被解释器忽略。
多行以:<<EOF
开始,以EOF
结束,中间的都为注释,示例如下:
# 注释内容...:<<EOF注释内容...注释内容...注释内容...EOF
1.4 变量
定义变量
格式如下,“=”左右两边不能有空格
变量名=值
其中变量名的要求为:
命名只能使用英文字母,数字和下划线,首个字符不能以数字开头。
中间不能有空格,可以使用下划线 _。
不能使用标点符号。
不能使用bash里的关键字
值的话没有要求,可以为整数、字符串、数组等,赋值完某个类型后,后面还可以继续再赋值为其他类型,示例如下:
# 定义一个字符串变量var="hello word"# 定义一个值为1的变量var=1# 定义一个包含a、b、c的数组变量var=(a b c)
使用变量
使用一个定义过的变量,只要在变量名前面加美元符号$
即可,示例如下:
var="hello word"# 打印变量var的值,echo $var# 如果变量没有定义过,则值为空echo $test# 输出结果hello word
只读变量
使用 readonly
命令可以将变量定义为只读变量,只读变量的值不能被改变。示例如下:
readonly val="ready only"# 给只读变量再次赋值会报错val="test"# 输出结果-bash: val:只读变量
删除变量
使用 unset 命令可以删除变量。语法:
unset variable_name
变量被删除后不能再次使用。unset 命令不能删除只读变量。示例如下:
var="hello word"unset varecho $var# 输出结果为空
1.5 字符串
字符串定义
字符串是shell编程中最常用最有用的数据类型,字符串可以用单引号,也可以用双引号,也可以不用引号(不用引用的情况中间不能有空格)。
str="this is string"str='this is string'# 正确示例,无引号无空格str=thisisstring# 错误示例,无引号有空格str=this is string
获取字符串长度
通过${#variable_name}
方式获取,示例如下:
string="abcd"echo ${#string}# 输出结果4
1.6 索引数组
索引数组定义
数组中可以存放多个值。Bash Shell 只支持一维数组(不支持多维数组),初始化时不需要定义数组大小。数组元素的下标由 0 开始。
Shell 数组用括号来表示,元素用"空格"符号分割开,语法格式如下:
array_name=(value1 value2 ... valuen)
赋值也支持指定索引位置赋值,示例如下:
# 数组赋值常见的两种方式array_name=("abc" "def")array_name[0]="abc"array_name[1]="def"
获取数组长度
同获取字符串长度一样,也是通过${#variable_name}
方式获取,示例如下:
array_name=("abc" "def")# 打印数组长度echo ${#array_name}# 输出结果:2
读取数组元素
指定索引位置的使用${array_name[index]}
,全部的使用${array_name[*]}
,示例如下:
array_name=("abc" "def")# 读取数组指定索引元素echo ${array_name[0]}# 读取数组全部的元素echo ${array_name[*]}#输出结果abcabc def
1.7 关联数组
关联数组定义
Bash 支持关联数组,可以使用任意的字符串、或者整数作为下标来访问数组元素。与其他语言中的map是类似的,它是无序的。
关联数组使用 declare -A
命令来声明,语法格式如下:
declare -A map=(["key1"]="value1" ["key2"]="value2")
和索引数组一样,也支持指定key赋值,示例如下:
declare -A map=(["key1"]="value1" ["key2"]="value2")declare -A map2map2["key3"]="value3"# 获取指定key的值echo ${map["key1"]}# 获取关联数组的所有键echo ${!map[*]}echo ${map[*]}
获取关联数组长度
同获取字符串长度一样,也是通过${#variable_name}
方式获取,示例如下:
declare -A map=(["key1"]="value1" ["key2"]="value2")#获取关联数组长度echo ${#map[*]}# 输出结果2
获取key与value
通过${map[key]}
方式获取指定key的值,通过${map[*]}
获取所有的值,通过${!map[*]}
获取所有的键,示例如下:
map=(["key1"]="value1" ["key2"]="value2")# 获取指定key的值echo ${map["key1"]}echo ${map[*]}echo ${!map[*]}# 输出结果value1value2 value1key2 key1
1.8 流程控制
If else
判断某个条件成立或者不成立时要执行的动作,中间的elif 和else都是可选,格式如下:
if condition1; thencommand1elif condition2; thencommand2elsecommand3fi
常见的判断操作符如下:
文件/目录 | -f | 文件是否存在 | |
-d | 目录是否存在 | ||
-x | 文件存在且可执行 | ||
-r | 文件存在且可读 | ||
-w | 文件存在且可写 | ||
字符串 | -z | 字符串为空 | |
-n | 字符串不为空 | ||
= | 字符串相等 | ||
!= | 字符串不相等 | ||
=~ | 字符串包含 | ||
数字比较 | -eq | 相等 | |
-ne | 不相等 | ||
-lt | 小于 | ||
-le | 小于等于 | ||
-gt | 大于 | ||
-ge | 大于等于 |
if
使用示例如下:
# 判断文件是否存在if [ -f ~/.bashrc ]; thenecho "~/.bashrc exist"fi# 判断字符串是否为空if [ -z $str ];thenecho "str is empty"elseecho "str is not empty"fi# 数字大小判断if [ "$var" -eq 1 ];thenecho "var equal 1"elif [ "$var" -gt 4 ];thenecho "var great than 4"elseecho "other case"fi
for 循环
for循环格式如下,item列表值可以分别单独写出来,也可以是一个列表
for var in item1 item2 ... itemNdocommand1command2...commandNdone
代码示例:
for loop in 1 2 3doecho "The value is: $loop"donelist=(a b c)for var in ${list[*]}doecho "var is $var"done# 输出结果The value is: 1The value is: 2The value is: 3var is avar is bvar is c
while
while conditiondocommanddone
代码示例:
a=1while (( $a<=3 ));doecho $alet a++done# 结合read可以实现常见的按行读取文件内容echo "aa" > 1.textecho "bb" >> 1.txtwhile read line;doecho $linedone < 1.text# 输出结果123aabb
case ... esac
为多选择语句,与其他语言中的 switch ... case 语句类似,是一种多分支选择结构,每个 case 分支用右圆括号开始,用两个分号 ;; 表示 break,即执行结束,跳出整个 case ... esac 语句,esac(就是 case 反过来)作为结束标记。
可以用 case 语句匹配一个值与一个模式,如果匹配成功,执行相匹配的命令。
语法格式如下:
case 值 in模式1)command1command2;;模式2)command1command2;;esacecho -n "Enter the name of an animal: "read ANIMALecho -n "The $ANIMAL has "case $ANIMAL inhorse | dog | cat) echo -n "four";;man | kangaroo ) echo -n "two";;*) echo -n "an unknown number of";;esacecho " legs."
break与continue
与其他编程相同里的概念相同,代码示例如下:
# break示例:while truedoecho -n "输入 1 到 5 之间的数字:"read aNumcase $aNum in1|2|3|4|5) echo "你输入的数字为 $aNum!";;*) echo "你输入的数字不是 1 到 5 之间的! 游戏结束"break;;esacdone# 输出结果输入 1 到 5 之间的数字:3你输入的数字为 3!输入 1 到 5 之间的数字:7你输入的数字不是 1 到 5 之间的! 游戏结束# continue示例while truedoecho -n "输入 1 到 5 之间的数字:"read aNumcase $aNum in1|2|3|4|5) echo "你输入的数字为 $aNum!";;*) echo "你输入的数字不是 1 到 5 之间的! 游戏结束"continueecho "游戏结束";;esacdone# 输出结果,无法结束输入 1 到 5 之间的数字:3你输入的数字为 3!输入 1 到 5 之间的数字:7你输入的数字不是 1 到 5 之间的! 游戏结束输入 1 到 5 之间的数字:
select
选择一个列表中的一个值,item列表值可以分别单独写出来,也可以是一个列表
语法格式如下:
select var in item1 item2..itemNdocommanddone
代码示例如下:
list=(a b)PS3="Please select the value:"select var in ${list[*]};dobreakdoneecho "your select is $var"# 输出结果1) a2) bPlease select the value:1your select is a
1.9 函数
函数定义
shell中函数的定义格式如下,其中return的返回值在[0-225]之间,如果不加return将以最后一条命令运行结果,作为返回值.
[ function ] funname(){action;[return int;]}
示例代码:
# 完整格式定义函数function test1() {echo "hello world1"return 0}# 简写的函数定义test2() {echo "hello world2"}# 函数调用test1# 获取上一条指令的返回值,紧接着test1之后就是代表获取test1函数的返回值echo $?test2# 输出结果hello world10hello world2
函数参数
在Shell中,调用函数时可以向其传递参数。在函数体内部,通过 $$$$n 的形式来获取参数的值,例如,
$$$$$1表示第一个参数,$2表示第二个参数,依此类推。$*
表示所有的参数,$#
表示参数的个数
带参数的函数示例:
add() {echo "parmas: $*"echo "params count:$#"return $(($1+$2))}add 1 2echo "sum is $?"# 输出结果parmas: 1 2params count:2sum is 3
根据上面的定义来看shell函数只能返回整形且为0-255之间,那么我们想返回字符串或者数组有没有其他办法呢?
我们可以通过其他方法来实现类似的效果的,示例代码如下:
获取字符串结果,调用的时候通过$(function_name param)
方式来调用,那这个函数里面所有echo
出来的字符串就会赋值给你的变量,示例如下:
hello() {echo "say hello to $1"}result=$(hello world)echo $result# 输出结果say hello to world
获取数组,这个可以通过定义全局变量,在函数里面对其赋值
arr=()testArray() {arr[0]=1arr[1]=2}testArrayecho "arr content is ${arr[*]}"echo "arr size is ${#arr[*]}"# 输出结果arr content is 1 2arr size is 2
2.参考
bash手册:https://www.gnu.org/software/bash/manual/bash.html
Zsh手册: https://zsh.sourceforge.io/Doc/Release/index.html#Top




