缘起
说起来呢,这些脚本我自己就已经用了很久了,只是这两天从封锁、适应、到收拾心情继续码字呢,需要一个过渡期。
所以就先放它们出来了。
而实际上关于运维和 DevOps 这一块来说,诸如用 Ansible 或者在 bash.sh 之上建立的 ops.sh
呢,暂时还没精力整理出来,还要等等。毕竟我主业还是 C++ 和 Golang,而不是每天监视服务器。
好接续上一篇 vagrant_run - 更方便地从命令行启动 vagrant 虚拟机 ,这次是 vmware_run,面对的是 VMware Fusion,但应该可以适用于 VMware Workstation。
vmware_run
Vmware_run 不再使用 Hash 文件夹了。因为当年做这个脚本时我对 Hash 文件夹其实根本没认识。另外呢,vmrun(VMware Fusion 自带的工具)提供的能力是面对它的专属格式的 vmwareram 文件夹(Apple Package)的,所以需不着我重命名或者建立特别的助记名来帮助定位,更无需像 vagrant 那样一定要转到目标文件夹才能发出命令。
所以相对而言究其策略反而很简单。
我们首先需要约定所有的虚拟机一律做到 ~/Downloads/VMs/vmware
之下。实际上整个 Downloads 文件夹默认是被我排除在 Time Machine 以及 Spotlight 之外的,这样来说频繁变化的内容、可能并不真的有价值的内容,以及根本不应该备份的就好像虚拟机及其磁盘镜像这些,就没必要被TM或者被索引了,那样没什么意义,徒耗资源。
下面就说说我的思路。
发现 vmrun
首先是找到了 VMware 自带的命令行工具 vmrun,这个呢去 VMware Fusion.app 的文件夹里面扫视一遍就能找得到,再不行就是通过 Google “start stop vmware from command-line” 也最终能够找到源头。
1
which vmrun >/dev/null 2>&1 || ln -s "/Applications/VMware Fusion.app/Contents/Library/vmrun" /usr/local/bin/vmrun
所以我们会将这个工具符号连接出来,以后我们就可以直接发出命令 vmrun 而不是去 Fusion.app 的子目录中调用了。
检测 PATH 搜索路径中有没有 vmrun,我这里用到的是 which,这个方法很简单粗暴。但是实际上在 bash 中检测一个命令是不是可用是有标准方案的:
1
2
3
4
5
if command -v vmrun; then
echo vmrun exists
else
echo vmrun cannot be found
fi
command -v <a command>
不但更标准,而且是 POSIX 兼容的。这话的意思是你可以将这个测试用在 Ubuntu,ArchLinux 等的 bash,zsh 等不同的场所。
如果不高兴,你也可以使用 hash 或者 type(type 不适合于 Windows,本文讲述的也都是 Unix-Like):
1
2
3
4
5
❯ hash vmrun && echo Y || echo N
Y
❯ type vmrun && echo Y || echo N
vmrun is '/Applications/VMware Fusion.app/Contents/Public/vmrun'
Y
哦,type 说的目标怎么和前文设定的目标不一样呢?
因为这也是一个符号链接:
1
2
❯ ll /Applications/VMware\ Fusion.app/Contents/Public/vmrun
lrw-r--r-- 1 root wheel 16 Nov 16 23:50 /Applications/VMware Fusion.app/Contents/Public/vmrun -> ../Library/vmrun
检测一个 command 的可用性
command -v <command>
type <command>
hash <command>
which <command>
(( $+commands[foobar] ))
- zsh only
在必要时,你需要禁止标准输出和错误输出以免这些测试的 outputs 污染你的脚本流程:
1
type vmrun 1>/dev/null 2>&1
不过这是可选的。
启动一个虚拟机
vmrun 自己给出的范例是:
1
vmrun -T ws start "c:\my VMs\myVM.vmx"
我们需要变形一下,首先 -T 应该后接 fusion
而不是 ws
,ws
适用于 VMware Workstation 环境。其次,.vmx 文件是在 一个 vmwarevm Package Folder 之中的。例如我们有一个 ~/Downloads/VMs/vmware/u20s.local.vmwarevm
的虚拟机包文件,那么通常情况下 vmx 文件就是 ~/Downloads/VMs/vmware/u20s.local.vmwarevm/u20s.local.vmx
。
所以我们可以这样启动 u20s.local
虚拟机:
1
vmrun -T fusion start "~/Downloads/VMs/vmware/u20s.local.vmwarevm/u20s.local.vmx"
无界面启动
另一个问题是 headless 启动一个虚拟机,不要弹出虚拟机窗口。这个要求可以用追加参数 nogui 来实现:
1
vmrun -T fusion start "~/Downloads/VMs/vmware/u20s.local.vmwarevm/u20s.local.vmx" nogui
停止一个虚拟机
参考前文,除了将 start
换为 stop
之外,其他没有改动。
检测重复启停
vmrun list 能够列出当前已经运行的虚拟机。所以我们只需要检测我们的虚拟机名字是不是存在就可以了:
1
2
3
4
5
6
7
❯ vmrun list
Total running VMs: 1
~/Downloads/VMs/vmware/u20s.local.vmwarevm/u20s.local.vmx
❯ vmrun list|grep 'u20s.local.vmx' && echo Y || echo N
~/Downloads/VMs/vmware/u20s.local.vmwarevm/u20s.local.vmx
Y
完整的脚本定义
于是我们就可以整合上面的知识,形成一个完整的 vmware_run 命令了。
Bash Script 的有些细节往往能够颠覆 C++ 开发者的思维惯性。比如说在 Bash 脚本开发中,function 是不必写 function 的:
1
2
3
4
5
6
7
function real-world(){
echo This is a real world!
}
real-world(){
echo This is a real world!
}
上面两个函数都是正确的写法。
多数情况下,我不写 function 关键字。但是如果你在一个巨型 shell script 团队中协作的话,注意如下亮点:
- 总是写 function 关键字
- 不要使用短横线作为函数名的一部分
如果是为了让你的函数更具备通用性和可移植性,那么短横线不要使用,改用下划线以代替之。尽管这一点我很不喜欢,但是在例如 zsh 中,函数名中的短横线将会带来问题,而在 fish 中就根本不允许短横线参与到名字之中。
如果要指出 Shell script 中反常识的地方,最 topest 的应该是嵌套函数,就是说一个函数的实体可以被写在另一个之中。
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
vmware_run() {
local VMDIR="${VMWARE_DIR:-$HOME/Downloads/VMs/vmware}"
local VMTYPE="${VMWARE_TYPE:-fusion}" # or "ws"
# local VMDIR="$HOME/Documents/Virtual Machines.localized"
which vmrun >/dev/null 2>&1 || ln -s "/Applications/VMware Fusion.app/Contents/Library/vmrun" /usr/local/bin/vmrun
vmware_run_usage() {
cat <<-EOF
Usage:
$(basename $0) <VM-name> <start|up|stop|halt|suspend|reset|pause|unpause>
$(basename $0) <VM-name> <start_with_gui|start-with-gui>
Examples:
$(basename $0) u20.local start
# start vm without gui (headless mode)
EOF
}
vmware_run_start() {
local cmd=$1
local vmname=$2
shift
shift
vmrun list | grep -q "$vmname" && {
echo "VM '$vmname' already running"
} || {
[ "$cmd" != "start" ] && cmd=start
echo "vmrun -T $VMTYPE $cmd $VMDIR/$vmname.vmwarevm/$vmname.vmx" "$@"
vmrun -T $VMTYPE $cmd $VMDIR/$vmname.vmwarevm/$vmname.vmx "$@"
}
}
vmware_run_stop() {
local cmd=$1
local vmname=$2
vmrun list | grep -q "$vmname" || {
echo "VM '$vmname' not running"
} && {
[ "$cmd" != "stop" ] && cmd=stop
vmrun -T $VMTYPE $cmd $VMDIR/$vmname.vmwarevm/$vmname.vmx
}
}
case ${2:-start} in
start | start-without-gui | up)
local cmd=${2:-start} && local vmn=$1
(($#)) && shift
(($#)) && shift
dbg "vmn=$wmn" &&
vmware_run_start $cmd $vmn nogui "$@"
;;
start_with_gui | start-with-gui | unpause)
local cmd=$2 && local vmn=$1
(($#)) && shift
(($#)) && shift
vmware_run_start start $vmn "$@"
;;
stop | suspend | reset | pause | halt)
local cmd=$2 && local vmn=$1
(($#)) && shift
(($#)) && shift
vmware_run_stop $cmd $vmn "$@"
;;
usage | help | -h | -H | --help)
vmware_run_usage
;;
*)
case $1 in
usage | help | -h | -H | --help)
vmware_run_usage
;;
*)
vmrun "$@"
;;
esac
;;
esac
}
同样地,够用就好,所以这个大大的脚本并不够精细,少数地方可堪斟酌。但通常情况下它完全足够了。
使用
使用 vmware_run 的方法是比较明显的,上面的脚本不困难:
1
2
3
4
vmware_run u20s.local
vmware_run k8s.local start
vmware_run k8s.local stop
vmware_run kubuntu.local start-with-gui
可以定制环境变量:
1
VMWARE_DIR="/Users/Shared/Virtual Hosts" vmware_run u20s.local
Gist
这个大大的脚本并不是太大,所以还是放在 gist 了:
请自行按需取用吧。
小结
暂时写这么多。
留下评论