参考指南:
本文主要部分参考如下:
https://www.w3cschool.cn/shellbook/uglqdozt.html
https://www.runoob.com/linux/linux-shell-variable.html
shell脚本的创建 & 运行:
保存文件为xxx.sh
运行方法:
-
直接赋予执行权限运行
chmod +x /path/to/test.sh /path/to/test.sh
-
将文件作为参数传入解释器运行
bash /path/to/test.sh # 或 source /path/to/test.sh # 或 . /path/to/test.sh
基础语法介绍:
以helloWorld程序为例:
#!/bin/bash -v
# test.sh
echo "Hello, World"
使用以上两种执行方式
-
直接执行:
-
参数执行:
这里主要看第一行的#!
, 其只是系统使用之后的解释器与相应的参数来执行脚本文件
而#
在shell语法中是注释的作用, 直接执行的时候就不会注意这两行
变量:
变量类型:
- 1) 局部变量 局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。
- 2) 环境变量 所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。
- 3) shell变量 shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行
定义与使用
定义变量, 不加$
, 并且注意, 变量名与等号, 等号与后头的表达式间不能有空格
其次就是, 变量名遵循编程通用的变量名要求规则
使用变量就在变量名前加$
, 可以选择加上{}
用于帮助解释器识别变量的边界, 推荐加上{}
var="This is a var"
echo $var
只读变量:
变量定义完成后, 加上个readonly
, 就会将变量设置为只读, 之后无法修改
如果之后修改, 就会出现报警
var="This is a var"
readonly var
echo ${var}
var="New Value"
删除变量:
unset variable_name
删除的变量无法再次使用
不能删除只读变量
var="This is a var"
varR="This is a readonly var"
readonly varR
echo ${var}
echo ${varR}
# var="New Value"
unset var
unset varR
echo ${var}
echo ${varR}
字符串:
字符串作为Shell中特殊的也是最好用的变量类型
定义:
可以使用‘’
“”
或不用 不同的定义方式有不同的特点
由于变量名之前是需要加$
的, 所以如果不加的话默认就为字符串
str='this is a string'
单引号定义特点
- 单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的
- 单引号字串中不能出现单独一个的单引号(对单引号使用转义符后也不行),但可成对出现,作为字符串拼接使用
your_name='runoob'
str="Hello, I know you are \"$your_name\"! \n"
echo -e $str
双引号定义特点:
- 字符串可以内嵌变量
- 允许出现转义字符
所以, 通常默认使用“”
, 如果需要raw string, 则使用‘’
字符串拼接:
your_name="runoob"
# 使用双引号拼接
greeting="hello, "$your_name" !"
greeting_1="hello, ${your_name} !"
echo $greeting $greeting_1
# 使用单引号拼接
greeting_2='hello, '$your_name' !'
greeting_3='hello, ${your_name} !'
echo $greeting_2 $greeting_3
可以看到字符串拼接的很自然, 直接使用连续配对的“”
拼接即可
注意到一个细节:
echo同一行输出俩变量, 中间是有俩空格的, 但是输出结果只有一个空格, 经测试:
- 在没有空格, 一个空格的情况, 都不会输出空格
- 在两个空格以上的情况, 输出一个空格
获取字符串长度:
使用#
string="abcd"
echo ${#string} #输出 4
获取子串:
string="runoob is a great site"
echo ${string:1:4} # 输出 unoo
index从0开始
查找子串:
string="runoob is a great site"
echo `expr index "$string" io` # 输出 4
注意, 这里使用到了`` `与 expr命令, 并不是字符串自带的功能, 主要使用到了expr的功能
直接参考Linux命令
其他功能:
其他字符串操作很多都是通过expr实现的, 直接去看后头的字符串操作部分
数组:
bash仅支持一维数组
栗子:
arr=(1 2 3 4)
echo ${arr[0]}
echo ${arr[@]}
echo ${arr[*]}
echo ${#arr[@]}
echo ${#arr[0]}
定义:
array_name=(value0 value1 value2 value3)
# 或
array_name=(
value0
value1
value2
value3
)
#或
array_name[0]=value0
array_name[1]=value1
array_name[n]=valuen
读取元素:
${数组名[下标]}
# 获取所有元素
echo ${array_name[@]}
echo ${array_name[*]}
获取数组长度:
# 取得数组元素的个数
length=${#array_name[@]}
# 或者
length=${#array_name[*]}
# 取得数组单个元素的长度
lengthn=${#array_name[n]}
注释:
单行注释直接使用#
标记即可
多行注释使用
:<<EOF
注释内容...
注释内容...
注释内容...
EOF
# 或者其他符号
:<<'
注释内容...
注释内容...
注释内容...
'
:<<!
注释内容...
注释内容...
注释内容...
!
命令替换:
参考博客:
https://www.cnblogs.com/chengd/p/7803664.html
命令替换与变量替换的作用相似, 变量替换是将变量的值替换到shell中, 而命令替换是将命令返回的结果替换到shell中
可以使用两种方法:
$()
或 ````
$()
比较直观, 但是并非所有类unix系统都支持
反引号 所有系统都支持, 但并不直观
所以推荐使用第二种
echo的替代方案: printf
基本语法格式:
printf format-string [arguments...]
printf "%d is bigger than %d\n" 3 2
使用上基本与C++相同, 易于上手
也可以直接在字符串中加参数
v1=1
v2=2
v3=3
printf "${v1} ${v2} ${v3}\n"
获取输入 read:
参考博客;
https://blog.csdn.net/zhizhengguan/article/details/88391694
使用read获取用户输入
echo -n "Enter your name:" #echo -n不会在字符串末尾输出换行符
read name
echo "Hello $name"
不指定变量的情况:
read不指定变量时,会存储到特殊环境变量REPLY中
read -p "Enter your name:"
echo "Hello $REPLY"
其他常用参数:
-
输出提示信息
read -p str
read -p "Enter your name:" name echo "Hello ${name}"
-
设定超时时间:
read -t set
如果未超时, 会返回0, 超时会返回一个随机非零值
read -t 5 name echo "ans=$?" echo "Hello ${name}"
-
限制字符输入个数:
当输入的字符达到预设字符个数时, 就自动退出
read -n num
read -n 5 name echo "ans=$?" echo "Hello ${name}"
-
输入密码
read命令会将输入的文本颜色设成跟背景色一样, 达到不显示的目的
read -n 5 name echo "ans=$?" echo "Hello ${name}"
流程控制:
和 Java、PHP 等语言不一样,sh 的流程控制不可为空
如果一个if-else
的分支为空, 则不要写这个分支, 否则报错
后头所有的condition都是表达式, 使用双括号(())
或中括号[]
框起来的部分, 如
if [ $a = $b ]
then
echo "$a = $b : a 等于 b"
else
echo "$a = $b: a 不等于 b"
fi
if-else:
语法格式:
if condition
then
command1
command2
...
commandN
fi
末尾的fi
是if
倒过来写, 后头还有很多类似的语法习惯
if condition
then
command1
command2
...
commandN
else
command
fi
级联版本
if condition1
then
command1
elif condition2
then
command2
else
commandN
fi
for
语法格式
for var in item1 item2 ... itemN
do
command1
command2
...
commandN
done
item1….itemN, 可以使用seq生成序列, 如:
for i in `seq 1 5`
do
echo "i=${i}"
done
[hhtxzzj@localhost temp]$ ./shellTest.sh
i=1
i=2
i=3
i=4
i=5
更方便的使用序列的方法
[hhtxzzj@localhost temp]$ echo {1..10}
1 2 3 4 5 6 7 8 9 10
for i in {1..10}
do
echo "i=${i}"
done
[hhtxzzj@localhost temp]$ ./shellTest.sh
i=1
i=2
i=3
i=4
i=5
i=6
i=7
i=8
i=9
i=10
仿C++格式:
语法与原生for有很大不同, 但是就是这样, 记着就对了
for((i=1;i<10;++i));
do
...
done
字符循环:
直接传入字符序列即可
list="rootfs usr data data2"
for i in ${list}
do
echo "i=${i}"
done
[hhtxzzj@localhost temp]$ ./shellTest.sh
i=rootfs
i=usr
i=data
i=data2
或
for i in `ls`
do
echo "i=${i}"
done
[hhtxzzj@localhost temp]$ ./shellTest.sh
i=makeFiles.sh
i=shellTest.sh
while
while condition
do
command
done
例子:
int=1
while(( $int<=5 ))
do
echo $int
let "int++"
done
until
until 循环执行一系列命令直至条件为 true 时停止
其实就是while加个!
没必要的语法, 在其他语言中都没这东西, 了解一下就好
until condition
do
command
done
case:
其实就是其他语言的switch-case
case 值 in
模式1)
command1
command2
...
commandN
;;
模式2)
command1
command2
...
commandN
;;
esac
-
每个case最后以右括号结束
可以为变量或常数, 一旦匹配, 将会一直执行直到遇到双分号
;;
-
;;
相当于C++中的break
break & continue
shell中也有break & continue
其作用也是跳出最内层循环
函数:
函数定义语法:
[ function ] funname [()]
{
action;
[return int;]
}
几个注意点:
-
函数名前头的function可选, 不加也能用
-
函数定义时不能指定参数, 即括号中必须为空, 参数调用后头再讲
-
return也可选, 如果没有return, 则默认最后一条命令的运行结果作为返回值
-
return后跟的数值n必须为0<=n<=255
如果超过255会被舍入
栗子:
function fun(){
echo "This is a funciton"
return 666
}
fun
var=$?
echo "var=${var}"
可以看到, 函数整体定义起来与C++基本相同
返回值:
Shell函数的返回值不能像C++一样, 以表达式的形式返回, 而是需要特殊的调用
在调用函数之后, 使用$?
获得函数的返回值
function fun(){
echo "This is a funciton"
return 666
}
fun
var=$?
echo "var=${var}"
参数:
之前说到函数定义时不能有参数, 但是调用的时候可以传入参数
function fun(){
echo "1 var = $1"
echo "2 var = $2"
echo "3 var = $3"
echo "4 var = $4"
echo "5 var = $5"
}
fun 5 6 7 8 9
函数中使用参数的方法与脚本中调用外部传入参数的方法相同:
参数处理 | 说明 |
---|---|
$# | 传递到脚本或函数的参数个数 |
$* | 以一个单字符串显示所有向脚本传递的参数 |
$$ | 脚本运行的当前进程ID号 |
$! | 后台运行的最后一个进程的ID号 |
$@ | 与$*相同,但是使用时加引号,并在引号中返回每个参数。 |
$- | 显示Shell使用的当前选项,与set命令功能相同。 |
$? | 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。 |
获取传入脚本的参数:
使用以下保留变量获取传入的参数:
参数处理 | 说明 |
---|---|
$# | 传递到脚本的参数个数 |
$n | 获取第n个参数, index从0开始 |
$* | 以一个单字符串显示所有向脚本传递的参数。 如"$*“用「”」括起来的情况、以"$1 $2 … $n"的形式输出所有参数。 |
$$ | 脚本运行的当前进程ID号 |
$! | 后台运行的最后一个进程的ID号 |
$@ | 与$*相同,但* 相当于将n个参数合成了一个, 如“1 2 3”, 而@ 等价与“1” “2” “3” |
$- | 显示Shell使用的当前选项,与set命令功能相同。 |
$? | 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。 |
echo "args 0 = ${0}"
echo "args 1 = ${1}"
echo "args 2 = ${2}"
echo "args 3 = ${3}"
echo "args 4 = ${4}"
echo "args # = ${#}"
echo "args * = ${*}"
echo "args @ = ${@}"
echo "args $ = ${$}"
echo "args ! = ${!}"
echo "args - = ${-}"
echo "args ? = ${?}"
可以看到第一个参数是文件名
$*
与 $@
的区别:
echo "-- \$* 演示 ---"
for i in "$*"; do
echo $i
done
echo "-- \$@ 演示 ---"
for i in "$@"; do
echo $i
done
数值运算:
bash原生并不支持数值运算, 但是可以通过其他命令实现, 如awk expr, expr最常用
运算符 | 说明 | 举例 |
---|---|---|
+ | 加法 | 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 $((5%2))
echo $((5!=2))
echo $((5==2))
echo $[1+1]
echo $[5%2]
echo $[5!=2]
echo $[5==1]
赋值运算:
var=$((5-1))
echo $var
var=$[55-1]
echo $var
关系运算:
ans=$[5==1]
echo $ans
ans=$[5!=1]
echo $ans
ans=$[5>1]
echo $ans
ans=$[5>=1]
echo $ans
ans=$[5<1]
echo $ans
ans=$[5<=1]
echo $ans
还有一种参数化的运算
参数化运算虽然没有$[]
来的好用, 但是最好也需要掌握, 看代码的时候肯定会用到
逻辑运算:
true=1
false=0
echo $[!true]
echo $[$true && $false]
echo $[$true || $false]
位运算:
var1=64
var2=32
var3=65
echo $[var1 | var2]
echo $[var1 & var3]
echo $[~var1]
字符串运算:
运算符 | 说明 | 举例 |
---|---|---|
= | 检测两个字符串是否相等,相等返回 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。 |
这里注意
字符串运算并不能像表达式一样直接返回值, 即变量赋值或输出时不能直接使用字符串运算
只能用在上头的流程控制语句中, 如if-else
a="abc"
b="efg"
if [ $a = $b ]
then
echo "$a = $b : a 等于 b"
else
echo "$a = $b: a 不等于 b"
fi
echo "ans=$[$a = $a]"
echo "ans=$[$a = $b]"
printf "ans=%d\n" $[$a = $a]
printf "ans=%d\n" $[$a = $b]
可以看到返回值都是0, 但是在if-else中却可以正常工作
文件测试运算:
操作符 | 说明 | 举例 |
---|---|---|
-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。 |
与上头的字符串运算一样, 文件测试运算同样也不能直接利用返回值, 只能放到流程控制语句中
file="/home/hhtxzzj/temp/makeFiles.sh"
if [ -r $file ]
then
echo "文件可读"
else
echo "文件不可读"
fi
if [ -w $file ]
then
echo "文件可写"
else
echo "文件不可写"
fi
if [ -x $file ]
then
echo "文件可执行"
else
echo "文件不可执行"
fi
if [ -f $file ]
then
echo "文件为普通文件"
else
echo "文件为特殊文件"
fi
if [ -d $file ]
then
echo "文件是个目录"
else
echo "文件不是个目录"
fi
if [ -s $file ]
then
echo "文件不为空"
else
echo "文件为空"
fi
if [ -e $file ]
then
echo "文件存在"
else
echo "文件不存在"
fi
输入/输出重定向
参考博客:
https://www.runoob.com/linux/linux-shell-io-redirections.html
命令 | 说明 |
---|---|
command > file | 将输出重定向到 file。 |
command < file | 将输入重定向到 file。 |
command >> file | 将输出以追加的方式重定向到 file。 |
n > file | 将文件描述符为 n 的文件重定向到 file。 |
n >> file | 将文件描述符为 n 的文件以追加的方式重定向到 file。 |
n >& m | 将输出文件 m 和 n 合并。 |
n <& m | 将输入文件 m 和 n 合并。 |
<< tag | 将开始标记 tag 和结束标记 tag 之间的内容作为输入。 |
还有个Linux中默认的文件描述符:
- 0 通常是标准输入(STDIN)
- 1 是标准输出(STDOUT)
- 2 是标准错误输出(STDERR)
这也是之前2>&1
是将标准错误输出重定向到标准输出的操作缘由
重定向这里用到的比较多, 如果有啥不懂的再来看参考博客吧
引用外部脚本
Shell同样支持将一些公用的代码单独封装到一个文件中, 并在其他文件中引用
语法格式:
. filename # 注意点号(.)和文件名中间有一空格
#或
source filename
注意, 引用文件中的内容也将被执行
栗子:
#! /bin/bash
# shellTest2.sh
msg="This is shellTest2.sh msg"
echo "$0 : msg = ${msg}"
#! /bin/bash
# shellTest.sh
source ./shellTest2.sh
echo "msg = ${msg}"