Physical Address:
ChongQing,China.
WebSite:
当我们通过adb shell进入Android的shell环境时,命令行左侧都会显示当前的设备名。如下所示:
c1200:/ $
在Linux环境下,当我们通过SSH连接进入Linux Shell时,命令行左侧同样会显示当前设备的名称,不同的是,Linux环境下显示的设备名称,其实是对应当前Linux设备的Hostname与User相结合,而安卓默认的命令行提示符则简单一些。
在Linux环境中,命令行提示符的显示由PS1环境变量决定,Android内同样如此,这里我们查看一下PS1环境变量:
PS C:\Users\bst> adb shell
c1200:/ # echo $PS1
${| local e=$? (( e )) && REPLY+="$e|" return $e }$USER@$HOSTNAME:${PWD:-?} #
c1200:/ #
该环境变量的值设定的含义具体解析如下:
${| … }:这是一种形式的命令替换,它执行其中的命令并将其输出作为字符串插入到PS1中。在这个情况下,整个 ${| … } 结构实际上是用来设置提示符的。
local e=$?:这一行声明了一个局部变量 e 来存储上一个命令的退出状态,熟悉shell的朋友应该知道$? 是一个特殊变量,保存了上一个执行命令的退出状态码。
(( e )) && REPLY+=”$e|”:这是一个条件表达式,它检查变量 e 的值是否为非零。如果是,它将上一个命令的退出状态码附加到提示符字符串 REPLY 中,以及一个竖线(|),表示非零退出状态码。
return $e:这一行会返回上一个命令的退出状态码。这意味着,如果前一个命令失败(退出状态码非零),那么整个提示符将会包含该失败命令的退出状态码,并且Shell的退出状态码将会被设置为该值。
$HOSTNAME:这是主机名变量,用于显示当前主机的名称。
${PWD:-?}:这是当前工作目录的变量。${PWD} 会显示当前工作目录的绝对路径。如果当前工作目录无法获取,它会显示一个问号(?)作为占位符。
所以,综上所述,这个 PS1 设置的含义是:显示主机名、当前工作目录的绝对路径,并在提示符中显示上一个命令的退出状态码(如果存在),以及一个竖线分隔符;那么PS1是如何被设置的呢,关于这部分的设置逻辑,这里我们需要参考sh的源码:
//external/mksh/src/main.c
if (Flag(FLOGIN))
include(substitute("$HOME/.profile", 0), 0, NULL, true);
if (Flag(FTALKING)) {
cp = substitute("${ENV:-" MKSHRC_PATH "}", DOTILDE);
if (cp[0] != '\0')
// include属于自定义函数
include(cp, 0, NULL, true);
}
//include为自定义实现的函数,实现了对shell环境变量的配置
include(const char *name, int argc, const char **argv, bool intr_ok)
{
Source *volatile s = NULL;
struct shf *shf;
const char **volatile old_argv;
volatile int old_argc;
int i;
shf = shf_open(name, O_RDONLY | O_MAYEXEC, 0, SHF_MAPHI | SHF_CLEXEC);
if (shf == NULL)
return (-1);
if (argv) {
old_argv = e->loc->argv;
old_argc = e->loc->argc;
} else {
old_argv = NULL;
old_argc = 0;
}
newenv(E_INCL);
if ((i = kshsetjmp(e->jbuf))) {
quitenv(s ? s->u.shf : NULL);
if (old_argv) {
e->loc->argv = old_argv;
e->loc->argc = old_argc;
}
switch (i) {
case LRETURN:
case LERROR:
case LERREXT:
/* see below */
return (exstat & 0xFF);
case LINTR:
/*
* intr_ok is set if we are including .profile or $ENV.
* If user ^Cs out, we don't want to kill the shell...
*/
if (intr_ok && ((exstat & 0xFF) - 128) != SIGTERM)
return (1);
/* FALLTHROUGH */
case LEXIT:
case LLEAVE:
case LSHELL:
unwind(i);
/* NOTREACHED */
default:
internal_errorf(Tunexpected_type, Tunwind, Tsource, i);
/* NOTREACHED */
}
}
if (argv) {
e->loc->argv = argv;
e->loc->argc = argc;
}
s = pushs(SFILE, ATEMP);
s->u.shf = shf;
strdupx(s->file, name, ATEMP);
i = shell(s, 1);
quitenv(s->u.shf);
if (old_argv) {
e->loc->argv = old_argv;
e->loc->argc = old_argc;
}
/* & 0xff to ensure value not -1 */
return (i & 0xFF);
}
这里MKSHRC_PATH的值为/system/etc/mkshrc,其内容为:
set +o nohup
if (( USER_ID )); then PS1='$'; else PS1='#'; fi
PS4='[$EPOCHREALTIME] '; PS1='${|
local e=$?
(( e )) && REPLY+="$e|"
return $e
}$HOSTNAME:${PWD:-?} '"$PS1 "
所以这里PS1的设置逻辑其实是通过sh解析/system/etc/mkshrc文件后配置到环境变量内的。
所以如果我们要修改命令行提示符,可以通过修改/system/etc/mkshrc文件的配置,比如我希望命令行提示符显示当前所属用户(一般情况下为shell用户,adb root之后则为root用户),则可以修改/system/etc/mkshrc为如下所示:
set +o nohup
if (( USER_ID )); then PS1='$'; else PS1='#'; fi
PS4='[$EPOCHREALTIME] '; PS1='${|
local e=$?
(( e )) && REPLY+="$e|"
return $e
}$USER@$HOSTNAME:${PWD:-?} '"$PS1 "
修改的逻辑很简单,其实就是利用$USER环境变量来获取当前用户信息,并将其置于HOSTNAME之前。
之后我们再重新adb shell进入,看看效果:
shell@c1200:/ $
如果我们先adb root,再adb shell进入,会发现命令行提示符为:
root@c1200:/ $
是不是不一样了?是不是很神奇!
除此之后,我们应该还关注到在设置PS1的过程中用到HOSTNAME环境变量,该变量的值一般是与ro.product.device的值保持一致的,而ro.product.device与Android编译配置时的环境变量TARGET_DEVICE保持一致,TARGET_DEVICE的值又与我们的编译配置PRODUCT_DEVICE配置一致;
所以如果我们要修改命令行提示符中的设备标识,一来我们可以修改编译配置时的PRODUCT_DEVICE名称,二来可以修改/system/etc/mkshrc,设定HOSTNAME的值即可。