使用alternatives工具进行版本控制

个人感觉alternatives挺不好用的,真要进行版本管理还有很多方法(

alternatives大致原理

关于alternatives是什么的已经有很多教程,大致是一个进行版本管理的应用,比如系统中装了两个版本以上的java,这个时候可以用alternatives进行java版本的切换。

其本质是用了Linux中链接的机制,分成linknamepath

  1. link是应用查找的位置,比如/usr/bin这种大多数系统shell默认的应用查找位置,比如/usr/bin/java就是java程序的查找位置。
  2. name充当linkpath的中介,同时也是alternatives进行版本控制的关键。
  3. path是应用真实存放的位置,比如jdk11的java存放于/usr/lib/jvm/jdk-1.11.1-openjdk-amd64/bin/java,jdk17的java存放于/usr/lib/jvm/jdk-1.17.9-openjdk-amd64/bin/java,这种情况下就需要进行版本控制。

以上述为例,alternatives会如此处理链接:

/usr/bin/java -> /etc/alternatives/java -> /usr/lib/jvm/jdk-1.11.1-openjdk-amd64/bin/java

由于查找目录在/usr/bin下,因此用户运行的java最终会是jdk11版本,如果要切换到17,只需将最终的链接切换至/usr/lib/jvm/jdk-1.17.9-openjdk-amd64/bin/java即可。

使用

alternatives对应的命令是update-alternatives,常用的指令如下:

  • update-alternatives --install <link> <name> <path> <priority> [--slave <link> <name> <path> <priority> ...] 为应用注册一个链接,通过这个之后可以通过--config <name>进行选择,--slave格式与install类似,在install后面的链接被选择后会跟着一起更改,对于应用版本的完全切换非常有用。
  • update-alternatives --config <name> 切换应用版本,会列出已经使用install注册后的链接进行选择。

更方便的alternatives切换应用版本

由于不能批量指定,因此alternatives还是比较麻烦的,这里对于一些常见的应用其实alternatives已经提供了一些脚本进行更方便的切换,这里以阿里巴巴的dragonwell为例(阿里巴巴的jvm实现),对于java版本控制,alternatives提供了update-java-alternatives应用,本质上是一个脚本,存放于/usr/sbin目录,其脚本内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#!/bin/bash

shopt -s dotglob

prog=$(basename $0)

usage()
{
rv=$1
cat >&2 <<-EOF
usage: $prog [--jre-headless] [--jre] [--plugin] [-v|--verbose]
-l|--list [<jname>]
-s|--set <jname>
-a|--auto
-h|-?|--help
EOF
exit $rv
}

action=
uaopts='--quiet'

while [ "$#" -gt 0 ]; do
case "$1" in
-a|--auto)
[ -z "$action" ] || usage 1
action=auto;;
-hl|--jre-headless)
hlonly=yes;;
-j|--jre)
jreonly=yes;;
--plugin)
pluginonly=yes;;
-l|--list)
[ -z "$action" ] || usage 1
action=list
if [ "$#" -gt 0 ]; then
shift
jname=$1
fi
;;
-s|--set)
[ -z "$action" ] || usage 1
action=set
shift
[ "$#" -gt 0 ] || usage 1
jname=$1
;;
-v|--verbose)
verbose=yes
uaopts="${uaopts/--quiet/}";;
-h|-?|--help)
usage 0;;
-*)
echo "X: $1"
usage 1;;
*)
break;;
esac
shift
done

[ "$#" -eq 0 ] || usage 1
[ -n "$action" ] || usage 1

which='^(hl|jre|jdk|jdkhl|plugin|DUMMY) '
if [ -n "$hlonly$jreonly$pluginonly" ]; then
which='^('
[ -n "$hlonly" ] && which="${which}hl|"
[ -n "$jreonly" ] && [ -n "$hlonly" ] && which="${which}jre|"
[ -n "$jreonly" ] && [ -z "$hlonly" ] && which="${which}hl|jre|"
[ -n "$pluginonly" ] && which="${which}plugin|"
which="${which}DUMMY) "
fi

top=/usr/lib/jvm

