Shell脚本的基本使用方法介绍,Shell脚本是一种脚本语言,支持逻辑判断, 循环执行等操作,可以实现批量处理非常的复杂Shell命令, 是Linux系统高级用法的灵魂
Shell 脚本介绍
- Shell 一个命令行解释器,它的作用是将输入的命令加以解释并传给系统执行, 是用户与系统沟通的桥梁, 而 Shell 脚本是一种脚本语言, 支持逻辑判断, 循环执行等操作, 可以实现批量处理非常的复杂Shell 命令, 是 Linux 系统高级用法的灵魂
- Shell 脚本以文件的形式存在,格式为
*.sh
,需要注意文件默认是没有执行权限的, 因此我们需要手动给 shell 脚本执行权限, chmod +x ./test.sh #使脚本具有执行权限./test.sh #执行脚本
- 我们可以使用三种方式调用 shell 脚本
- . xx.sh 先按照定义的指定解析器解析,如果找不到使用默认解析
- 定义指定解析器的格式为在脚本行首添加
#!/bin/bash
或其他解析器
- bash xxx.sh 指定使用 bash 解析器解析,如果找不到使用默认解析
- source xx.sh 直接在当前环境中运行的 shell 中运行脚本
- 如果使用
source *.sh
命令则不需要给脚本文件执行权限
- 除此之外, 将脚本添加到
/etc/profile
或 ~/.bashrc
中会在登录或打开终端时自动调用此脚本
创建一个脚本
- 脚本的开头为
#!/bin/bash
, 如果没有这一行, 会使用默认的解释器,只能写在第一行
- /bin/sh
- /bin/bash
- /usr/bin/sh
- /usr/bin/bash
#
代表单行注释, <<EOF … EOF 或 :<<! … !
代表多行注释
基础语法
- Shell 脚本基础写法和概念与常见的编程语言类似, 本文档只解释Shell脚本和其他编程语言语法上的区别, 不解释详细细节,需要一定编程基础才能理解
变量
- 普通变量:
test1=10
- 只读变量:
readonly test2="Hello World"
无法修改变量的值
- 局部变量:
local test3="Hello Linux"
只在函数体内有效
- 环境变量:
export test4="Hello shellScript"
只要不重启电脑就永久有效
- 想要永久生效, 可以将环境变量编辑进
/etc/profile
或 ~/.bashrc
- 使用
env
命令可以查看当前所有的环境变量
- 为了规范书写, 建议将定义环境变量时所有字母大写
- 删除变量:
unset test1
- 使用变量:
echo $test2
或 echo {$test2}
{}用于区别变量和字符串例如 echo {$test2}NiHao
- Shell 脚本中存在一些特殊变量
特殊变量列表变量 | 含义 |
---|
$0 | 当前脚本的文件名 |
$n | 传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是$1,第二个参数是$2。 |
$# | 传递给脚本或函数的参数个数。 |
$* | 传递给脚本或函数的所有参数。被双引号 (" ") 包含时,"$\*" 会将所有的参数作为一个整体 |
$@ | 传递给脚本或函数的所有参数。被双引号 (" ") 包含时,"$@" 会将各个参数分开 |
$? | 上个命令的退出状态,或函数的返回值。 |
$$ | 当前 Shell 进程 ID。对于 Shell 脚本,就是这些脚本所在的进程 ID。 |
$! | 后台运行的最后一个进程的 ID |
字符串
- 使用单引号''创建的字符串只能原样输出, 字符串中的变量是无效的, 且不支持转义字符
- 双引号""里可以有变量, 也可以使用转义字符, 非特殊情况建议使用双引号
- 字符串是可以拼接到一起的
str01="1""2"
str3="hello, "$test2""
str03=${test01}${test02}
- 获取字符串长度
echo ${#name}
echo $test1 | wc -L
- 提取字符串
MYVAR=foodforthought.jpg
${varible##*string}
从左向右截取最后一个 string 后的字符串
${varible#*string}
从左向右截取第一个 string 后的字符串
${varible%%string*}
从右向左截取最后一个 string 后的字符串
${varible%string*}
从右向左截取第一个 string 后的字符串
${varible:n1:n2}
从n1个位置到底n2个位置的字符串
- 读取用户输入
read
命令会将提示符后输入的所有数据分配给单个变量,要么就指定多个变量。输入的每个数据值都会分配给变量列表中的下一个变量。如果变量数量不够,剩下的数据就全部分配给最后一个变量
- -p 设置提示信息
- -d 定义结束标志
- -e 允许使用命令补全功能
- -a 定义一个数组
- -n 定义文本长度, 超过长度自动结束
- -t 定义时间, 超过时间自动结束
- 示例
read -p "Enter your name:" test01
echo "$test01"
数组
- 在 Shell 脚本中, 数组的下标不需要连续, 且没有范围限制
数组名=(元素1 元素2 元素3)
定义数组
数组名[0]=值
给该数组第一个元素赋值
数组名=([0]=值1 [1]=值2 [2]=值3)
给该数组多个元素赋值
${数组名[0]}
引用数组
${数组名[*]}
引用数组中的全部元素, 用 @
也行
${#数组名[@]}
获取数组长度, 和获取字符串长度的方法一样
c=($a{*} $a{*})
将 a 和 b 的数组元素合并为新的数组 c
unset a
或 unset a[3]
删除数组或删除数组中的指定元素
运算符
- bash 中的运算符依靠
expr
命令实现, 以下表格中的 a
和 b
都是变量
需注意:
条件表达式要放在方括号之间, 表达式和运算符之间要有空格
使用 expr
进行计算时需要使用 ` ` 将表达式包含起来
算数运算符
运算 |
写法 |
加法 |
expr $a + $b |
减法 |
expr $a - $b |
乘法 |
expr $a \* $b |
除法 |
expr $b / $a |
取余 |
expr $b % $a |
赋值 |
a=$b |
相等 |
[ $a == $b ] |
不相等 |
[ $a != $b ] |
#!/bin/bash
a=10
b=20
val=`expr $a + $b`
echo "a + b : $val"
关系运算符
- 只支持数字,不支持字符串,除非字符串的值是数字,字符串有单独的运算符
运算 |
写法 |
主要符号 |
检测两个数是否相等 |
[ $a -eq $b ] |
-eq |
检测两个数是否不相等 |
[ $a -ne $b ] |
-ne |
检测左边的数是否大于右边的 |
[ $a -gt $b ] |
-gt |
检测左边的数是否小于右边的 |
[ $a -lt $b ] |
-lt |
检测左边的数是否大于等于右边的 |
[ $a -ge $b ] |
-ge |
检测左边的数是否小于等于右边的 |
[ $a -le $b ] |
-le |
#!/bin/bash
a=1
b=2
if [ $a != $b ]
then
echo "$a != $b : a 不等于 b"
else
echo "$a == $b: a 等于 b"
布尔运算符
运算 |
写法 |
主要符号 |
非运算 |
[ ! false ] |
! |
或运算 |
[ $a -lt 20 -o $b -gt 100 ] |
-o |
与运算 |
[ $a -lt 20 -a $b -gt 100 ] |
-a |
逻辑运算符
- 逻辑运算符需要双中括号括起来, 布尔运算只需要单括号
- 此外逻辑运算符具有短路功能, 在 AND 运算中第一个表达式为 false 时则不执行第二个表达式,在 OR 运算中第一个表达式为 true 时不执行第二个表达式。
运算 |
写法 |
主要符号 |
逻辑的 AND |
[[ $a -lt 100 && $b -gt 100 ]] |
&& |
逻辑的 OR |
[[ $a -lt 100 || $b -gt 100 ]] |
|| |
字符串运算符
- 字符串需要使用专用的运算符, 不要和算数运算符和关系运算符弄混
运算 |
写法 |
主要符号 |
检测两个字符串是否相等 |
[ $a = $b ] |
= |
检测两个字符串是否不相等 |
[ $a != $b ] |
!= |
检测字符串长度是否为 0 |
[ -z $a ] |
-z |
检测字符串长度是否不为 0 |
[ -n "$a" ] |
-n |
检测字符串是否为空 |
[ $a ] |
Dollar符号 |
文件测试运算符
- 主要用于检测 Unix 文件的各种属性, 同样返回 true 或 false
运算 |
写法 |
主要符号 |
检测文件是否是块设备文件 |
[ -b $file ] |
-b file |
检测文件是否是字符设备文件 |
[ -c $file ] |
-c file |
检测文件是否是目录 |
[ -d $file ] |
-d file |
检测文件是否是普通文件(既不是目录,也不是设备文件) |
[ -f $file ] 返回 true |
-f file |
检测文件是否设置了 SGID 位 |
[ -g $file ] |
-g file |
检测文件是否设置了粘着位 (Sticky Bit) |
[ -k $file ] |
-k file |
检测文件是否是有名管道 |
[ -p $file ] |
-p file |
检测文件是否设置了 SUID 位 |
[ -u $file ] |
-u file |
检测文件是否可读 |
[ -r $file ] |
-r file |
检测文件是否可写 |
[ -w $file ] |
-w file |
检测文件是否可执行 |
[ -x $file ] |
-x file |
检测文件是否为空(文件大小是否大于 0) |
[ -s $file ] |
-s file |
检测文件(包括目录)是否存在 |
[ -e $file ] |
-e file |
#!/bin/bash
file="/home/textcpp/test.sh"
if [ -r $file ]
then
echo "文件可读"
else
echo "文件不可读"
fi
运算指令
- (( ))
- 直接使用双圆括弧计算其中的内容,如 ((var=a+b)),经常在 if/while 等条件判断中需要计算时使用。
((a==b))
等价于 [ $a == $b ]
等价于 [ $a -eq $b ]
((a>b))
等价于 [ $a -gt $b]
- let
- 在计算表达式的时候可以直接使用 let,如 let var=a+b。
var=`expr a+b`
- bc 计算器
- bc 计算器支持 shell 中的小数进行运算,并且可以交互式或者非交互式的使用。基本使用方式为
var=$(echo "(1.1+2.1)"|bc)
;
- bc 的浮点运算由
scale
变量控制, 仅对除法生效,需要显示浮点数时, 请直接修改 scale 的值 scale=3
- $[]
- 可以直接使用这种方式计算中括弧中的内容,如
echo $[1+2]
选择结构
if 语句
if [ $a == $b ]
then
command1
command2
fi
#或者以下写法
if [ $a == $b ]; then
command1
command2
fi
#test命令用于检查条件是否成立,与[]类似,当test后面没有命令时默认为false
if test $a -eq $b
if [ $a == $b ]
then
command1
else
command2
fi
if [ $a == $b ]
then
command1
elif [ $a -gt $b ]
command2
else
command3
fi
- 在 Shell 脚本中,if 需要以 fi 结尾表示结束
- 如果返回值为 true, 则执行 then 后面的语句, 返回值为 false 时执行 else 后面的语句
case 语句
read a
case $a in:
1)
command1
command2
;;
2)
command3
command4
;;
*)
command4
;;
esac
- 基本规则与 C 语言的 switch-case 类似, 匹配到相同值时, 一直运行到结束或遇到
;;
为止, ;;
等价于 C 语言中的 break
, 但 Shell 中的 break 命令并不适用于 case, 只适用于循环
- 当所有值都不匹配时, 运行*内的命令
- 同样以倒过来的 esac 结尾表示结束
循环结构
for 语句
for i in 1 2 3 4 5
do
echo $i
echo $i
done
#支持C语言风格的写法,需要两个括号
for((i=1;i<=10;i++))
do
command1
done
- 每循环一次, 列表里的值就会依次赋给变量
- 使用 for 命令遍历目录与文件时, 最好将变量用双引号包围, 例如
'$file'
, 因为 Linux 允许包含空格的文件名和目录名, 但 shell 脚本会将空格, 换行符,TAB 键视为分隔符
- 此外,IFS 变量可以用定义分隔符, 例如
IFS=":"
, 会将冒号视为分隔符
while 语句
a=0
while [ $a -gt 10 ]
do
a=`expr $a+1`
if [ $a -eq 6 ]
then
continue
elif [ $a -eq 8 ]
then
break
fi
echo $a
done
#可以循环读入,按Crtl+D结束输入
while read b
do
echo "You input $b"
done
- 循环同样是支持 break 和 continue 的,
break 3
可以直接跳出三层循环
- shell 中, 实现无限循环的方法为
for (( ; ; )
和 while :
和 while true
until 语句
a=0
until ((a>10))
do
echo $a
a=`expr $a + 1`
done
- until 的判断方法与 while 相反,until 只有在返回值为 false 时执行, 当返回值为 true 时停止循环
select-in 语句
echo "What is your favorite OS?"
select var in "Windows" "Linux" "MacOS"
do
case $var in
"Windows")
echo "by Microsoft"
break;;
"Linux")
echo "Open Source System by Linus"
break;;
"MacOS")
echo "by Apple"
break;;
*)
echo "Error input"
break;;
esac
done
echo "You have selected : $var"
- select-in 是 shell 脚本中特有的循环语句, 将列表打印成菜单, 用户可以选择菜单并执行不同的功能
PS3
变量是 select-in 的菜单提示符, 默认值为 #?
, 可以自行修改
函数
function_name(){}
或 function function_name{}
function sub()
{
res=`expr $1 - $2` # $1 代表传递过来的第一个参数,从10开始需要写成 ${10}
echo "减法结果为$res"
}
sum() # 创建函数时的 function 关键字写不写都行
{
res=`expr $1 + $2`
echo "加法的结果为"
return $res # 不写return的话,默认将最后一条命令的直接结果作为返回值,默认返回值为0,非0表示执行失败
}
sum 3 3 # 调用函数执行的方式为: 函数名 参数1 参数2 参数3
echo $? # 调用返回值的方法为 $? ,需要注意的是该命令只能调用上一次执行的命令的返回值
sub 10 7
echo $?
- 函数内变量的作用域为全局变量, 在 shell 内的任意位置都有效, 在函数内对外部变量做的修改也会生效, 但传递进去的变量传递的只是该变量的参数, 修改传进去的变量的值不影响参数的值, 因此不要沿用常见编程语言的编写习惯
- 想让函数内的变量只在函数内生效, 可以加上
local
使其变为局部变量
- shell 脚本的函数同样是支持嵌套和递归调用的
- 删除函数:
unset .f functionName
重定向
- 除了标准的输入输出重定向之外,Shell 脚本还支持一些特殊的重定向方法, 例如 Here Document, 它的作用是将两个 delimiter (分隔符) 之间的内容 (document) 作为输入传递给 command, 基本格式是
command << delimiter 内容delimiter
#!/bin/bash
cat << COMMAND
Hello
Nihao
COMMAND
例题讲解
在/usr/bin 目录下创建一个 repwis 脚本, 查找/usr 目录下小于 10M 且组 ID 不为 root 的文件, 把查到的文件结果拷贝到/root/cyfiles 文件内
vim /usr/bin/repwis
#进入vim后编辑以下内容
#!/bin/bash
mkdir /root/cyfiles
for i in `awk -F":" '{print $3}' /etc/group` #将group文件的gid信息过滤出来作为列表
do
if [ 0 != $i ] #如果gid不是0的话,即gid不为root
then
find /usr -size -10M -gid $i -type f -exec cp -a {} /root/cyfiles \;
#cp的-a参数一定不能忘记,否则文件的源数据不会保留
fi
done
#保存退出
chmod +x /usr/bin/repwis
bash /usr/bin/repwis