shell的变量
变量类型:
- 局部变量 局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。
- 环境变量 所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。
- shell变量 shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行
变量定义:
- 定义变量时变量名和等号之间不能有空格
- 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头
- 不能使用标点符号
实例:
1
2
3my_name="hulc"
readonly url
unset urlshell字符串
shell里定义字符串可以用单引号也可以用双引号
单引号
- 单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的;
- 单引号字串中不能出现单独一个的单引号(对单引号使用转义符后也不行),但可成对出现,作为字符串拼接使用。
双引号
双引号里可以有变量
双引号里可以出现转义字符
1
2
3
4
my_str="this is a test"
echo $my_str
echo "The str is:\$ $my_str "
字符串长度的获取
使用**#**获取字符串长度
1
2string="abcd"
echo ${#string} #输出 4字符串的截断操作
1
2
3#从字符串str第一个字符(下标0)开始,截取长度4
str="this is a test"
echo ${str:0:4} #输出this字符串的查找操作
1
2str="This is a test"
echo `expr index "$str" th` #在str里查找字符t或者h,哪个字符先出现就计算哪个,输出结果1shell数组
shell数组有点不同于C语言的数组,其元素类型可以是不同的
shell里用括号”()”表示数组,数组中元素用空格分开
1
2
3
4
5
6
7
8
9
10
11
12
13
14第一种形式
arr_int1=(1 2 3 4)
第二种形式
arr_int2[0]=1
arr_int2[1]=2
第三种形式
arr_int3=(
1
2
3
4
)数组元素的获取
单个元素获取,使用下标:
1
{arr_name[arr_index]}
获取数组所有元素,使用**@或者***
1
2
3{arr_name[@]}
{arr_name[*]}
数组长度的获取
使用#获取数组长度
1
2
3
4
5arr_int=(1 2 3 4)
len_arr=${#arr_int[@]}
len_single=${#arr_int[0]}
echo "the length of arr_int is:${len_arr}"
echo "the length of single element is:${len_single}"shell传递参数
shell脚本获取传递参数方式如下
参数处理 说明 $n 这里的n代表向脚本传递的参数号,**$0:获取脚本的执行名字** $1:向脚本传递的第一个参数 $# 传递到脚本的参数个数 $* 以一个单字符串显示所有向脚本传递的参数。 如”$*”用「”」括起来的情况、以”$1 $2 … $n”的形式输出所有参数。 $$ 脚本运行的当前进程ID号 $! 后台运行的最后一个进程的ID号 $@ 与$*相同,但是使用时加引号,并在引号中返回每个参数。 如”$@”用「”」括起来的情况、以”$1” “$2” … “$n” 的形式输出所有参数。 $- 显示Shell使用的当前选项 $? 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。 $ 与 $@ 区别:*
- 相同点:都是引用所有参数。
- 不同点:只有在双引号中体现出来。假设在脚本运行时写了三个参数 1、2、3,,则 “ * “ 等价于 “1 2 3”(传递了一个参数),而 “@” 等价于 “1” “2” “3”(传递了三个参数)
实例:
1
2
3
4
5
6
7
8
9for loop1 in "$*"; do
echo $loop1
done
for loop2 in "$@"; do
echo $loop2
done
#第一个loop输出1 2 3
#第二个loop每行输出一个数字注意点:
在为shell脚本传递的参数中如果包含空格,应该使用单引号或者双引号将该参数括起来,以便于脚本将这个参数作为整体来接收(尤其注意在函数的参数传递过程)
实例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
echo "---------------with double quote ---------------"
echo "$1"
echo "$2"
echo "$3"
function my_procedure1() {
echo "$1"
echo "$2"
echo "$3"
}
my_procedure1 "$@"
echo "---------------withtout double quote ---------------"
echo $1
echo $2
echo $3
function my_procedure2() {
echo $1
echo $2
echo $3
}
my_procedure2 "$@"
echo "---------------withtout double quote by passing arguments---------------"
my_procedure2 $@执行前用shellcheck检查会发现如下错:
SC2068: Double quote array expansions to avoid re-splitting elements.命令行执行:./test_str “a b” “c d” “e f”,执行之后会发现对于函数传参,如果使用了$@(没有加双引号),会出现元素分割的情况,避免这种情况的方法就是”$@”
1
echo -e "\033[47;31mERROR: $*\033[0m"
用法:echo -e “\033[字背景颜色;字体颜色m字符串\033[0m” 。首先
echo -e作用就是激活转义字符,因为我们要实现的是终端打印带有颜色的文字。47代表输出的文字底色是白色,31代表字体颜色是红色字背景颜色范围:40—-49
40:黑
41:深红
42:绿
43:黄色
44:蓝色
45:紫色
46:深绿
47:白色字颜色:30———–39
30:黑
31:红
32:绿
33:黄
34:蓝色
35:紫色
36:深绿
37:白色========ANSI控制码的说明
\33[0m 关闭所有属性
\33[1m 设置高亮度
\33[4m 下划线
\33[5m 闪烁
\33[7m 反显
\33[8m 消隐
\33[30m – \33[37m 设置前景色
\33[40m – \33[47m 设置背景色
\33[nA 光标上移n行
\33[nB 光标下移n行
\33[nC 光标右移n行
\33[nD 光标左移n行
\33[y;xH设置光标位置
\33[2J 清屏
\33[K 清除从光标到行尾的内容
\33[s 保存光标位置
\33[u 恢复光标位置
\33[?25l 隐藏光标
\33[?25h 显示光标```bash
declare -a var1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
declare是shell的内建命令可以用来声明shell变量、设置变量的属性,上面这段代码作用就是显式将var声明为数组变量
`declare 选项 变量名=变量值`
> | 选项 | 释义 | 举例 |
> | ---- | -------------------------- | -------------------------------------------- |
> | -i | 将变量看成整数 | declare -i A=123 |
> | -r | 定义只读变量 | declare -r B=hello |
> | -a | 定义普通数组;查看普通数组 | |
> | -A | 定义关联数组;查看关联数组 | |
> | -x | 将变量通过环境导出 | declare -x AAA=123456 等于 export AAA=123456 |
5. shell的字符抵消原则
```bash
if[x$1 = x];比如上面这段话里的
$1就代表给.sh传的第一个参数,比如./test.sh a b,那么$1就代表了a,$2就代表了b。这样写的主要目的是当如果写成[“$1” = “$2” ] 在 $1,$2为空时会在某些bash版本中出现编译错误所以运用了shell中的字符抵消原则,实际使用过程中不管x就可以了shell里局部变量的声明
1
local var
关于局部变量和全局变量:
(1)shell 脚本中定义的变量是global的,作用域从被定义的地方开始,一直到shell结束或者被显示删除的地方为止。
(2)shell函数定义的变量也是global的,其作用域从 函数被调用执行变量的地方 开始,到shell或结束或者显示删除为止。函数定义的变量可以是local的,其作用域局限于函数内部。但是函数的参数是local的。
(3)如果局部变量和全局变量名字相同,那么在这个函数内部,会使用局部变量shell getopts用法
比如:sh test.sh -u tom -p 123456
1
2
3
4
5
6
7
8
9
10
11
12
13
while getopts "u:p:" opt; do
case $opt in
u)
use=$OPTARG
echo "user is $use" ;;
p)
passwd=$OPTARG
echo "passwd is $passwd" ;;
\?)
echo "invalid arg" ;;
esac
donegetopts的使用形式:getopts OPTION_STRING VAR;其中
OPTION_STRING就是-u和-p这些选项,$OPTARG就是tom或者123456这些自定义选项(-u,-p)后的参数shell的流程控制
if
if ……fi
1
2
3
4
5
6
7if condition
then
command1
command2
...
commandN
fiif …else … fi
1
2
3
4
5
6
7
8
9if condition
then
command1
command2
...
commandN
else
command
fiif …elif…else…fi
1
2
3
4
5
6
7
8
9if condition1
then
command1
elif condition2
then
command2
else
commandN
fi
for循环
1
2
3
4
5
6
7for var in item1 item2 ... itemN
do
command1
command2
...
commandN
done1
2
3
4
5
6
arr_num=(1 2 3 4)
for loop in ${arr_num[*]}
do
echo "this is a: $loop"
donewhile语句
while语句用来不断执行一系列命令
1
2
3
4while condition
do
command
done无限循环语法格式
1
2
3
4while :
do
command
donecase … esac
case … esac类似C语言里的switch case语句,可以用两个分号**;;**表示分支的结束。语法格式如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14case val in
branch1)
command1
command2
...
commandN
;;
branch2)
command1
command2
...
commandN
;;
esac实例如下:用户需要首先需要键入0或者1来选择编译的机型,然后再键入0或者1选择编译选项是否为Debug
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
3714 while :
15 do
16 echo "----------------------------------"
17 echo "please enter your choise(0-1):"
18 echo "0) xxx"
19 echo "1) yyyy"
20 echo "----------------------------------"
21 read input
22 case $input in
23 0)
24 MACHINE_TYPE=xxx
25 break;;
26 1)
27 MACHINE_TYPE=yyy
28 break;;
29 *) echo "please input again!!!" ;;
30 esac
31 done
32
33 while :
34 do
35 echo "----------------------------------"
36 echo "please enter your choise(0-1):"
37 echo "0) Debug"
38 echo "1) Release"
39 echo "----------------------------------"
40 read input
41 case $input in
42 0)
43 DEBUG_OR_RELEASE=Debug
44 break;;
45 1)
46 DEBUG_OR_RELEASE=Release
47 break;;
48 *) echo "please input again!!!" ;;
49 esac
50 doneshell运算符
使用
expr来进行数学运算,shell运算符包含五种运算符:- 算数运算符
- 关系运算符
- 布尔运算符
- 字符串运算符
- 文件测试运算符
算数运算符:
注意点:
- 表达式和运算符之间要有空格
- 完整的表达式被包含在**``**反引号内
运算符 说明 举例 + 加法 expr $a + $b结果为 30。- 减法 expr $a - $b结果为 -10。* 乘法 expr $a \* $b结果为 200。/ 除法 expr $b / $a结果为 2。% 取余 expr $b % $a结果为 0。= 赋值 a=$b 将把变量 b 的值赋给 a。 == 相等。用于比较两个数字,相同则返回 true。 [ $a == $b ] 返回 false。 != 不相等。用于比较两个数字,不相同则返回 true。 [ $a != $b ] 返回 true。 四则运算表达式:
表达式 举例 注意 $(( )) echo $((1+1)) $[ ] echo $[10-5] expr expr 10 / 5 let n=1;let n+=1 等价于 let n=n+1 let sum+=$i注意这里的表达式和运算符之间不能有空格 关系运算符:
关系运算符只支持数字,不支持字符串,除非字符串的值是数字,假定变量 a 为 10,变量 b 为 20
运算符 说明 举例 -eq 检测两个数是否相等,相等返回 true。 [ $a -eq $b ] 返回 false。 -ne 检测两个数是否不相等,不相等返回 true。 [ $a -ne $b ] 返回 true。 -gt 检测左边的数是否大于右边的,如果是,则返回 true。 [ $a -gt $b ] 返回 false。 -lt 检测左边的数是否小于右边的,如果是,则返回 true。 [ $a -lt $b ] 返回 true。 -ge 检测左边的数是否大于等于右边的,如果是,则返回 true。 [ $a -ge $b ] 返回 false。 -le 检测左边的数是否小于等于右边的,如果是,则返回 true。 [ $a -le $b ] 返回 true。 布尔运算符:
shell的布尔运算符
假定变量 a 为 10,变量 b 为 20
运算符 说明 举例 ! 非运算,表达式为 true 则返回 false,否则返回 true。 [ ! false ] 返回 true。 -o 或运算,有一个表达式为 true 则返回 true。 [ $a -lt 20 -o $b -gt 100 ] 返回 true。 -a 与运算,两个表达式都为 true 才返回 true。 [ $a -lt 20 -a $b -gt 100 ] 返回 false。 逻辑运算符:
注意:
&&前面的内容必须为真才执行&&后面内容||前面的内容必须为假才执行||后面内容
假定变量 a 为 10,变量 b 为 20
运算符 说明 举例 && 逻辑的 AND [[ $a -lt 100 && $b -gt 100 ]] 返回 false || 逻辑的 OR [[ $a -lt 100 || $b -gt 100 ]] 返回 true 字符串运算符:
下表列出了常用的字符串运算符,假定变量 a 为 “abc”,变量 b 为 “efg”:
运算符 说明 举例 = 检测两个字符串是否相等,相等返回 true。 [ $a = $b ] 返回 false。 != 检测两个字符串是否不相等,不相等返回 true。 [ $a != $b ] 返回 true。 -z 检测字符串长度是否为0,为0返回 true。 [ -z $a ] 返回 false。 -n 检测字符串长度是否不为 0,不为 0 返回 true。 [ -n “$a” ] 返回 true。 $ 检测字符串是否为空,不为空返回 true。 [ $a ] 返回 true。 需要注意的是[],[后面必须留一个空格,]前面必须也留一个空格([]里有运算符和表达式的情况下)
文件运算符:
操作符 说明 举例 -b file 检测文件是否是块设备文件,如果是,则返回 true。 [ -b $file ] 返回 false。 -c file 检测文件是否是字符设备文件,如果是,则返回 true。 [ -c $file ] 返回 false。 -d file 检测文件是否是目录,如果是,则返回 true。 [ -d $file ] 返回 false。 -f file 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。 [ -f $file ] 返回 true。 -g file 检测文件是否设置了 SGID 位,如果是,则返回 true。 [ -g $file ] 返回 false。 -k file 检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。 [ -k $file ] 返回 false。 -p file 检测文件是否是有名管道,如果是,则返回 true。 [ -p $file ] 返回 false。 -u file 检测文件是否设置了 SUID 位,如果是,则返回 true。 [ -u $file ] 返回 false。 -r file 检测文件是否可读,如果是,则返回 true。 [ -r $file ] 返回 true。 -w file 检测文件是否可写,如果是,则返回 true。 [ -w $file ] 返回 true。 -x file 检测文件是否可执行,如果是,则返回 true。 [ -x $file ] 返回 true。 -s file 检测文件是否为空(文件大小是否大于0),不为空返回 true。 [ -s $file ] 返回 true。 -e file 检测文件(包括目录)是否存在,如果是,则返回 true。 [ -e $file ] 返回 true。 -a file exists.
-b file exists and is a block special file.
-c file exists and is a character special file.
-d file exists and is a directory.
-e file exists (just the same as -a).
-f file exists and is a regular file.
-g file exists and has its setgid(2) bit set.
-G file exists and has the same group ID as this process.
-k file exists and has its sticky bit set.
-L file exists and is a symbolic link.
-n string length is not zero.
-o Named option is set on.
-O file exists and is owned by the user ID of this process.
-p file exists and is a first in, first out (FIFO) special file or
named pipe.
-r file exists and is readable by the current process.
-s file exists and has a size greater than zero.
-S file exists and is a socket.
-t file descriptor number fildes is open and associated with a
terminal device.
-u file exists and has its setuid(2) bit set.
-w file exists and is writable by the current process.
-x file exists and is executable by the current process.
-z string length is zero.实例:
1
2
3
4
5
6
7
8
9
echo "this is test for file attribute"
filepath="/home/hlc/shell/test_str"
if [ -x $filepath ]; then
echo "the file:$filepath is excutable"
else
echo "no no no"
fiprintf命令
printf输出格式:
1
printf format-string [arguments...]
格式化字符有:
%s %c %d %f都是格式替代符,%s 输出一个字符串,%d 整型输出,%c 输出一个字符,%f 输出实数,以小数形式输出printf转义序列:
序列 说明 \a 警告字符,通常为ASCII的BEL字符 \b 后退 \c 抑制(不显示)输出结果中任何结尾的换行字符(只在%b格式指示符控制下的参数字符串中有效),而且,任何留在参数里的字符、任何接下来的参数以及任何留在格式字符串中的字符,都被忽略 \f 换页(formfeed) \n 换行 \r 回车(Carriage return) \t 水平制表符 \v 垂直制表符 \ 一个字面上的反斜杠字符 \ddd 表示1到3位数八进制值的字符。仅在格式字符串中有效 \0ddd 表示1到3位的八进制值字符 实例:输出系统时间
1
2
3
4
5
6
7
8
9
10
11
12
13
echo "this is test for file attribute"
filepath="/home/hlc/shell/test_str"
if [ -x $filepath ]; then
echo "the file:$filepath is excutable"
else
echo "no no no"
fi
#echo `date`
#printf "the date is:%s \n" "`date`"
printf "the date is:%s \n" "$(date)"shell函数
shell里可以自定义函数,格式为:
1
2
3
4
5
6
7
8
9function functionname ()
{
function body...
return val;
}注意点:
- 其中function可有可无,
**如果函数没有函数返回值,则以最后一条命令的运行结果作为返回值**,返回值的范围(0-255),如果返回值不在这个范围,使用shellcheck检查之后就会出现:SC2152: Can only return 0-255. Other data should be written to stdout - 函数的返回值可以在调用该函数后,通过
$?来获得。 - 调用函数时可以给其传递参数,获取函数传递的参数方法也可以用
$n,详情可参见shell传递参数 - $? 仅对其上一条指令负责,一旦函数返回后其返回值没有立即保存入参数,那么其返回值将不再能通过 $? 获得(比如连续两次echo $?,函数的返回值就被覆盖了)
- 一定要注意:函数与命令的执行结果可以作为条件语句使用。要注意的是,和 C 语言不同,shell 语言中 0 代表 true,0 以外的值代表 false
实例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#calculate two nums's sum
function my_func() {
echo "argv[1]=$1"
echo "argc[2]=$2"
echo "come into my_func"
read num1
echo "the fist para is:$num1"
read num2
echo "the second is:$num2"
return `expr $num1 + $num2`;
}
my_func 9 10
echo "after call the func,the return val is:$?"遇到的坑:
return
expr $num1 + $num2;表达式两边的反引号``写成了单引号expr已经过时了,shellcheck提示:
SC2003: expr is antiquated. Consider rewriting this using $((..)), ${} or [[ ]].即可使用**$((..))或者${}或者[[ ]]**来重写返回值只能0-255,因此两数之和大于255的时候就会溢出
使用
$((..))重写之后的代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#calculate two nums's sum
function my_func() {
echo "argv[1]=$1"
echo "argc[2]=$2"
echo "come into my_func"
read num1
echo "the fist para is:$num1"
read num2
echo "the second is:$num2"
#return `expr $num1 + $num2`;
return $((num1 + num2))
}
my_func 9 10
echo "after call the func,the return val is:$?"
- 其中function可有可无,
shell重定向
shell通常从终端获取输入并且将输出写入到终端上,而我们可以利用重定向把输入和输出重定向到文件里
如下的cmd表示命令、fd表示文件描述符、
命令 说明 cmd > file 将输出重定向到 file。 cmd < file 将输入重定向到 file。 cmd >> file 将输出以追加的方式重定向到 file。 fd > file 将文件描述符为 fd的文件重定向到 file。 fd >> file 将文件描述符为 fd 的文件以追加的方式重定向到 file。 fd >& m 将输出文件 m 和 fd 合并。 fd <& m 将输入文件 m 和 fd合并。 << tag 将开始标记 tag 和结束标记 tag 之间的内容作为输入。 注意:
文件描述符0通常是标准输入stdin,文件描述符1通常是标准输出stdout,文件描述符2通常是标准错误输出stderr实例:
把stderr信息重定向到文件:
1
cmd 2>file
把stderr信息追加到file文件
1
cmd 2>>file
把stdout和stderr合并后重定向到file
1
cmd > file 2>&1
执行某个命令,但不希望在屏幕上显示结果,可以把输出重定向到/dev/null,即
1
cmd > /dev/null
屏蔽掉stderr和stdout
1
cmd > /dev/null 2>&1
实例:
1
find /etc -names "*.txt" >list 2>&1
该条命令的作用就是在/etc目录下寻找所有后缀为.txt文件,然后把输出的结果stdout重定向到list文件内。当执行到2>&1时,即表示把stderr重定向到stdout,此时的stdout已经是list了,即把上面命令的执行的错误结果重定向到list文件中,所以会发现屏幕上没有出现错误信息,查看list文件就可以看到如下信息:find: unknown predicate `-names’
shell文件包含
shell里也可以像C语言那样封装一些公共的或者通用的东西,然后供其他模块访问
格式:
source filepath/filename
实例:
test_include1.sh1
2
my_str="this is file1"test_include2.sh1
2
3
source ./test_include1.sh
echo Smy_strshell里常见的用法
- 在脚本里获取当前脚本的绝对路径:CUR_DIR=$(cd $(dirname $0) && pwd)
shell里使用”.”和”./“执行区别
- “ ./ “ 的方式类似于新建了一个shell, 在这个新建的shell中去执行脚本中的程序,类似于新建了一个子进程,但这个子进程不继承父进程的所有非export类型的变量,并且脚本中对非export环境变量的创建或修改不会反馈到外部调用shell中
- ” . “ 的方式类似于将脚本中的每一行指令逐条在当前shell中执行,因此它继承了当前shell的环境变量,同时脚本中对环境变量的修改也可以反馈到shell中
测试代码:新建一个test_dot1.sh和test_dot2.sh。
test1.sh
1
2
3
4
5
6
7
VAL=10
echo "pre val is:$VAL"
VAL=20
echo "middle val is:$VAL"
echo "end"test2.sh
1
2
3
4
echo "test the val is:$VAL"
VAL=30
echo "the text is:$VAL"首先./test1.sh,然后./test2.sh,会发现如下输出结果。不难看出如果使用./方式执行,test1.sh里变量与test2.sh的变量是互不影响的,即使在test1.sh里修改了VAL的值,test2.sh也访问不到。
1
2
3
4
5
6
7
8#test1.sh
pre val is:10
middle val is:20
end
#test2.sh
test the val is:
the text is:30如果我们以. test1.sh,然后. test2.sh访问,会发现如下输出结果。很显然使用.方式执行的程序的变量的修改,在另外一个sh里也能继承到
1
2
3
4
5
6
7
8#test1.sh
pre val is:10
middle val is:20
end
#test2.sh
test the val is:20
the text is:30bash里的引号
- 双引号”” :会把引号的内容当成整体来看待,允许通过$符号引用其他变量值
- 单引号’’ :会把引号的内容当成整体来看待,禁止引用其他变量值,shell中特殊符号都被视为普通字符
- 反撇号`` :反撇号和$()一样,引号或括号里的命令会优先执行,如果存在嵌套,反撇号不能用
实例:
1
2
3
4
5
6
7
8echo "$(hostname)"
echo '$(hostname)'
echo `date +%F`
#输出结果
hulc
$(hostname)
2018-11-22shell里的seq
用法:比如seq 100意思就是1到100的数,默认增量为1
Usage: seq [OPTION]… LAST
or: seq [OPTION]… FIRST LAST
or: seq [OPTION]… FIRST INCREMENT LAST
Print numbers from FIRST to LAST, in steps of INCREMENT.shell里的
()和{}区别- 相同点:()和{}都是把一串的命令放在括号里面,如果命令在一行,则命令之间用;隔开
- 不同点:
- ()只是把一串命令重新开一个子shell进行执行,不影响当前shell环境;{}对一串命令在当前shell执行,影响当前shell环境
- ()最后一个命令不用分号,{}最后一个命令要用分号
- ()里的第一个命令和左边括号不必有空格,{}的第一个命令和左括号之间必要要有一个空格
- ()和{}中括号里面的某个命令的重定向只影响改名了,但括号外的重定向则影响到括号里的所有命令