if [ -n "$jname" ]; then
arch=$(dpkg --print-architecture)
if [ ! -x $top/$jname/bin/java ] && [ -x $top/$jname-$arch/bin/java ]; then
echo >&2 "$prog: obsolete <jname>, please use $jname-$arch instead"
jname=$jname-$arch
fi
case "$jname" in
/*) jdir=$jname; jname=$(basename $jdir);;
*) jdir=$top/$jname
esac
if [ ! -d $jdir ]; then
echo >&2 "$prog: directory does not exist: $jdir"
exit 1
fi
if [ -f $top/.$jname.jinfo ]; then
jinfo=$top/.$jname.jinfo
elif [ -f $top/$jname.jinfo ]; then
jinfo=$top/$jname.jinfo
else
echo >&2 "$prog: file does not exist: $top/.$jname.jinfo"
exit 1
fi
fi

vecho()
{
[ -z "$verbose" ] || echo >&2 "$@"
}

jinfo_files=

do_auto()
{
vecho "resetting java alternatives"
awk "/$which/ {print \$2}" $top/*.jinfo | sort -u \
| \
while read name; do
update-alternatives $uaopts --auto $name
done
}

do_list()
{
vecho "listing java alternatives:"
for i in ${jinfo:-$top/*.jinfo}; do
alias=$(basename ${i%.jinfo})
alias=${alias#.}
prio=$(awk -F= '/priority=/ {print $2}' $i)
printf "%-30s %-10s %s\n" $alias "$prio " $top/$alias
[ -n "$verbose" ] && egrep "$which" $i
done
}

do_set()
{
do_auto

awk "/$which/ {print}" $jinfo | sort -u \
| \
while read type name location; do
if [ $type = jdk -a -z "$hlonly$jreonly$pluginonly" -a ! -f $location ]; then
echo >&2 "$prog: jdk alternative does not exist: $location"
continue
fi
if [ $type = plugin -a -z "$hlonly$jreonly" -a ! -f $location ]; then
echo >&2 "$prog: plugin alternative does not exist: $location"
continue
fi
update-alternatives $uaopts --set $name $location
done
}

if [ "$action" != list ] && [ "$(id -u)" -ne 0 ]; then
echo >&2 "$prog: no root privileges"
exit 1
fi

do_$action

分析该脚本,可以看到其本质是调用了update-alternatives进行批量的应用版本切换(do_set函数),其中会读取top变量所指定的目录下的.*.jinfo文件进行配置,这个文件配置大致如下(以我系统上安装的jdk11为例):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
name=java-11-openjdk-amd64
alias=java-1.11.0-openjdk-amd64
priority=1111
section=main

hl java /usr/lib/jvm/java-11-openjdk-amd64/bin/java
hl jjs /usr/lib/jvm/java-11-openjdk-amd64/bin/jjs
hl keytool /usr/lib/jvm/java-11-openjdk-amd64/bin/keytool
hl rmid /usr/lib/jvm/java-11-openjdk-amd64/bin/rmid
hl rmiregistry /usr/lib/jvm/java-11-openjdk-amd64/bin/rmiregistry
hl pack200 /usr/lib/jvm/java-11-openjdk-amd64/bin/pack200
hl unpack200 /usr/lib/jvm/java-11-openjdk-amd64/bin/unpack200
hl jexec /usr/lib/jvm/java-11-openjdk-amd64/lib/jexec
jdkhl jar /usr/lib/jvm/java-11-openjdk-amd64/bin/jar
jdkhl jarsigner /usr/lib/jvm/java-11-openjdk-amd64/bin/jarsigner
jdkhl javac /usr/lib/jvm/java-11-openjdk-amd64/bin/javac
jdkhl javadoc /usr/lib/jvm/java-11-openjdk-amd64/bin/javadoc
jdkhl javap /usr/lib/jvm/java-11-openjdk-amd64/bin/javap
jdkhl jcmd /usr/lib/jvm/java-11-openjdk-amd64/bin/jcmd
jdkhl jdb /usr/lib/jvm/java-11-openjdk-amd64/bin/jdb
jdkhl jdeprscan /usr/lib/jvm/java-11-openjdk-amd64/bin/jdeprscan
jdkhl jdeps /usr/lib/jvm/java-11-openjdk-amd64/bin/jdeps
jdkhl jfr /usr/lib/jvm/java-11-openjdk-amd64/bin/jfr
jdkhl jimage /usr/lib/jvm/java-11-openjdk-amd64/bin/jimage
jdkhl jinfo /usr/lib/jvm/java-11-openjdk-amd64/bin/jinfo
jdkhl jlink /usr/lib/jvm/java-11-openjdk-amd64/bin/jlink
jdkhl jmap /usr/lib/jvm/java-11-openjdk-amd64/bin/jmap
jdkhl jmod /usr/lib/jvm/java-11-openjdk-amd64/bin/jmod
jdkhl jps /usr/lib/jvm/java-11-openjdk-amd64/bin/jps
jdkhl jrunscript /usr/lib/jvm/java-11-openjdk-amd64/bin/jrunscript
jdkhl jshell /usr/lib/jvm/java-11-openjdk-amd64/bin/jshell
jdkhl jstack /usr/lib/jvm/java-11-openjdk-amd64/bin/jstack
jdkhl jstat /usr/lib/jvm/java-11-openjdk-amd64/bin/jstat
jdkhl jstatd /usr/lib/jvm/java-11-openjdk-amd64/bin/jstatd
jdkhl rmic /usr/lib/jvm/java-11-openjdk-amd64/bin/rmic
jdkhl serialver /usr/lib/jvm/java-11-openjdk-amd64/bin/serialver
jdkhl jaotc /usr/lib/jvm/java-11-openjdk-amd64/bin/jaotc
jdkhl jhsdb /usr/lib/jvm/java-11-openjdk-amd64/bin/jhsdb
jdk jconsole /usr/lib/jvm/java-11-openjdk-amd64/bin/jconsole

因此我们可以借用这个脚本和配置文件将我们自己手动安装的软件进行alternatives一键配置。

首先设置在/usr/lib/jvm/目录下的链接,链接至需要部署的软件的目录:

1
royenheart@VM-4-16-debian:~$ ln -s /home/royenheart/.local/dragonwell-17.0.3.0.3+7-GA/ /usr/lib/jvm/dragonwell-17

然后修改配置文件存放于/usr/lib/jvm/目录下,可通过sed批量替换

1
sed -i 's/java-11-openjdk-amd64/dragonwell-17/g' .xxx.jinfo

最后直接通过/usr/sbin/update-java-alternatives --set dragonwell-17进行一键切换,set后的名称为.jinfo文件里name的设置。

总结

如果只涉及到包管理安装(会自动注册alternatives和生成对应的配置文件),那么alternatives也是比较轻量方便的版本控制做法,但如果涉及到自己安装的软件(很多时候是直接二进制文件解压进行安装),又不太想破坏环境变量,alternatives显得有点鸡肋。

  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.

请我喝杯咖啡吧~

支付宝
微信