什么是 Shell?
Shell 这里指的是 Uinx 中 Shell 解释器,而不是某跨国公司品牌的 shell 名称,这才使得人搞混了。在常用的 Linux 系统中 Shell 是一种介于系统内核和用户之间的命令解释器程序,用户可以通过输入命令通过 Shell 解析器执行命令从而达到操作计算机内核作用。
如果经常使用类 UNIX 系统的话,如果掌握 Shell Script 会大大提高一下工作效率,常用的系统资源管理自动化操作,都可以使用编写 Shell 脚本来完成,而且好处是大多数操作系统默认都有自带的解释器。
每个操作系统的默认自带的解释器都有很多种,可以通过下面这个命令来查看支持的解释器列表:
$: cat /etc/shells
# List of acceptable shells for chpass(1).
# Ftpd will not allow users to connect who are not using
# one of these shells.
/bin/bash
/bin/csh
/bin/ksh
/bin/sh
/bin/tcsh
/bin/zsh #我默认使用的这个,功能强大于普通bash
/usr/local/bin/zsh
最常用的解释器也就是/bin/bash
,下面可以编写一个简单的脚本,内容如下:
#!/bin/bash
echo "Hello Shell Script!" # 执行会打印这句话
变量的使用
在 Shell 脚本中也有变量的概念,也内置一些变量方便编写脚本的时候使用,例如$HOME
、$USER
、$PWD
、$SHELL
... 在 Shell 脚本中的变量也简单,没有类型系统所有数据都字符串类型,无法参入运算,但是有区分只读静态变量,如下:
#!/bin/bash
variable = "这是一个普通的变量"
echo $variable
readonly immutable = 1024 # 只读不可变 zsh: read-only variable: immutable
unset variable # 取消设置的值
echo $variable
如果需要把当前脚本文件里面的变量导出给其他脚本使用的话,那就需要将变量导出为全局变量,使用关键字 export
:
#!/bin/bash
GLOBAL = "全局变量"
export GLOBAL
这样在其他的 Shell 环境变量就可以访问到这个变量了。
特殊变量 $n
这里的 n
表示数字,从 0 开始的可以是很多个,但是 1-9
可以使用 $n
,但是超过之后访问其值必须加上大括号 ${10}
这样才能访问到,一般这使用与脚本文件外部传入参数获取使用。
另外一个如果需要得到外部传入参数个数需要使用 $#
它返回就是参数个数。如果需要获取全部输入的参数直接使用 $@
,它就获取到所有传入的参数。
#!/bin/bash
echo "args length: $#"
echo "$0 $1 $2"
args=("$@")
echo "first : ${args[0]}"
echo "second : ${args[1]}"
output:
$: sh shell-args.sh 1 2 3
args length: 3
shell-args.sh 1 2
first : 1
second : 2
还有一个比较特殊的 $?
,它可以检查上次执行命令是否出错,它会检查上次命令执行返回结果,如果结果为 0
则表示正常运行退出,反之则认为是程序非正常退出,当然退出错误码可以自定义,例如修改一个不可变变量:
#!/bin/bash
readonly immutable=1024
echo $? #0
immutable=2048
echo $? #1
运算符
上面介绍 Shell 脚本中变量的使用,但是变量的缺点就是没有类型系统,所有的变量都是字符串如果需要进行计算那么就不可能的。为了解决数值运算的问题,在 Shell 中运算使用的关键字 expr
命令,例如 expr n + x
加法计算。
expr
命令是一个手工命令行计数器,用于在 UNIX/LINUX
下求表达式变量的值,一般用于整数值也可用于字符串,运算的时候用空格隔开每个项,特殊字符需要转义的使用 \
,对包含空格和其他特殊字符的字符串要用引号括起来。
#!/bin/bash
expr 10 + 10 #加法计算
expr 20 - 10 #减法计算
expr 10 \* 10 #乘法计算
expr 50 / 5 #除法计算
expr 8 % 7 #取余计算
特殊的字符串运算:
#!/bin/bash
expr length "text content" # 计算长度 12
expr substr "this is text" # 截取 is is
expr index "hello" e #第一次出现的位置2
当然如果想纯做数值计算,使用 $[运算表达式]
如下:
#!/bin/bash
echo "10 + 10 = $[10 + 10]" # 10 + 10 = 20
expr `expr 2 + 3` \* 4 # 20
流程控制
shell
作为脚本语言同样也有条件语句,只要支持条件流程控制才能算是一门脚本语言,shell
脚本支持条件判断、流程控制、循环这些下面是一些例子。
IF 语句
条件判断表达式,条件语句需要放置在 [ 条件 ]
里面,条件相关的关键字有:
-ge
是否大于或等于-le
是否小于或等于-lt
小于-gt
小于
检测是否有某种权限关键字:
-w
是否有写权限-r
是否有读权限-e
文件夹或者文件是否存在
#!/bin/bash
x=$1
y=$2
if [ $x -le $y ]; then
echo "x less than y"
elif [ $x -gt $y ]; then
echo "y grater than x"
else
echo "other result"
fi
CASE 语句
熟悉其他编程语言的应该都使用过 Case
语句来匹配处理多个条件,在 shell
中也存在 case
语句,如下:
#!/bin/bash
language=$1
case $language in
Java)
echo "Java 语言";;
Go)
echo "Go 语言";;
Rust)
echo "Rust 语言";;
esac
FOR 循环
for
循环可以根据某种条件让程序重复执行块逻辑,下面是普通循环的例子:
#!/bin/bash
sum=0
for ((i=0;i<=100;i++))
do
sum=$[$sum+$i]
done
echo $sum
for loop in 1 2 3 4 5;do
echo "number: $loop"
done
WHILE循环
while
循环也是一种循环的,在循环里面设置一个条件,直到该条件满足为止,如下:
#!/bin/bash
counter=0
while [ $counter -le 5 ]; do
counter=`expr $counter + 1`
echo $counter
done
函数编程
在shell
脚本里面也有函数的概念,函数可以把一些重复执行逻辑块单独拿出来,通过传递参数到达不同结果,在shell
脚本里面有内置函数,也可以自定义函数,本节将介绍内置函数和自定义函数的使用。
有时候编写的脚本需要接受外部的输入,这时我们就要失业内置的read
函数了,read
函数可以从控制台读入值,并且还能设置等待用户输入的超时时间,下面就是一个例子:
#!/bin/bash
read -t 8 -p "input your name:" NAME
echo "Hello $NAME~"
basename
主要用于显示文件路径名不包括目录部分后的显示文件名,如果指定了后缀参数suffix
那么返回就是文件名,如下:
basename /Users/ding/jmeter.log .log
dirname
从给定的文件路径,去掉文件部分返回文件路径部分,文件路径如下:
dirname /Users/ding/jmeter.log
/Users/ding
其实内置函数也就是一个一个内置命令,当然除了内置的一些函数以外,还可以在脚本里面自定义函数,但是函数必须定义在调用者前面,因为脚本是顺序执行的,如下是一个标准的函数定义:
#!/bin/bash
function sum() {
read -t 5 -p "请输入加数:" x
read -t 5 -p "请输入加数:" y
return $[$x + $y]
}
sum
echo "和是:$?"
需要注意的是函数返回值,如果没有使用return
关键字,那么返回值则使用最后一行作为返回值。
CUT命令
cut
是内置的命令,主要用于在文件中负责剪切数据用的,cut
可以在文件的每一行剪切字节、字符和字段并将这些字节、字符和字段输出。
例如下面通过ifconfig
命令的内容,要把网卡的本地ip
截取出来,内容如下:
$: ifconfig
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384
options=1203<RXCSUM,TXCSUM,TXSTATUS,SW_TIMESTAMP>
inet 127.0.0.1 netmask 0xff000000
inet6 ::1 prefixlen 128
inet6 fe80::1%lo0 prefixlen 64 scopeid 0x1
nd6 options=201<PERFORMNUD,DAD>
gif0: flags=8010<POINTOPOINT,MULTICAST> mtu 1280
stf0: flags=0<> mtu 1280
XHC20: flags=0<> mtu 0
XHC0: flags=0<> mtu 0
en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
options=400<CHANNEL_IO>
ether 8c:85:90:c4:67:da
inet6 fe80::ca2:121e:899c:3504%en0 prefixlen 64 secured scopeid 0x6
inet 192.168.31.221 netmask 0xffffff00 broadcast 192.168.31.255
nd6 options=201<PERFORMNUD,DAD>
media: autoselect
status: active
en1: flags=8963<UP,BROADCAST,SMART,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1500
options=460<TSO4,TSO6,CHANNEL_IO>
ether 82:87:9a:86:cc:01
media: autoselect <full-duplex>
status: inactive
en2: flags=8963<UP,BROADCAST,SMART,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1500
options=460<TSO4,TSO6,CHANNEL_IO>
ether 82:87:9a:86:cc:00
media: autoselect <full-duplex>
status: inactive
bridge0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
options=63<RXCSUM,TXCSUM,TSO4,TSO6>
ether 82:87:9a:86:cc:01
Configuration:
id 0:0:0:0:0:0 priority 0 hellotime 0 fwddelay 0
maxage 0 holdcnt 0 proto stp maxaddr 100 timeout 1200
root id 0:0:0:0:0:0 priority 0 ifcost 0 port 0
ipfilter disabled flags 0x0
member: en1 flags=3<LEARNING,DISCOVER>
ifmaxaddr 0 port 7 priority 0 path cost 0
member: en2 flags=3<LEARNING,DISCOVER>
ifmaxaddr 0 port 8 priority 0 path cost 0
nd6 options=201<PERFORMNUD,DAD>
media: <unknown type>
status: inactive
现在需要截取出来eno
的ip
地址:
ifconfig en0 | grep "inet " | cut -d " " -f 2
192.168.31.221
SED流式编辑器
sed
是内置的一款流式编辑器,它可以把内容读取到内存里面缓冲区里,然后在对每行内容进行操作,然后把结果输出到标准输出,不会影响到原来的文件,如果需要转存可以使用重定向输出,常用的一些操作有:
public class HelloWorld {
public static void main(String[] args) {
System.out.println("HelloWorld!");
}
}
现在有一块Java
源代码文件,需要使用sed
对该文件的一些操作,如下:
#替换HelloWorld为中文
$: sed -e 's/HelloWorld!/您好,世界!/' HelloWorld.java
public class HelloWorld {
public static void main(String[] args) {
System.out.println("您好,世界!");
}
}
好现在把源代码添加多一点方便,后面测试,现在内容如下:
[root@aliyun-centos7 developer]# cat /root/helloworld.java
public class helloworld {
public static void main(String[] agrs) {
// 让程序输出双引号中的字符串
System.out.println("Hello World!");
System.out.println("Hello World!");
System.out.println("Hello World!");
System.out.println("Hello World!");
System.out.println("Hello World!");
System.out.println("Hello World!");
System.out.println("Hello World!");
}
}
下面是删除第三行,并且在第二行添加新的内容:
$: sed -e "3d" -e '2a\\t\t\// 修改了第3行注释' /root/helloworld.java
public class helloworld {
public static void main(String[] agrs) {
// 修改了第3行注释
System.out.println("Hello World!");
}
}
sed '5,9d' /root/helloworld.java
删除文件的第九行然后把内容输出到标准输出上sed '4a\\t\tSystem.out.println("您好!世界!");' /root/helloworld.java
在某一行向后插入内容nl /root/helloworld.java | sed '5,9d'
列出文件行号,并且从第5行删到第9行!
[root@aliyun-centos7 developer]# nl /root/helloworld.java | sed '5,9d'
1 public class helloworld {
2 public static void main(String[] agrs) {
3 // 让程序输出双引号中的字符串
4 System.out.println("Hello World!");
10 System.out.println("Hello World!");
11 System.out.println("Hello World!");
12 }
13 }
- 在第五行之前插入内容
i前 a后
[root@aliyun-centos7 ~]# nl helloworld.java | sed '5i\\t\t\tSystem.out.println("这是通过sed命令插入的内容!!");' | nl
1 1 public class helloworld {
2 2 public static void main(String[] agrs) {
3 3 // 让程序输出双引号中的字符串
4 4 System.out.println("Hello World!");
5 System.out.println("这是通过sed命令插入的内容!!");
6 5 System.out.println("Hello World!");
7 6 System.out.println("Hello World!");
8 7 System.out.println("Hello World!");
9 8 System.out.println("Hello World!");
10 9 System.out.println("Hello World!");
11 10 System.out.println("Hello World!");
12 11 System.out.println("Hello World!");
13 12 }
14 13 }
- 修改内容
c
[root@aliyun-centos7 ~]# nl helloworld.java | sed '5c \\t\t\tSystem.out.println("通过sed命令修改的内容!!");' | nl
1 1 public class helloworld {
2 2 public static void main(String[] agrs) {
3 3 // 让程序输出双引号中的字符串
4 4 System.out.println("Hello World!");
5 System.out.println("通过sed命令修改的内容!!");
6 6 System.out.println("Hello World!");
7 7 System.out.println("Hello World!");
8 8 System.out.println("Hello World!");
9 9 System.out.println("Hello World!");
10 10 System.out.println("Hello World!");
11 11 System.out.println("Hello World!");
12 12 }
13 13 }
AWK 分析工具
一款强大的文本分析工具,把文本逐行读入,以空格为默认的方式分隔将其为切片,切开的部分进行分析处理,一般要配合正则表达式使用。
#获取Java版本号
$: java -version 2>&1 | sed '1!d' | sed -e 's/"//g' | awk '{print $3}'
大部分内置的函数和命令在使用的时候可以去查文档,没有必要记住那么多,不过了解 shell
脚本编写是有必要的!