一、先来说下解决的问题
我要写一个给Apk文件进行V3签名的Shell脚本来实现自动化,避免每次手动输入大量命令的问题。实现过程中主要的两个点,第一个是部分命令执行后需要输入密码,如何实现自动化;第二个是Apk文件的地址需要以参数形式传入,Shell脚本中如何传递命令行参数。下面围绕问题开始展开。
二、前置知识准备
2.1 Linux Shell脚本基础知识:(可以自己再查查)
https://www.runoob.com/linux/linux-shell.html
Shell 编程跟 JavaScript、php 编程一样,只要有一个能编写代码的文本编辑器和一个能解释执行的脚本解释器就可以了。Linux 的 Shell 种类众多,常见的有:
- Bourne Shell(/usr/bin/sh或/bin/sh)
- Bourne Again Shell(/bin/bash)
- C Shell(/usr/bin/csh)
- K Shell(/usr/bin/ksh)
- Shell for Root(/sbin/sh)
- ……
我使用的是 Bash,也就是 Bourne Again Shell,由于易用和免费,Bash 在日常工作中被广泛使用。同时,Bash 也是大多数Linux 系统默认的 Shell。在一般情况下,人们并不区分 Bourne Shell 和 Bourne Again Shell,所以,像 #!/bin/sh,它同样也可以改为 #!/bin/bash。#! 告诉系统其后路径所指定的程序即是解释此脚本文件的 Shell 程序。
Vim编辑器使用方法可以参考我这篇博客:使用Vim创建和编辑文本文件
2.2 提供一个连接远程Linux服务器的工具:
主流:Xshell、SecureCRT
非主流:MobaXterm(前两个主流软件要么收费,要么得破解,太麻烦,我用得这个)
https://mobaxterm.mobatek.net/https://mobaxterm.mobatek.net/
2.3 Android SDK Tools下的 apksigner 工具的使用方法
三、实现Shell脚本自动交互
场景类似于我们平时在控制台输入指令如:sudo、ssh、ftp或者修改admin权限的文件时候都会要求输入password,那么如何让脚本在执行完上面的命令后自动输入password呢?下面总结三种实现方法:
3.1 重定向
用重定向方法实现交互的前提:是指令需要有参数来指定密码输入方式,如ftp就有-i参数来指定使用标准输入来输入密码。
shell用重定向作为标准输入的用法是:cmd<<delimiter ,shell 会将分界符delimiter之后直到下一个同样的分界符之前的内容作为输入。实现ftp自动登录并运行ls指令的用法如下:其中zjk为用户名,zjk123为密码 。
ftp -i -n 192.168.21.46 <<EOF
user zjk zjk123
ls
EOF
3.2 管道
跟重定向一样,指令同样要有参数来指定密码输入方式,如sudo的-S参数,passwd的-stdin参数,所以实现sudo自动输入密码的脚本如下:其中zjk123为密码
echo 'zjk123' | sudo -S cp file1 /etc/hosts
实现自动修改密码的脚本写法如下:echo 'password' | passwd -stdin username
3.3 expect
上面介绍的两种方法前提条件是指令有参数来设定密码输入方式,像ssh指令就没有这样的参数,第三种交互方式就派上用场了。这也是本次需求实现中我使用的方法,因为更通用一些。
expect就是用来做交互用的,基本任何交互登录的场合都能使用,但是需要安装expect包。
关于expect的详细使用方法,可以参考我下面两篇博文:
Linux Shell-通过expect工具实现脚本的自动交互(一)
Linux Shell-通过expect工具实现脚本的自动交互(二)
四、Shell脚本中传递命令行参数
4.1 采用$0,$1,$2..等方式获取脚本命令行传入的参数
值得注意的是,$0获取到的是脚本路径以及脚本名,后面按顺序获取参数,当参数超过10个时(包括10个),需要使用${10},${11}....才能获取到参数。
4.2 通过getopts 方法传递参数
语法格式:getopts [option[:]] [DESCPRITION] VARIABLE
option:表示为某个脚本可以使用的选项
":":如果某个选项(option)后面出现了冒号(":"),则表示这个选项后面可以接参数(即一段描述信息DESCPRITION)
VARIABLE:表示将某个选项保存在变量VARIABLE中
关于这部分的详细使用方法,可以参考下面的博客:
五、实现脚本
如下:
#!/bin/bash
apksigner_path="/Users/jlpay/Library/Android/sdk/build-tools/30.0.3/apksigner"
origin_apk=$1
v2_sign_apk="v2_sign.apk"
v2_v3_sign_apk="v2_v3_sign.apk"
old_sign_file="AAA"
new_sign_file="aaa"
password="aa123456"
# 下面三行可以在当前目录下创建文件夹
# work_dir=$PWD
# echo $work_dir
# mkdir $work_dir/v3_file
chmod +x ${apksigner_path}
${apksigner_path} sign --ks $old_sign_file --ks-key-alias jlian --ks-pass pass:$password --key-pass pass:$password --out $v2_sign_apk --v3-signing-enabled true $origin_apk
# expect嵌入
/usr/bin/expect << EOF
set timeout 30
spawn ${apksigner_path} rotate --out lineage --old-signer --ks $old_sign_file --new-signer --ks $new_sign_file
expect {
"*password*" { send "$password\r"; exp_continue }
"*password*" { send \"$password\r\" }
eof
}
EOF
# expect嵌入
/usr/bin/expect << EOF
set timeout 30
spawn ${apksigner_path} sign --ks $old_sign_file --next-signer --ks $new_sign_file --lineage lineage --out $v2_v3_sign_apk --in $origin_apk
expect {
"*password*" { send "$password\r"; exp_continue }
"*password*" { send \"$password\r\" }
eof
}
EOF
中间遇到个问题,就是上面expect中的 eof 如果放到 expect{ } 外面,写成expect eof 就会报 expect: spawn id exp6 not open while executing "expect eof" 的报错,看这篇文章意思是 expect eof 导致直接停止了 spawn进程,进而使得 expect 无法捕获到 想要捕获的 内容。
六、补充的小知识点
6.1 shell变量与字符串间的拼接方法
对于变量或者字符串的连接,shell提供了相当简单的做法,比string的连接还要直接。直接放到一起或用双引号即可。
例如$a, $b,有
c=$a$b
c=$a"xyz"$b
c=$a$b.txt
c=$work_dir/v3_sign_dir
6.2 获取Shell脚本或者传入文件参数的当前工作目录的方法
如下:
# 获取shell脚步的当前文件目录
workdir=$(cd $(dirname $0); pwd)
# 获取输入参数中的文件目录
work_dir=$(cd $(dirname $1);pwd)
步骤1
dirname $0,取得当前执行的脚本文件的父目录
步骤2
cd到父目录,即进入当前工作目录
步骤3
pwd显示当前工作目录
参考的一些文章: