最近终于放假了,当然修仙了一些时日。日子不能太水,最近再研究mc服务器的时候,觉得手动启动mc服务器实在太麻烦了,所以像写出来一个系统服务脚本来启动minecraft服务器,但是又没有shell编程的基础,我花了一天的时间研究了一下,现在把笔记誊写上来!
1. 认识shell
1.1 什么是shell
Shell是LINUX内核和用户之间的解释器程序,解释器有很多种,通常是指bash
,负责向内核翻译以及传达用户/程序指令。Shell 既是一种命令语言,又是一种程序设计语言。

shell的使用方式:
- 交互指令模式 :人工输入命令,执行效率低
- 非交互指令模式:使用脚本,在后台执行,需要程序员编写
我们可以选择的解释器有很多,我们通常默认的就是bash
,使用cat /etc/shells
可以查看电脑上有哪几种shell解释器:
我这里使用的是 zsh,可以说是个性话的bash解释器。如果我们向切换解释器只需要在,命令行中输入对应的解释器即可!输入 exit
就可以退出当前shell解释器,我们登录linux系统后,进入的界面就是shell解释器,我们使用pstree
查看进程树
我采用的是 ssh链接到服务器,由于默认的解释器是zsh
,所以一进去系统就是zsh的交互界面。如果我们此时输入 exit
退出当前解释器,那么就会自动终端我们的ssh连接。
1.2 什么是shell脚本
标准脚本的构成:
-
声明解释器,一般我们声明bash解释器,兼容最好,一般机器上的默认解释器
#!/bin/bash
-
注释 脚本介绍
-
具体代码 ,其实就是我们的shell命令,系统会一行一行的执行
#! /bin/bash echo hello world echo shell脚本
如何执行shell脚本
- 输入绝对或者相对路径 执行 ,需要脚本具有可执行权限
- 输入
bash [脚本名/路径]
,重新开辟一个bash解释器,执行脚本,并且执行完毕后自动退出开辟的shell脚本。无需可执行权限 - 输入
source 脚本名/路径
,在当前解释器里执行脚本,执行完毕解释器状态会改变,也无需可执行权限
#! /bin/bash
cd ~
mkdir bash
我们输入编写一个测试脚本来验证一下后面两条:
使用bash执行
使用 source执行:
退出shell脚本使用 exit
命令即可
2. shell 语法
2.1 变量
我们可以使用set
查看所有定于的变量
变量的类型有如下几种:
- 自定义变量
- 环境变量
- 位置变量
- 预定义变量
自定义变量以及使用
有了变量我们写的程序更加灵活了。 我们可以直接在 bash里直接设定变量,然后也可以读取它,例如:
我们在自定义变量的时候,可能要求和其他编程语言语法有些不同,但是大致语法一致!
- 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头。
- 中间不能有空格,可以使用下划线(_)。
- 不能使用标点符号。
- 不能使用bash里的关键字(可用help命令查看保留关键字)。
- 变量名不加美元符号
$
定义变量的时候,我们通常直接显式赋值! 如
a=10
b= "10"
echo $a
echo $b
变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界
除了显式地直接赋值,还可以用语句给变量赋值,如:
for file in `ls /etc`
或
for file in $(ls /etc)
使用变量,想到大家已经猜到了 这里和php的做法一致: $变量名
的形式取出来,但是有如下情况,我们向变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界,例如:
a=5
echo 我是第$a个程序员
解释器无法智能的理解变量名是 a
,而去把变量名理解成了 a个程序员
,这里少了什么?少了边界! 如果我们使用{}
括起来,完美解决了:
echo 我是第${a}个程序员
只读变量: 使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变。例如:
a=10
readonly a
a=11#错误 zsh: read-only variable: a
删除变量 :unset ,变量被删除后不能再次使用。unset 命令不能删除只读变量。例如 unset a
环境变量
所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。使用env
命令就可以输出所有环境变量!
bash有两个基本的系统级配置文件:/etc/bashrc
和/etc/profile
。这些配置文件包含了两组不同的变量:shell变量和环境变量。shell变量是局部的,而环境变量是全局的。环境变量是通过shell命令来设置。设置好的环境变量又可以被所以当前用户的程序使用。
常用的环境变量如下:USER UID HOME PATH SHELL PS1 PS2 TMOUT
这些都是大写命令组成,我们shell可以直接使用它,我们简单介绍一下 PS1 PS2 TMOUT
-
PS1
第一级Shell命令提示符,root用户是#,普通用户是$
-
PS2
第二级命令提示符,默认是“>” ,什么是 二级命令提示符 ? 我们是输入一大串命令的时候 ,可以使用
\
连接符换到下一行输入,此时,bash的提示符就是二级提示符: -
TMOUT
用户和系统交互过程的超时值。
系统与用户进行交互时,系统提示让用户进行输入,但用户迟迟没有输入,时间超过TMOUT设定的值后,shell将会因超时而终止执行。
-
PATH , 这里和windows上的 path是一致的,只要我们把可执行文件添加到PATH中去 ,我们输入文件名就可以执行对应的可执行文件。每一个冒号都是一个路径,这些搜索路径都是一些可以找到可执行程序的目录列表。当我们输入一个指令时,shell会先检查命令是否是内部命令,不是的话会再检查这个命令是否是一个应用程序。然后shell会试着从搜索路径,即PATH中寻找这些应用程序。如果shell在这些路径目录里没有找到可执行文件。则会报错。若找到,shell内部命令或应用程序将被分解为系统调用并传给Linux内核。
那么我们如果添加 PATH呢?使用
export
指令,将a.out的路径添加到搜索路径当中,命令:export PATH=$PATH
:路径 (PATH中路径是通过冒号“:”进行分隔的,把新的路径加在最后就OK)
位置变量 与 预定义变量
和bat脚本一样,也有位置变量,bat中读取%1 %2 %...可以读取执行命令传入的第一个参数和第二个参数 等,shell这里也是这样的,$1
$2
可以取出对应的变量。
常用的预定义变量有很多 ,如 $ # * @ ? 0 !
我们直接使用shell脚本展示一下
#! /bin/bash
echo 位置变量和预定义变量
echo 1:$1
echo 2:$2
echo '$':$$
echo '#':$#
echo *:$*
echo @:$@
echo ?:$?
echo !:$!
部分预定义变量含义如下:
#
$#变量是命令行参数或位置参数的数量?
,$? 变量是最近一次执行的命令或shell脚本的出口状态,0表示成功 非零表示错误$
,$$ 变量是shell脚本里面的进程ID。Shell脚本经常使用 $$ 变量组织临时文件名,确保文件名的唯一性。!
,&! 变量的值是最近运行的一个后台进程的PID ,我们执行命令的时候使用&
,可以让程序后台执行,使用&!获取它的进程号*
,$* 变量的值表示所有的位置参数,其值是所有位置参数的值@
,$@ 变量的值类似于$*,表示所有的位置参数,其值也是所有位置参数的值。
2.2三种引号
shell脚本有三种引号我们要注意
-
“”
界定范围 -
‘’
界定范围并且屏蔽特殊符号! -
`` //反撇号
反撇号的作用就是,当包含的是指令的时候,会将该指令的执行结果返回出来,我们称之为命令替换,与之作用相同的是 $()
2.3全局变量和局部变量
我们通常使用 a=10
,声明的是局部变量,只能在当前bash中使用,如果我们新开一个bash, a变量就未定义了!
有局部就有全局,我们可以将一个定义完成的变量声明为全局变量 使用 export 变量名
,也可以边定义边 声明为全局 export a=10
,如果想取消它的全局身份,我们需要输入 export -n a
这样a转换成为局部变量了。
2.4回显控制
和C语言一样,我们需要scanf()类似功能。shell中我们常用 read
命令
read可以带有-a, -d, -e, -n, -p, -r, -t, 和 -s八个选项。会使用 -p
,我感觉就够了
#! /bin/bash
read -p "请输入变量 n" n
echo 输入变量为$n
当然如果我们输入的变量如果是命令是 密码等 这样 私密的数据,我们可以使用 stty -echo
屏蔽键盘输入bash的内容 却不屏蔽系统输出 ,再次输入stty echo
就会恢复了。
当然如果我们想把系统的回显也屏蔽呢? 我们可以把 该命令的标准错误和 标准输出都重定向到 /dev/null
中,如下
ls &> /dev/null
2.5变量运算
expr
常有运算符 + - * / %
,不必多言
我们使用 expr
命令来运算表达式并且输出:例如
#! /bin/bash
echo + :
expr 1 + 5
echo / :
expr 1 / 2
echo \* :
expr 5 \* 6
echo % :
expr 6 % 4
echo 使用 echo '$[]'
echo $[5*6]
使用 echo $[表达式] 也可以达到类似的效果,使用 epre ,值得注意的是 *
是特殊符号,需要转义
let
我们可以使用let 可以修改 变量的本身 并且不显示结果,例如 如果我们这样使用
如果使用let ,如下:
当然让我们也可使用 let a++
来自增变量 -- += -= ...
等C语言出现过的一元赋值运算符都是一个道理。
很可惜的是 shell脚本无法直接使用小数计算
使用bc 完成小数以及复杂的运算
我们可以使用bc命令以及 echo管道配合|
来计算小数
例如:
#! /bin/bash
echo "scale=3;10/3" | bc
2.6流程控制
shell条件表达式
一门编程语言常具这几种条件控制语句:
- if
- case
- for
- while
条件式我们需要 使用 [ ]
包括起来,注意表达式与 []
两侧有空格隔离,并且条件运算符之间也需要空格隔离 ,这是为了防止识别成一个变量,关系运算符有 (列举常用的)
- = 相等 ,常用字符类数据对比
- != 不相等 ,常用字符类数据对比
- -z 判断字符串长度是否为0
- -eq 等于,这个以及下面的常用于数值类型数据对比
- -ne 不等于
- -gt 大于
- -ge 大于等于
- -lt 小于
- -le 小于等于
- ! 取非
shell中还有争对文件和目录中进行判断的
- -e 判断文件或者目录是否存在
- -f 判断 是否为文件
- -d判断是否为目录
- -r 判断是否可读(root用户 无效)
- -w 判断是否可写(root用户 无效)
- -x 判断是否可执行
条件式不仅仅有关系运算符,还有逻辑运算符 && ||
含义无需多少了, ,和C语言一样 这里也存在逻辑短路的特性!使用逻辑短路可以完成一些条件判断
[ $USER != root ] || echo root
如果我们要判断一个变量是否为空 我们需要这样操作: [ -Z "$变量" ]
, -Z
判断字串长度是否为0
当然我们可以采用这样的书写方式 [[ -z $g ]] && echo 空
, 规则如下: [ -z "$g" ] 单对中括号变量必须要加双引号
[[ -z $g ]] 双对括号,变量不用加双引号.
当然还有存在一个符号 ;
这里提一下 多个指令可以;
隔开,;
可以看作没有逻辑功能的 运算符,可以利用它将多个命令写在一行内。
条件语句
if 单分支语句
虽然逻辑短路可以实现条件判断,但是可读性非常差,所以 条件语句我们一般都会使用 if语句,如果你是压缩代码狂,那么逻辑短路就非常适合你。if语法如下
# 语法一
if 条件表达式 ; then
命令序列
fi//表示if语句块结束
#语法 2
if 条件表达式
then
命令序列
fi//表示if语句块结束
#例如
read -p "请输入用户名" u
if [ -z $u ] ; then
echo "请输入用户名"
#...处理
exit
fi
由于 shell没有像C语言用{}
来标记语句块,shell的做法是 采用 fi
倒序的if来标记if语句的结束 。语法一和语法二 的区别不大,主要是是否使用 ;
来将命令放在一行
if多分支语句
语法如下:
# 双分支
if 条件语句 ; then
#命令序列
else
#命令序列
if
# 多分支
if 条件语句 ; then
#命令序列
elif 条件语句 ; then
#命令序列
elif 条件语句 ; then
#命令语句
#...
else
#命令序列
fi
case语句
这个case语法和Pascal有点类似
case 值 in
模式1)
命令序列
;;
模式2)
命令序列
;;
*)
默认情况
;;
esac
case 工作方式如上所示,取值后面必须为单词 in,每一模式必须以右括号结束。取值可以为变量或常数,匹配发现取值符合某一模式后,其间所有命令开始执行直至 ;;。
取值将检测匹配的每一个模式。一旦模式匹配,则执行完匹配模式相应命令后不再继续其他模式。如果无一匹配模式,使用星号 * 捕获该值,再执行后面的命令。
循环
和C语言一样,我们可以使用 break,continue 来进行循环控制,语法一致!
for 语句语法
for 变量名 in 值列表
do
命令序列
done
#例如
for i in {1..10}
do
echo &i
dono
for i in a b c
do
#这样也会循环三次
echo abc
dono
#如果你想使用 变量来控制 循环测试 请使用 seq命令
for i in `seq 10`
do
# 条件语句
done
seq命令可以输出连续的数字,或者输出固定间隔的数字,或者输出指定格式的数字。
while 语句
# 条件语句为真 循环
while 条件语句
do
# 任务序列
done
# 另外一种形式 永远执行命令序列,无限循环
while :
do
#命令序列
done
until 循环
# 条件为假执行
until 条件语句
do
#命令序列
done
3.shell 函数
基础语法
我们可以使用 alias
来给一些命令起别名,比如 我们.bashrc
中就定义着许多变量的别名,我们当然也可以手动更改。
shell中的函数,我感觉理解成加强版的alias
不错,函数是为了给一堆命令序列起别名,使用函数和使用命令的语法是一致的。例如:
#! /bin/bash
fun()
{
echo 函数的使用和命令一样
for i in $*
do
echo $i
done
}
fun c1 c2 c3
由于shell是逐行执行的,我们需要把函数的 定义放在 执行的前面。
#! /bin/bash
fun 1 2 3
function fun()
{
echo 函数的使用和命令一样
for i in $*
do
echo $i
done
}
fun c1 c2 c3
fun 1 2 3
会执行错误!
当然shell函数完整声明方式为:
function funname ()
{
#命令序列
#返回值 必须是整数 0-255
return int;
}
- 可以带function fun() 定义,也可以直接fun() 定义,不带任何参数。
- 参数返回,可以显示加:return 返回,如果不加,将以最后一条命令运行结果,作为返回值。 return后跟数值n(0-255)
函数的传参和取返回值
函数是为了给一堆命令序列起别名,使用函数和使用命令的语法是一致的,所以传参使用 位置变量即可 ,取返回值使用 $?
取返回值。这里就说明了 返回值为啥是 0-255的原因了
练习 -- 检查当前网段有多少主机和主机ip
#! /bin/bash
myping()
{
ping -c1 -W1 $1 &> /dev/null
[ $? -eq 0 ] && echo "$1 is open"
}
# 获取主机ip
a=` ip addr | grep 'state UP' -A2 | tail -n1 | awk '{print $2}' | cut -f1 -d '/' `
echo $a
b=`echo ${a%.*}`
echo "测试网段为 ${b}.x"
#ping
for i in `seq 254`
do
myping ${b}.${i} &
done
wait
wait 命令的作用是等待所有的 后台程序结束后 才结束 wait配合 & 后台执行
相当于 C语言的fork语句
我们可以写一个fork炸弹 :
#! /bin/bash
fun()
{
fun | fun &
}
fun
shell 字串处理
使用shell的过程中,我们常常需要对串进行拼接获取是截取 ,我们在这里讨论shell如何处理字串
字串的截取
语法为 : ${变量名:起始位置:长度}
,字串的位置 从0开始计数
从0开始截取可以省略0.
字串替换
语法为:${变量名/old/new}
这里默认替换的位置是首次出现的位置,也就是只会替换一次
使用 ${变量名//old/new}
就可以将所有的old替换成 new
字串删除
准备工作:首先使用 给str 赋值
str=`head -1 /etc/password`
root:x:0:0:root:/root:/usr/bin/zsh
这里我们会使用两种符号:
-
#
从左往右处理 -
%
从右手往左处理 -
最短匹配删除(从左到右) 格式:
${变量名#*关键词}
, 删除从左侧第1个字符到最近的关键词 ,*
是通配符 ,匹配任意字串root:x:0:0:root:/root:/usr/bin/zsh ╭─root@localhost /etc ╰─# echo ${str#*root} :x:0:0:root:/root:/usr/bin/zsh
我们发现这里匹配到第一个root删除了,我们如果想匹配到第三个root呢,再去删除前面的字符呢?
-
最长匹配删除(从左到右) 格式:
${变量名##*关键词}
╭─root@localhost /etc ╰─# echo ${str##*root} :/usr/bin/zsh
那么反过来 ${变量名%%*关键词}
和 ${变量名%*关键词}
的左右就不必多言了 %是从由右手往左匹配