本文事实上是在介绍安卓系统下的shell以及一些有用的工具,以期“抛砖引玉”。对于那些没有兴趣或者耐心读注解的读者,以井号“#”打头的行可以跳过不看。
1 引言
1.1 本文的目标读者
对“安卓终端模拟器怎么用”这个问题感兴趣的人。虽然说我设想的读者是“有了一点点Shell基础的小白”,但是高手读了也不会感到乏味吧。1.2 终端模拟器是个啥?
要回答终端模拟器是什么,首先要回答“终端”是什么。在那个遥远的年代,计算机的数量十分有限,而且几乎都是大型机。如果想要多人共用一台计算机怎么办?就是使用“终端”(Terminal)。Terminal在Oxford Learner's Dictionary中有这样一个解释:(computing) a piece of equipment, usually consisting of a keyboard and a screen that joins the user to a central computer system
大概意思是Terminal是用来连接到*计算机的一套包含键盘和显示器的设备。据笔者所知,终端使用串行通信协议和*计算机进行通信。没错,就是本科数电和单片机讲的那个串行通信。显然,终端可以是一个复杂的计算机系统,但更多情况下终端都是较为简易的计算机系统(甚至单片机系统)。如果你手头有树莓派(Raspberry Pi)的话,可以把树莓派当作“*计算机”,把你的PC当作“终端”,利用树莓派的串行通信接口和USB转TTL的器件(比如PL2303或者CH340)体验一回真正的“终端”。终端和串行通信具体细节已经远远超出了本文的范围,这里不再描述。
所谓终端模拟器,模拟的并不是终端和*计算机通信这件事本身,而是终端的用户界面。简单地说就是“一个黑框框里面有个光标你可以敲一些命令完成你想要做的事情”。如果你用过DOS或者命令提示符或者Linux的“虚拟终端”,你肯定已经知道是什么了。今后本文在不引起误解的情况下,“终端”均指“终端模拟器”这类软件。
1.3 本文需要用到的软件/程序
(1) Terminal Emulator for Android(安卓终端模拟器),包名为jackpal.androidterm(2) busybox,到各大应用市场搜busybox即可找到各种各样的busybox安装工具。有的ROM(比如我的MIUI)自带了busybox,这时就不必重新安装了。
(3) 好用的键盘,笔者使用的是Keyboard for Excel(有tab键但是没有ctrl)。你也可以使用Hacker's Keyboard这样的tab键、ctrl键都有的键盘。
1.4 在手机上使用终端模拟器的一些特点
(1) 能懒则懒——命令序列均封装成脚本或函数或别名(alias);(2) 很多在Linux中熟悉的工具需要通过调用busybox实现;
(3) 文件名包含非ASIIC字符(比如中文字符)不总是很好地被支持,尤其是调用busybox的时候。
2 编辑脚本文件的方法
2.1 使用安卓或者PC系统上的文本编辑器
大多数文件管理器都内置了文本编辑的功能,如Total Commander、ES文件管理器等。如果你特别喜欢语法高亮,可以考虑一下Quick Edit,这是一个安卓上的支持多种程序设计语言高亮的app。比如,你编辑了一个文件名为hello.sh的脚本文件,你写入了如下内容
#!/system/bin/sh # 显示“Hello Android terminal”,然后退出 echo "Hello Android terminal"
# 这个脚本的第一行指示要用什么程序运行这个脚本,也就是所谓的释伴行(Shebang)。一定要写释伴行。虽然有某些方式允许你没有释伴行也能够成功执行脚本,但是不写释伴行是一件非常不严谨的事情。关于释伴行的更多知识可以参阅*(https://en.wikipedia.org/wiki/Shebang_(Unix))
# 这个脚本的地第二行是一个注释。Shell脚本中,井号“#”之后的到所在行末尾的内容都不会被当作语句执行。
# 脚本的第三行是一条很简单的显示文本的语句。
2.2 使用busybox中的vi
关于vi以及vim的入门学习的资料请自行搜索,这里只介绍一些基本操作。vi是一种有模式文本编辑器,分为编辑模式和命令模式。在命令模式中,通过hjkl四个键移动光标,键入:w写入(保存)文件,键入:q退出vi,键入:q!不保存直接退出vi。在命令模式下按下i(在光标位置插入)、o(在下一行插入)、a(在当前光标之后插入)都可以进入编辑模式。在编辑模式中按ESC返回命令模式。由于终端模拟器提供特殊按键映射,因此可以先按下音量+,再按下e来等效ESC。
键入
busybox vi hello.sh
编辑上述的hello.sh,并使用:w保存。3. 运行脚本文件
3.1 "正常的方法"
假设你把这个hello.sh文件保存在了sd卡的根目录中,也就是说这个文件的位置是/sdcard/hello.sh中,你可以通过如下命令把它复制到终端模拟器的主目录中。cp /sdcard/hello.sh ~/
# NOTE: 自动补全
# 如果你的终端支持自动补全的话,你可以试着只打出cp /sdcard/he,然后按tab键。如果支持自动补全,此时终端的输入缓冲区插入的不是制表位,而应该是补全了整个文件路径/sdcard/hello.sh接着,赋予这个文件执行权限
cd ~ # 切回主目录
chmod 700 hello.sh # 给文件的所有者读取(r)、写入(w)和执行(x)权限
# NOTE: 安卓上的chmod和Linux的chmod的区别
# 你可能注意到了在Linux上通常采用的另一种写法
chmod u+x hello.sh
# 用不了,只能用八进制文件权限表示法。那么“700”这三个数字是什么意思呢?
# 在Linux文件权限中,文件权限的作用域有三个,分别是所有者(User)、所有者所在的组(Group)和其他人(Other)。每个作用域内可以管控的权限都是三种,即上文提到的读取(r)、写入(w)和执行(x)权限。把rwx看成一个三位二进制数(r表示最高位,w表示中间那一位,x表示最低位),那么便可以用每一个二进制位取0或者取1代表对应权限是“有”,还是“无”。# 例如,如果要求只能读取,不能写入也不能执行,那么rwx=100,换算成八进制就是4。反过来说,八进制7对应二进制111,也就是说rwx=111。亦即允许所有权限。
# 由于文件权限作用域有三个,因此用于文件权限控制的二进制数实际上应该是rwxrwxrwx这样一个9位二进制数。高三位表示所有者权限(User),中间三位表示所在组(Group)的权限,低三位表示其他人的权限(Others)。也就是说,上面的700表示的是rwxrwxrwx=111000000。
# 对于一个脚本文件的执行,至少需要读(r)和执行(x)这两个权限。如果你的脚本想要被以root权限执行(如果你已经root了的话),那么这个文件的权限应该变成705或者755。
# 需要注意的是,作为一种安全机制,安卓系统为每一个安装的app都创建一个独立的用户和组。因此你在使用终端模拟器执行某个脚本的时候,实际上对应的用户是“终端模拟器”这个用户。
# NOTE: FAT文件系统不能保存文件权限!
# 这也就是为什么要复制到你的终端模拟器的主目录下的原因。SD卡通常是格式化成FAT格式的,这种格式不能保存文件权限标志位。如果你硬要在SD卡上chmod的话,会得到如下图的错误提示。
执行这个脚本
./hello.sh
成功的话应该能看到类似下图的输出。
3.2 简单粗暴的方法
这里的方法不需要复制到主目录,可以让脚本留在SD卡上,也不需要chmod(1) 使用sh
直接执行
cd /sdcard/
sh hello.sh
(2) 使用source和.(一个半角句号)
cd /sdcard/
source hello.sh
# . hello.sh
# NOTE: 有什么区别和联系呢?
# 首先,如果你使用简单粗暴的方法,那么你的脚本中可以没有释伴行(有也没关系),因为你在把hello.sh作为sh的参数执行的时候,已经明示了脚本解释程序为sh。在使用source的时候,你的脚本解释程序即为当前的shell(一般来说也是sh)。
# 区别是,如果你的脚本中包含变量声明、函数、别名,2.1节 “正常的方法”和使用sh的方法在脚本执行完成后会,这些变量不会保存在当前shell空间中,但是source相当于把脚本直接插入当前shell空间(这一点类似于C/C++中的#include)。source完了之后,脚本中的变量声明、函数、别名会保留在当前的shell空间中
# NOTE: 简单粗暴的方法只能由人类使用
# 如果你的脚本是http服务器的cgi脚本,那么你必须采用3.1节的方法——因为这时候调用脚本的是httpd,而不是某个shell!
4 实例
4.1 告诉我这周是这学期的第几周?
我经常想不起来这学期的这一周是这学期的第几周。这可不是什么好事,要是临近考试周,就要抓紧时间复习了才行。busybox vi ~/weekofterm.sh
#!/system/bin/sh start=$(busybox date -d "2017-01-01 00:00" +%U) # 为了演示假设有个从1月1日开始的学期 now=$(date +%U) let count=now-start+1 length=19 # 我校一学期只有可怜的19周 if [ $count -gt 0 ] && [ $count -le 19 ] ; then let mod=count%10 case $mod in 1) if [ $count -ne 11 ] ; then suffix="st" else suffix="th" fi ;; 2) if [ $count -ne 12 ] ; then suffix="nd" else suffix="th" fi ;; 3) if [ $count -ne 13 ] ; then suffix="rd" else suffix="th" fi ;; *) suffix="th" ;; esac echo "Calendar: This is the ${count}${suffix} week of this term." #echo fichmod 700 ~/weekofterm.sh
执行
~/weekofterm.sh
4.2 探索所在局域网中有哪些主机在线
我的脚本中假设子网掩码是255.255.255.0
busybox vi ~/scan_online.sh
#!/system/bin/sh if [ $# -ne 1 ] ; then echo "Please supply a prefix, i.e. 192.168.1" exit 1 fi cat /dev/null > tmp for i in $(busybox seq 1 254) ; do ping -c 1 $1.$i >> tmp & done echo "Output stored in file tmp" echo "run" echo " grep -i5 '1 received' tmp" echo "to view"
chmod 700 ~/scan_online.sh
执行这个脚本
~/scan_online.sh 192.168.1运行
grep -i5 '1 received' tmp
查看输出标记有“1 received”的IP地址是在线的。
4.3 删除相册目录中总是在不断长大的.thumbnails (以及MIUI的最近任务缩略图 [需要root])
busybox vi ~/clean_thumbnails.sh#!/system/bin/sh if [ -d /sdcard/DCIM/.thumbnails/ ] ; then ( cd /sdcard/DCIM/.thumbnails/ t=$(busybox ls -1a | busybox wc -l) if [ $t -gt 2 ] ; then echo "Cleaning cache..." rm -v .* rm -v * # 如果你使用了alias rm='rm -i',你可能想要执行如下的语句 # busybox env rm -v .* # busybox env rm -v * echo # 输出一个空行 fi ) fi # 下面删除的是MIUI缓存的“近期任务”缩略图,非MIUI系统及未获取root权限的 # MIUI请注释掉或删除 if [ -d /data/system/app_screenshot ] ; then echo "Cleaning cache..." su -c "rm -v /data/system/app_screenshot/*" # 需要root权限 fi
chmod 755 ~/clean_thumbnails.sh
4.4 下载bbcnewssummary
下载这个功能可以用curl、wget和aria2实现,笔者发现curl、wget并不是所有ROM都有。为了方便起见可以使用强大的下载工具aria2。aria2的主页是https://aria2.github.io/,从那里你可以找到安卓版aria2的下载地址。
假设你已经把aria2的可执行程序aria2c下载下来,放到了/sdcard/aria2c这个位置。
cp /sdcard/aria2c ~/ # 复制到“终端模拟器”的主目录
cd ~
chmod 700 aria2c # 使其可执行
mkdir -p "/sdcard/Download/bbcnewssummary/"
busybox vi download_bbcnewsummary.sh
#!/system/bin/sh aria2c -x10 \ -d "/sdcard/Download/bbcnewssummary/" \ -o "$(date +%F_%H)hrs_bbcnewssummary.mp3" \ http://wsdownload.bbc.co.uk/worldservice/css/96mp3/latest/bbcnewssummary.mp3BBC news summary每小时更新一次,长度在5分钟以内。是非常好的练习英语听力的材料。
4.5 手机上的http服务器
这个功能实际上使用的是busybox的httpd组件。busybox httpd需要配置文件httpd.conf。配置文件可以参看下面的两个链接https://wiki.openwrt.org/doc/howto/http.httpd
https://git.busybox.net/busybox/tree/networking/httpd.c
先做一些准备工作
cd ~
mkdir www
cd www
mkdir www-data # 用于存放网页等
busybox vi httpd.conf # httpd配置文件
busybox vi www-data/index.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <title>演示</title> </head> <body> <h1>演示标题</h1> <div>这是一个演示的页面</div> </body> </html>
然后编写用于启动httpd服务器的脚本
busybox vi ~/www/android_startup.sh
#!/system/bin/sh busybox httpd -f -p 8080 -c ~/www/httpd.conf -h ~/www/www-data/ # 其中-f表示前台运行,方便使用Ctrl C关闭
chmod 700 ~/www/android_startup.sh
启动
~/www/android_startup.sh
停止直接按ctrl c(也可以先按音量减再按c)
效果如图:
当然也可以做得更美观一点,不过网站设计与编程不在本文的讨论之列。
4.6 重启到recovery [需要root]
这个很简单……只有一行
su -c "reboot recovery"
4.7 禁用、启用手机上的app [需要root]
停止Google Play服务
su -c "pm disable com.google.android.gms"
启用Google Play服务
su -c "pm enable com.google.android.gms"
3.8 查看CPU温度【可能需要root】
cat /sys/devices/virtual/thermal/thermal_zone0/temp
如果permission denied,就只能以root身份执行了
su -c "/sys/devices/virtual/thermal/thermal_zone0/temp"
5 使用初始化文件
5.1 初始化文件简介
如同bash等shell一样,终端模拟器使用的默认shell也有初始化文件。这个初始化文件在shell启动时自动插入当前shell空间。初始化文件主要用来设置PATH、别名、用户自定义函数、环境变量等。
5.2 默认的初始化文件/system/etc/mkshrc
这是我的MIUI系统的初始化文件。修改需要root权限,且系统升级后修改会丢失,因此不建议修改这个文件。5.3 Terminal Emulator for Android的初始化命令
在终端模拟器的首选项中,找到Shell栏目的初始命令
5.4 使用.shrc
一个例子.shrc如下# 设置自定义的PATH export PATH=$PATH:/data/data/jackpal.androidterm/app_HOME/bin:/system/bin # 增加一个LANG变量 export LANG=zh_CN.UTF-8 # 修改PS1变量,使得命令提示符不再只有一个“$”那么死板 export PS1='$?|[$USER@$HOSTNAME:$(busybox basename ${PWD:-?})] $ ' # 系统信息 function sysinfo() { echo Machine type: `busybox uname -m` echo Hostname: `busybox uname -n` echo OS name: `busybox uname -s` echo OS release: `busybox uname -r` echo OS version: `busybox uname -v` echo Host processor type: `busybox uname -p` } # 挂载/system分区 function mount_system { su -c "mount -o remount,rw /system" } # CPU温度 function cputemp { su -c "cat /sys/devices/virtual/thermal/thermal_zone0/temp" } # 抓取当前activity function current_activity { echo "I will grasp current window focus in 3 seconds..." su -c "sleep 3 ; dumpsys window windows | grep -E mCurrentFocus" } # 设置别名 alias gping='ping -c 5 www.google.com' alias which='busybox which' alias grep='grep --color=auto' alias rm='rm -i' alias ifconfig='busybox ifconfig' alias vi='busybox vi' alias nc='busybox nc' alias mkfifo='busybox mkfifo' #清屏 clear # 删除相册目录中的缓存 ~/clean_thumbnails.sh #显示日期和时间 echo "User logged in at `date`" # 显示日历 busybox cal `date +%m` `date +%Y` # 显示当前周次 ~/weekofterm.sh # 显示一个空行 echo # 切到主目录 cd ~