最近一直想写一个Shell脚本,用来迁移服务器的时候可以执行脚本自动开始部署网站,于是参考了很多教程,做了下面的简记。
创建Shell
脚本
大多数Linux
系统默认使用的shell
是bash shell
(Bourne Again Shell
),Shell
脚本分为简单的命令堆积和程序的设计
echo $SHELL # 查看系统默认使用的是什么Shell
Shell
脚本的开头如下,有些高版本的内核可以不写
#! /bin/bash
Shell
脚本文件的后缀名可以随意,只要有执行权限就行,一般为*.sh
使用touch
命令或者vim
(vi
)命令来创建Shell
脚本
运行Shell
脚本
使用chmod +x 文件路径
来为Shell
脚本添加执行权限,运行的时候使用./文件名
来执行当前文件夹中的shell
脚本
./shell.sh # ./shell脚本文件名
. shell.sh # . shell脚本文件名
source shell.sh # source shell脚本文件名
/bin/bash shell.sh # /bin/bash 脚本名称
/bin/sh
是/bin/bash
的一个快捷方式
也可以将Shell
脚本变为命令:修改家目录下面的.bashrc
文件,添加一个别名
vim ~/.bashrc
把Shell
配置成一个环境变量
vim /etc/profile
注释
单行注释直接使用#
即可,下面的案例中较多使用了单行注释
多行注释的方式有点特殊,需要使用下面的形式,EOF
也可以替换成其他的一些字符串
:<<EOF
注释内容...
注释内容...
注释内容...
EOF
变量的声明和使用
shell
中的变量没有数据类型(数据类型只有字符串),直接声明即可,变量名的命名规范和其他语言基本类似,变量声明的语法为:变量名=变量值
,等号连接,等号左右不能使用空格隔开
使用变量的时候使用$变量名
或者${变量名}
,使用前者虽然很方便,但是使用前者可能造成无法取值的情况,使用后者更加安全。
var="${name}Hello World" # 如果写成var="$nameHello World"则会发生歧义,导致程序错误
字符串变量使用单引号不能实现转义,结果会原样输出。
双引号会识别变量也能进行转义。
也可以完全不使用引号。
变量的作用域
-
全局变量(环境变量)
-
局部变量(本地变量)
-
Shell变量(
Shell
中默认就有的变量)
查看全局变量中的某个变量
env | grep PATH
使用export
的方式将一个变量变成环境变量
export 变量名
使用unset
删除变量
unset 变量名
如果需要声明一个只读变量
readonly 变量名 # 只读变量使用readonly来修饰
如果变量的值需要一个命令的执行结果
$(date) # $(命令)的形式
`date` # `命令`的形式
变量中的转义也和其他语言基本类似:
echo \$PATH # 回显 $PATH
touch ./--abc # 创建一个文件名为--abc
touch -- --abc # 也可以通过转义的方式创建--abc
如果变量的值是字符串,但是不想在一行书写,又不想换行,就可以使用到续行符了:
echo Hello \
World # \为续行符
如果需要使用用户的输入作为变量值
# read -p 提示信息 变量名
read -p '请输入需要创建的文件路径:' filepath # 其中的-p只是起到提示信息的作用
touch $filepath
echo '文件创建成功'
字符串的一些操作
计算出字符串的长度:
string="abcd"
echo ${#string} #输出 4
截取字符串的一部分:
string="Hello World"
echo ${string:1:4} # 输出 ello
查找子字符串:
例如:查找字符l
或o
的位置(最后计算出来的值是最先出现的字母的位置):
string="Hello World"
echo `expr index "$string" lo` # 输出 3
算数运算符
Shell中的算数运算符依然有 +
、-
、*
、/
、%
、=
、==
、!=
但是需要注意的是*
默认表示通配符,如果使用乘号需要写成\*
*
号默认表示通配符,例如下面的形式
ls *.sh
再介绍两种通配符
ls ??.sh
ls [a-z].sh
还有就是注意上面的算数运算符不能直接使用,Shell
是不支持的,需要借助一点手段,如下所示:
var=10
$((var+5)) # 相当于$((${var}+5))
$[var+5] # 使用$[四则运算]
`expr $a \* $b` # 使用expr命令来进行四则运算的计算,必须要有空格,$a+$b不正确,这里的*号需要转义
let var=1+1 # 使用let命令定义的变量也可以进行四则运算
如果相应对进制进行运算:
$[2#101+3] # 101是二进制,相当于5+3
$[8#101+3] # 101是八进制,相当于65+3
数组的定义
定义数组
array_name=(value0 value1 value2 value3) # 注意中间是空格,不是逗号
为数组赋值
array_name[0]=value0
array_name[1]=value1
array_name[n]=valuen
取出数组中的某个值
${数组名[下标]}
获取数组中所有元素
echo ${array_name[@]}
获取数组的长度
length=${#array_name[@]} # 取得数组元素的个数
length=${#array_name[*]} # 取得数组元素的个数的第二种方式
lengthN=${#array_name[n]} # 取得数组单个元素的长度
条件控制语句
基本语法
if [ 条件 ]
then
...
fi
如果要使用单行
if [ 条件 ]; then ...; fi
使用else
if [ 条件 ]
then
...
else
...
fi
使用else if
if [ 条件 ]
then
...
elif [ 条件 ]
then
...
else
...
fi
写一个永远为true
的条件控制语句
# 下面这段命令中的":"是永远为真的意思,如果在while后面加上:就是死循环了
if :; then echo "always true"; fi
条件控制语句中条件的写法
可以使用test
命令来作为条件
var=10
test $var -gt 5 # 和[ $var -gt 5 ]的形式执行的结果完全一样,1 假 0 真
# 需要注意,条件写法中必须要严格注意空格,错误写法[$a==$b]
# 除了变量的赋值操作必须不需要空格,其他的操作基本上都需要空格
echo $? # $?意思是取出上一次执行的结果
推荐使用双引号把变量括起来然后进行比较
[ -d test1 -a "$var" = 'hello'] # 推荐写成这种方式
[ -d test1 -a $var = 'hello'] # 这种写法,如果var不存在的话会直接报错,上面的写法不会报错
关系运算符
关系运算符只支持数字,不支持字符串(数字字符串除外),包括有-eq
、-ne
、-gt
、-lt
、-ge
、-le
逻辑运算符
-a
(与) -o
(或) !
(非)
逻辑运算符也可以和其他语言一样使用&&
、||
[ $a -lt 20 -o $b -gt 100]
字符串运算符
=
、!=
、-z
(字符串长度是否等于0)、-n
(字符串长度是否不等于0)、[ $var ]
(判断字符串是否为空)
[ -z $str ]
文件测试运算符【部分摘录】
-b file
检测文件是否是块设备文件(U盘等)
-c file
检测文件是否为字符设备
-d file
检测文件是否是一个目录
-f file
检测文件是否是普通文件(不是目录和设备文件)
-r file
文件是否可读
-w file
文件是否可写
-x file
是否可执行
对于可读,可写,可执行来说,文件权限中不需要拥有者,群组和其他组全部具有权限,任意一方具有一个x
,r
,w
就被判断为true
-s file
检测文件是否为空(文件大小是否大于0)
-e file
检测文件(目录)是否存在
switch
的语法
#! /bin/sh
echo "现在是早上吗?"
read YES_OR_NO
case "$YES_OR_NO" in
yes|y|Yes|YES) # 使用右圆括号做模式匹配
echo "早上好!"
;; # ;;防止case穿透,相当于其他语言中的break
[nN]*)
echo "下午好!"
;;
*)
echo "请输入yes或者no"
;;
esac # case倒过来写,表示结束
while
循环
基本用法
while [ 条件 ]
do
command
done
死循环的写法
while true # true也可以改为:
do
command
done
while循环的一个小案例:
#! /bin/bash
echo "请输入密码:"
read TRY
while [ "$TRY" != "123456" ]; do
echo "请重试..."
read TRY
done
如果需要规定密码输入的次数,可以使用下面的形式来进行累加操作
COUNTER=$(($COUNTER+1)) # 累加操作
for
循环
使用foreach
for FRUIT in apple banana pear; do
echo "I like ${FRUIT}"
done
循环遍历1-100
for i in {1..100}
do
echo $i
done
循环遍历1-100第二种方式
for i in `seq 1 100`
do
echo $i
done
for
循环也可以像其他语言的写法一样
for ((i=1; i<=100; i ++))
do
echo $i
done
for循环的死循环
for (( ; ; ))
break
和continue
的使用
break
和continue
和其他语言都一样,不一样的是break[n]
可以跳出多层循环
Shell
参数
怎么取出命令行执行Shell
脚本的时候传入的参数
echo $1 $2 $3 # $序号 (第一个参数就是$1)
$#
命令行中参数的个数
$@
将命令行中的参数作为一个数组,可以用作for
循环
$*
和 $@
类似,只有在双引号中才能体现出来不同点。假设在脚本运行时写了三个参数1 2 3
,则 *
等价于 "1 2 3"
(合并为了一个参数),而 @
等价于 "1" "2" "3"
(作为了三个参数)。
$$
脚本运行的当前进程ID号
shift
命令,可以将命令行参数的下标左移,原来$2
的位置的参数变成了$1
,使用时在Shell
脚本中单独占一行即可
函数
前面也可以加上function
关键字,也可以不加
function_name() {
function_body...
}
定义一个方法,并调用
print() {
echo "中间"
}
echo "开"
print
echo "结束"
函数传递参数的时候和命令行参数类似,取参的时候也是使用$1
这样的形式取参
func() {
echo $1
echo $2
}
func 1 2
函数内部如果使用$0
,和函数外面的$0
意义一样,都表示脚本名称本身
控制台打印
echo
命令中的参数:
-e
带有转义,
-n
不换行(也可以使用-e
参数中\c
替代)
printf
命令的使用
printf 格式控制字符串 参数列表
格式控制字符串中的格式替换符例如:%s
、%c
、%d
、%f
%-10s
指一个宽度为10个字符(-
表示左对齐,没有则表示右对齐),任何字符都会被显示在10个字符宽的字符内,如果不足则自动以空格填充,超过也会将内容全部显示出来。
%-4.2f
指格式化为小数,其中.2
指保留2
位小数。
重定向
重定向的一般用法
ps -aux | grep bash > out.log
上面的重定向直接就输入到了文件中,如果既想输出到文件中,又想在控制台上面显示:
ps -aux | grep bash | tee out.log
如果想让内容追加到已经存在的文件中
ps -aux | grep bash | tee -a out.log # 其中的-a是追加
默认情况下,错误信息是不会追加到文件中的,所以需要使用下面的方式来解决
命令 > 文件 2>&1
0
代表标准输入
1
代表标准输出
2
代表标准出错
上面的命令意思是将标准出错也定位到所指的文件中
如果想要控制台上完全不输出信息
command > /dev/null 2>&1
/dev/null
是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容,那么什么也读不到。但是 /dev/null
文件非常有用,将命令的输出重定向到它,会起到"禁止输出"的效果。
其余shell
脚本中常用的语法或者命令
脚本调试
脚本 -n
读一遍脚本中的命令,但是不执行,用来检查脚本中的语法错误
脚本 -v
边执行脚本将执行过的脚本命令打印到标准错误输出
/bin/bash -x 脚本名称
提供跟踪执行信息,将执行的每一条命令和结果依次打印出来,另一种同样的做法是将开头写成#! /bin/bash -x
如果想要调试指定的行:
在开始位置写上set -x
结束的位置写上set +x
Linux
脚本中总是反过来写语句相当于一个语句的结束,所以上面的-x
和+x
是一个开始一个结束
grep
命令 找文本内容
查询那个文件中包含某个字符串
grep -r "h" out.log -n # grep -r "字符串" 目录位置 -n , 其中的-n可以展示行号
find
命令 找文件
查找某个目录中是否有规定文件名的文件
find / -name "out.log" # find 目录位置 -name "文件名",也可以使用通配符find / -name "*init*"
搜索大于2k且小于5k的文件
find / -name -size +2k -size -5k
搜索所有类型为文件的文件,只搜索一层目录
find ./ -maxdepth 1 -type f
在查找到文件后并执行删除命令,并在删除前要一个文件一个文件的询问是否要进行删除
find ./ -maxdepth 1 -type d -ok rm -rf {} \;
xargs
可以解决查询的文件列表中数据太多,导致的内存泄露问题,详情
find ./ -maxdepth 1 -type d | xargs ls -ldh
xargs
在执行的时候默认使用的是空格来拆分结果集的,所以遇到一些文件名中带有空格的文件时,xargs
就不怎么好用了,所以-print
参数就派上用场了
find ./ -type f -print0 | xargs -0 ls -lh # xargs -0将\0作为定界符
更改用户是否可以登录
usermod -s /sbin/nologin 用户名
参考资料:
Shell
文件包含
可以使用.
或source
来完成Shell
文件包含,详细