oynix

于无声处听惊雷,于无色处见繁花

shell常用语法

平常经常写一些shell脚本,来执行一些自动化任务,比如打包、连接等,记录下常用到的语法和命令。

1. 格式和运行

格式是指开头第一行

1
#! /bin/bash

其中的#!叫shebang,也叫hashbang,后面跟着的这个脚本解释器的绝对路径,我的电脑上用的是bash。运行的时候直接使用sh后面跟上脚本的名字即可,如果给脚本文件加上可执行的权限,用./也可执行,如果不写第一行的话,你会发现也可以执行,如果不光是给自己用,建议还是要写上的。

2. 获取参数

有时一些参数是不固定的,需要每次运行的时候传入。方式有两种,一种是直接在后面写参数的值,如

1
sh command.sh arg1 arg2 arg3

这种方式在读取时,是凭借参数的位置顺序读取的,$0为command.sh,$1为参数一arg1,$2为参数二arg2,依次类推。这种方式方便传入和读取,但是受到位置的限制,有时可能只需要传入参数二,而参数一使用缺省值便可,这时使用-argName的方式会更方便,按需指定并传入,如

1
sh command.sh -a argA -b argB -c argC

读取时,要这样,getopts,即get options,获取所有的可选参数,通过循环读取每个参数的值

1
2
3
4
5
6
7
8
while getopts "a:b:c:" opt
do
case $opt in
a ) argA=$OPTARG ;;
b ) argB=$OPTARG ;;
c ) argC=$OPTARG ;;
esac
done

3. 条件判断

判断是脚本中最常见的命令语句了,基本语法为

1
2
3
4
5
6
7
if [ command ]; then
# do something
elif [ command ]; then
# do something
else
# do something
fi

其中的command有很多,按类型可以分为:判断文件/目录、判断字符串和判断数值大小,举几个常用的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 文件/目录
[ -e FILE ] 文件存在
[ -s FILE ] 文件存在且长度不为0
[ -f FILE ] 普通文件存在 -b:二进制文件 -c:字符文件 -r:可读文件 -x:可执行文件
[ -d DIR ] 目录存在
[ FILE1 -nt FILE2 ] newer than,也就是1比2更新 同理,-ot:older than

# 字符串
[ -z STRING ] zero长度为0
[ -n STRING ] not zero长度不为0,-n也可以省略,即等同于 [ STRING ]
[ S1 = S2 ] 相同,同理 !=为不同

# 数值
[ INT1 -eq INT2 ] equals,相等,同理 -ne:not equals
[ INT1 -gt INT2 ] greater than,大于,也可以写成大于号: >,同理 -lt:less than
[ INT1 -ge INT2 ] greater or equals, 即为>=,同理 -le:<= 小于等于

# 多个条件
[ (条件1) -a (条件2) ] all,与,也可以写成 &&
[ () -o () ] or,或,也可以写成 ||
[ ! () ] not,非

4. 服务器

ssh连接服务器,如果已经把自己的公钥id_rsa.pub添加到了服务器的authorized_keys里,那么可以直接连接,就可以了

1
ssh username@server-ip

如果没有添加,则需要指定服务器的私钥的pem文件作为参数

1
ssh -i identification.pem username@server-ip

原理很简单,一是服务器信任了自己的公钥,二是拿着服务器的私钥的pem,pem的获取方式为

1
2
3
4
# 服务器生成公钥匙私钥对,这会得到一个私钥private,和公钥private.pub
ssh-keygen
# 转换私钥,传入私钥,得到pem格式,凡是拿着这个pem的客户端都可以连接
openssl rsa -in private -outform pem > private.pem

当然,光连上服务器不是目的,这些如果写到shell脚本里会中断执行,而我的目的是在服务器上执行命令,连到服务器只是前提,若执行命令,如下

1
2
3
ssh username@server-ip "[command]"
# e.g。
ssh user@server-ip "cd documents && ls -la"

有时还会需要在本地和远程之间传递文件命令,从本地到远程如下(远程到本地就反过来),注意server-ip后面的冒号,

1
2
3
4
5
scp [options] /local/source/file/path user@server-ip:/path/to/destination
-C 要大写,压缩
-P 要大写,端口号,默认22
-r recursive递归
-p 保留访问修改时间

服务器的IP地址经常会变,每次通过ssh连接一个不存在于.ssh/known_hosts中的ip时,都会弹出一个警告,问是否要将这个正在连接的主机添加到已知设备中,中断自动化脚本的执行。

在这里,都要连接登陆了,所以肯定是要加的,可以通过指定一个参数,让其自动添加,而不中断自动化流程

1
ssh -o StrictHostKeyChecking=no -i identificaiton.pem username@host

5. 编译shell脚本

有时写完一个shell脚本,需要给别人使用,但是又不想让别人看到里面的代码,可能因为里面有重要数据,也可能没有为什么,就是不想,这时可以把shell脚本编译成可执行文件,这样一来,既可以执行,但又无法查看了。一共有两种方式,一是系统自带的gzexe,二是使用shc命令,

1
gzexe command.sh

这会在目录下生成两个文件,一个是command.sh,另个是command.sh~,前者是压缩之后的,带~的是原文件,打开压缩之后的文件,可以发现里面有不少内容还是可以看懂的,尽管多数都不沾边了。

1
shc -f command.sh

这也会在目录下生成两个文件,一个是command.sh.x,这是可执行文件,另个是command.sh.x.c,这是个c文件,打开可执行文件后,会发现完全看不懂,都是乱码,推测可能先生成了c文件,然后再将c编译成二进制可执行文件。这是shc的github地址

6. 脚本里调用其他脚本

当有多个shell文件,其中相互调用的时候,要用source命令

1
2
3
4
#! /bin/bash
echo 'first shell scripts'

source other.sh

虽然写在多个shell文件里,但是因为运行在同一个shell会话中,所以变量是可以共用的,也就是first里声明的变量,在other里可以读取,我一般用这种方式来处理脚本的参数,当一个脚本需要多个参数时,就把这些参数都单独写在一个shell里,然后再最后通过source调用真正的执行文件就好。

7. $相关的变量说明

变量 含义
$0 脚本的名字,即文件名
$n(n ≥ 1) 参数名字
$# 传给脚本的参数个数
$* 传来的所有参数
$@ 传来的所有参数
$? 上个命令的退出值
$$ shell进程ID

其中,$@$*,没有引号包围时,二者完全一样,当有双引号包围时,$@依然无变化,而$*则是把所有参数合并成了一个变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sh test.sh p1 p2 p3

# 读取参数代码
for p in []
do
echo $p
done

$@ $* "$@" 输出的都是
p1
p2
p3

"$*" 输出的是 p1 p2 p3
------------- (完) -------------
  • 本文作者: oynix
  • 本文链接: https://oynix.com/2022/04/740e031ce254/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

欢迎关注我的其它发布渠道