Shell编程简记

CY 2019年03月12日 1,190次浏览

最近一直想写一个Shell脚本,用来迁移服务器的时候可以执行脚本自动开始部署网站,于是参考了很多教程,做了下面的简记。

创建Shell脚本

大多数Linux系统默认使用的shellbash shellBourne Again Shell),Shell脚本分为简单的命令堆积程序的设计

echo $SHELL # 查看系统默认使用的是什么Shell

Shell脚本的开头如下,有些高版本的内核可以不写

#! /bin/bash

Shell脚本文件的后缀名可以随意,只要有执行权限就行,一般为*.sh

使用touch命令或者vimvi)命令来创建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

查找子字符串:

例如:查找字符lo的位置(最后计算出来的值是最先出现的字母的位置):

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 (( ; ; ))

breakcontinue的使用

breakcontinue和其他语言都一样,不一样的是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 用户名

参考资料:

菜鸟教程-Linux命令大全

Shell文件包含

可以使用.source来完成Shell文件包含,详细