Shell 编程跟 JavaScript、php 编程一样,只要有一个能编写代码的文本编辑器和一个能解释执行的脚本解释器就可以了。
Linux 的 Shell 种类众多,常见的有:
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环境到zsh
xq@ubuntu:~$ chsh -s /bin/zsh
#切换默认的shell环境到bash
xq@ubuntu:~$ chsh -s /bin/bash
我们开发环境用的最多的就是bash和zsh了,下面就介绍这两个shell为例介绍对应的配置文件。
由于bash脚本使用场景更广泛,如android源码build目录中各种shell脚本,下面就以bash脚本为例进行讲述。
shell没有官方的ide支持,可以通过vscode + shellman插件来实现高效编写。shellman插件有进行命令与模版提示的功能。
脚本文件一般以.sh结尾(扩展名并不影响脚本执行),文件第一行指定运行的shell解释器类型。
#! 是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种 Shell。
#!/bin/bash
echo "Hello World !"
还有一种指定shell解释器的写法是通过env来声明
#!/usr/bin/env bash
echo "Hello World !"
运行方式
chmod +x ./test.sh #使脚本具有执行权限
./test.sh #执行脚本
#以zsh解析运行
zsh test.sh
#以bash解析运行
bash test.sh
调试
shell脚本不能像android studio里面可以打断点方式调试,但是可以通过特定方式把执行过程都打印出来来排查问题。
对整个脚本加调试可以通过在指定解释器的地方添加-x参数
#!/usr/bin/env bash -x
echo "Hello World !"
或者
bash -x test.sh
如果是针对特定的代码块加调试可以通过下面的方式实现:
set -x
调试内容
set +x
示例代码如下:
set -x
echo "hello"
echo "world"
set +x
#输出结果,带+的为执行过程打印
+ echo hello
hello
+ echo world
world
+ set +x
单行以 # 开头的行就是注释,会被解释器忽略。 定义变量 使用变量 只读变量 删除变量 变量被删除后不能再次使用。unset 命令不能删除只读变量。示例如下: 字符串定义 获取字符串长度 索引数组定义 赋值也支持指定索引位置赋值,示例如下: 获取数组长度 读取数组元素 关联数组定义 和索引数组一样,也支持指定key赋值,示例如下: 获取关联数组长度 获取key与value If else 常见的判断操作符如下: for 循环 代码示例: while 代码示例: case … esac break与continue select 代码示例如下: shell中函数的定义格式如下,其中return的返回值在[0-225]之间,如果不加return将以最后一条命令运行结果,作为返回值. 示例代码: 在Shell中,调用函数时可以向其传递参数。在函数体内部,通过 根据上面的定义来看shell函数只能返回整形且为0-255之间,那么我们想返回字符串或者数组有没有其他办法呢? 获取数组,这个可以通过定义全局变量,在函数里面对其赋值 bash手册:https://www.gnu.org/software/bash/manual/bash.html
多行以:<#注释内容...
:<<EOF
注释内容...
注释内容...
注释内容...
EOF
1.4 变量
格式如下,“=”左右两边不能有空格
变量名=值
其中变量名的要求为:
值的话没有要求,可以为整数、字符串、数组等,赋值完某个类型后,后面还可以继续再赋值为其他类型,示例如下:#定义一个字符串变量
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
var="hello word"
unset var
echo $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
指定索引位置的使用 a r r a y n a m e [ i n d e x ] , 全部的使用 {array_name[index]},全部的使用 arrayname[index],全部的使用{array_name[*]},示例如下:array_name=("abc" "def")
#读取数组指定索引元素
echo ${array_name[0]}
#读取数组全部的元素
echo ${array_name[*]}
#输出结果
abc
abc def
1.7 关联数组
Bash 支持关联数组,可以使用任意的字符串、或者整数作为下标来访问数组元素。与其他语言中的map是类似的,它是无序的。
关联数组使用 declare -A命令来声明,语法格式如下:declare -A map=(["key1"]="value1" ["key2"]="value2")
declare -A map=(["key1"]="value1" ["key2"]="value2")
declare -A map2
map2["key3"]="value3"
#获取指定key的值
echo ${map["key1"]}
#获取关联数组的所有键
echo ${!map[*]}
echo ${map[*]}
同获取字符串长度一样,也是通过${#variable_name}方式获取,示例如下:declare -A map=(["key1"]="value1" ["key2"]="value2")
#获取关联数组长度
echo ${#map[*]}
#输出结果
2
通过 m a p [ k e y ] 方式获取指定 k e y 的值,通过 {map[key]}方式获取指定key的值,通过 map[key]方式获取指定key的值,通过{map[]}获取所有的值,通过${!map[]}获取所有的键,示例如下:map=(["key1"]="value1" ["key2"]="value2")
#获取指定key的值
echo ${map["key1"]}
echo ${map[*]}
echo ${!map[*]}
#输出结果
value1
value2 value1
key2 key1
1.8 流程控制
判断某个条件成立或者不成立时要执行的动作,中间的elif 和else都是可选,格式如下:if condition1; then
command1
elif condition2; then
command2
else
command3
fi
文件/目录
-f
文件是否存在
-d
目录是否存在
-x
文件存在且可执行
-r
文件存在且可读
-w
文件存在且可写
字符串
-z
字符串为空
-n
字符串不为空
=字符串相等
!=
字符串不相等
=~
字符串包含
数字比较
-eq
相等
-ne
不相等
-lt
小于
-le
小于等于
-gt
大于
-ge
大于等于
if使用示例如下:
#判断文件是否存在
if [ -f ~/.bashrc ]; then
echo "~/.bashrc exist"
fi
#判断字符串是否为空
if [ -z $str ];then
echo "str is empty"
else
echo "str is not empty"
fi
#数字大小判断
if [ "$var" -eq 1 ];then
echo "var equal 1"
elif [ "$var" -gt 4 ];then
echo "var great than 4"
else
echo "other case"
fi
for循环格式如下,item列表值可以分别单独写出来,也可以是一个列表for var in item1 item2 ... itemN
do
command1
command2
...
commandN
done
for loop in 1 2 3
do
echo "The value is: $loop"
done
list=(a b c)
for var in ${list[*]}
do
echo "var is $var"
done
#输出结果
The value is: 1
The value is: 2
The value is: 3
var is a
var is b
var is c
while condition
do
command
done
a=1
while (( $a<=3 ));do
echo $a
let a++
done
#结合read可以实现常见的按行读取文件内容
echo "aa" > 1.text
echo "bb" >> 1.txt
while read line;do
echo $line
done < 1.text
#输出结果
1
2
3
aa
bb
为多选择语句,与其他语言中的 switch … case 语句类似,是一种多分支选择结构,每个 case 分支用右圆括号开始,用两个分号 ;; 表示 break,即执行结束,跳出整个 case … esac 语句,esac(就是 case 反过来)作为结束标记。
可以用 case 语句匹配一个值与一个模式,如果匹配成功,执行相匹配的命令。
语法格式如下:case 值 in
模式1)
command1
command2
;;
模式2)
command1
command2
;;
esac
echo -n "Enter the name of an animal: "
read ANIMAL
echo -n "The $ANIMAL has "
case $ANIMAL in
horse | dog | cat) echo -n "four";;
man | kangaroo ) echo -n "two";;
*) echo -n "an unknown number of";;
esac
echo " legs."
与其他编程相同里的概念相同,代码示例如下:#break示例:
while true
do
echo -n "输入 1 到 5 之间的数字:"
read aNum
case $aNum in
1|2|3|4|5) echo "你输入的数字为 $aNum!"
;;
*) echo "你输入的数字不是 1 到 5 之间的! 游戏结束"
break
;;
esac
done
#输出结果
输入 1 到 5 之间的数字:3
你输入的数字为 3!
输入 1 到 5 之间的数字:7
你输入的数字不是 1 到 5 之间的! 游戏结束
#continue示例
while true
do
echo -n "输入 1 到 5 之间的数字:"
read aNum
case $aNum in
1|2|3|4|5) echo "你输入的数字为 $aNum!"
;;
*) echo "你输入的数字不是 1 到 5 之间的! 游戏结束"
continue
echo "游戏结束"
;;
esac
done
#输出结果,无法结束
输入 1 到 5 之间的数字:3
你输入的数字为 3!
输入 1 到 5 之间的数字:7
你输入的数字不是 1 到 5 之间的! 游戏结束
输入 1 到 5 之间的数字:
选择一个列表中的一个值,item列表值可以分别单独写出来,也可以是一个列表
语法格式如下:select var in item1 item2..itemN
do
command
done
list=(a b)
PS3="Please select the value:"
select var in ${list[*]};do
break
done
echo "your select is $var"
#输出结果
1) a
2) b
Please select the value:1
your select is a
1.9 函数
函数定义
[ function ] funname()
{
action;
[return int;]
}
#完整格式定义函数
function test1() {
echo "hello world1"
return 0
}
#简写的函数定义
test2() {
echo "hello world2"
}
#函数调用
test1
#获取上一条指令的返回值,紧接着test1之后就是代表获取test1函数的返回值
echo $?
test2
#输出结果
hello world1
0
hello world2
函数参数
$$n
的形式来获取参数的值,例如,$$$1
表示第一个参数,$2
表示第二个参数,依此类推。$*
表示所有的参数,$#表示参数的个数
带参数的函数示例:add() {
echo "parmas: $*"
echo "params count:$#"
return $(($1+$2))
}
add 1 2
echo "sum is $?"
#输出结果
parmas: 1 2
params count:2
sum is 3
我们可以通过其他方法来实现类似的效果的,示例代码如下:
获取字符串结果,调用的时候通过$(function_name param)方式来调用,那这个函数里面所有echo出来的字符串就会赋值给你的变量,示例如下:hello() {
echo "say hello to $1"
}
result=$(hello world)
echo $result
#输出结果
say hello to world
arr=()
testArray() {
arr[0]=1
arr[1]=2
}
testArray
echo "arr content is ${arr[*]}"
echo "arr size is ${#arr[*]}"
#输出结果
arr content is 1 2
arr size is 2
2. 参考
Zsh手册: https://zsh.sourceforge.io/Doc/Release/index.html#Top