├── .shellcheckrc ├── LICENSE ├── Makefile ├── README.md ├── package-lock.json ├── package.json └── vbox /.shellcheckrc: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # .shellcheckrc 3 | # 4 | # https://github.com/koalaman/shellcheck 5 | # https://github.com/koalaman/shellcheck/wiki/Ignore 6 | ############################################################################### 7 | 8 | # Disable SC2206 and SC2207 9 | # 10 | # `IFS` and `noglob` are set. 11 | # 12 | # https://github.com/koalaman/shellcheck/wiki/SC2206 13 | # https://github.com/koalaman/shellcheck/wiki/SC2207 14 | disable=SC2206,SC2207 15 | 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 William Melody 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BIN ?= vbox 2 | PREFIX ?= /usr/local 3 | 4 | install: 5 | install $(BIN) $(PREFIX)/bin 6 | 7 | uninstall: 8 | rm -f $(PREFIX)/bin/$(BIN) 9 | 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | _ 3 | __ _| |__ _____ __ 4 | \ \ / / '_ \ / _ \ \/ / 5 | \ V /| |_) | (_) > < 6 | \_/ |_.__/ \___/_/\_\ 7 | ``` 8 | 9 | # vbox 10 | 11 | A streamlined interface for 12 | [VBoxManage](https://www.virtualbox.org/manual/ch08.html), the 13 | [VirtualBox](https://www.virtualbox.org/) command line tool. 14 | 15 | `vbox` wraps `VBoxManage`, providing a simpler UI with a focus on frequently 16 | used commands. 17 | 18 | ## Installation 19 | 20 | ### Homebrew 21 | 22 | To install with [Homebrew](http://brew.sh/): 23 | 24 | ```bash 25 | brew tap xwmx/taps 26 | brew install vbox 27 | ``` 28 | 29 | ### npm 30 | 31 | To install with [npm](https://www.npmjs.com/package/vbox.sh): 32 | 33 | ```bash 34 | npm install --global vbox.sh 35 | ``` 36 | 37 | ### bpkg 38 | 39 | To install with [bpkg](http://www.bpkg.io/): 40 | 41 | ```bash 42 | bpkg install xwmx/vbox 43 | ``` 44 | 45 | ### Manual 46 | 47 | To install manually, simply add the `vbox` script to your `$PATH`. If 48 | you already have a `~/bin` directory, you can use the following command: 49 | 50 | ```bash 51 | curl -L https://raw.github.com/xwmx/vbox/master/vbox \ 52 | -o ~/bin/vbox && chmod +x ~/bin/vbox 53 | ``` 54 | 55 | ## Usage 56 | 57 | ``` 58 | Usage: 59 | vbox commands [--raw] 60 | vbox config [--path] 61 | vbox forwarding add 62 | vbox forwarding list 63 | vbox forwarding delete 64 | vbox (help [] | -h | --help) 65 | vbox kill ( | ) 66 | vbox list [running | status] 67 | vbox manage [] 68 | vbox pause ( | ) 69 | vbox reset ( | ) 70 | vbox resume ( | ) 71 | vbox show ( | ) 72 | vbox start ( | ) [--headless] 73 | vbox status [( | ) [--long|-l]] 74 | vbox stop ( | ) 75 | vbox (version | --version) 76 | 77 | Global Options: 78 | -h --help Display this help information. 79 | --version Display version information. 80 | 81 | Help: 82 | vbox help [] 83 | ``` 84 | 85 | ### Subcommands 86 | 87 | #### `commands` 88 | 89 | ``` 90 | Usage: 91 | vbox commands [--raw] 92 | 93 | Options: 94 | --raw Display the command list without formatting. 95 | 96 | Description: 97 | Display the list of available commands. 98 | ``` 99 | 100 | #### `config` 101 | 102 | ``` 103 | Usage: 104 | vbox config [--path] 105 | 106 | Options: 107 | --path Print the path to the configuration file, 'VirtualBox.xml'. 108 | 109 | Description: 110 | When no argument has been passed, open the 'VirtualBox.xml' configuration 111 | file in `$EDITOR`, which is currently set to ''. When the 112 | `--path` option is specified, the path to 'VirtualBox.xml' is printed. 113 | ``` 114 | 115 | #### `forwarding` 116 | 117 | ``` 118 | Usage: 119 | vbox forwarding add 120 | vbox forwarding list 121 | vbox forwarding delete 122 | 123 | Subcommands: 124 | add Add a new port forwarding rule. 125 | list List forwarding rules. 126 | delete Delete the specified rule. 127 | 128 | Description: 129 | Manage port forwarding. 130 | 131 | Example: 132 | vbox forwarding add "ubuntu-vm" "tcp5000" "5000" 133 | 134 | Equivalent of: 135 | VBoxManage controlvm ubuntu-vm \ 136 | natpf1 "tcp5000,tcp,127.0.0.1,5000,,5000" 137 | ``` 138 | 139 | #### `help` 140 | 141 | ``` 142 | Usage: 143 | vbox help [] 144 | 145 | Description: 146 | Display help information for vbox or a specified command. 147 | ``` 148 | 149 | #### `kill` 150 | 151 | ``` 152 | Usage: 153 | vbox kill ( | ) 154 | 155 | Description: 156 | Command: `VBoxManage controlvm poweroff` 157 | 158 | Has the same effect on a virtual machine as pulling the power cable on a real 159 | computer. The state of the VM is not saved beforehand, and data may be lost. 160 | (This is equivalent to selecting the "Close" item in the "Machine" menu of 161 | the GUI or pressing the window's close button, and then selecting "Power off 162 | the machine" in the dialog.) 163 | ``` 164 | 165 | #### `list` 166 | 167 | ``` 168 | Usage: 169 | vbox list [running | status] 170 | 171 | Arguments: 172 | running List all running VMs. 173 | status Display all VMs with basic status information. 174 | 175 | Description: 176 | List VirtualBox VMs. 177 | ``` 178 | 179 | #### `manage` 180 | 181 | ``` 182 | Usage: 183 | vbox manage [] 184 | 185 | Description: 186 | Alias for `VBoxManage`. 187 | 188 | VBoxManage Documentation: 189 | https://www.virtualbox.org/manual/ch08.html 190 | ``` 191 | 192 | #### `pause` 193 | 194 | ``` 195 | Usage: 196 | vbox pause ( | ) 197 | 198 | Description: 199 | Command: `VBoxManage controlvm pause` 200 | 201 | Temporarily puts a virtual machine on hold, without changing its state for 202 | good. The VM window will be painted in gray to indicate that the VM is 203 | currently paused. (This is equivalent to selecting the "Pause" item in the 204 | "Machine" menu of the GUI.) 205 | ``` 206 | 207 | #### `reset` 208 | 209 | ``` 210 | Usage: 211 | vbox reset ( | ) 212 | 213 | Description: 214 | Command: `VBoxManage controlvm reset` 215 | 216 | Has the same effect on a virtual machine as pressing the "Reset" button on a 217 | real computer: a cold reboot of the virtual machine, which will restart and 218 | boot the guest operating system again immediately. The state of the VM is not 219 | saved beforehand, and data may be lost. (This is equivalent to selecting the 220 | "Reset" item in the "Machine" menu of the GUI.) 221 | ``` 222 | 223 | #### `resume` 224 | 225 | ``` 226 | Usage: 227 | vbox resume ( | ) 228 | 229 | Description: 230 | Command: `VBoxManage controlvm resume` 231 | 232 | Undo a previous pause command. (This is equivalent to selecting the "Resume" 233 | item in the "Machine" menu of the GUI.) 234 | ``` 235 | 236 | #### `show` 237 | 238 | ``` 239 | Usage: 240 | vbox show ( | ) 241 | 242 | Description: 243 | Command: `VBoxManage showvminfo ` 244 | 245 | Show information about a particular virtual machine. 246 | ``` 247 | 248 | #### `start` 249 | 250 | ``` 251 | Usage: 252 | vbox start ( | ) [--headless] 253 | 254 | Description: 255 | Start the VM with the given name or UUID. 256 | ``` 257 | 258 | #### `status` 259 | 260 | ``` 261 | Usage: 262 | vbox status [( | ) [--long|-l]] 263 | 264 | Options: 265 | -l --long Display long-form status information for the specified VM. 266 | 267 | Description: 268 | When no argument has been passed, this acts as an alias for 269 | `vbox list status` and displays the status for all of the VMs. When 270 | passed a VM name or UUID, the status aka state of that VM is displayed. 271 | ``` 272 | #### `stop` 273 | 274 | ``` 275 | Usage: 276 | vbox stop ( | ) 277 | 278 | Description: 279 | Command: `VBoxManage controlvm savestate` 280 | 281 | Save the current state of the VM to disk and then stop the VM. (This is 282 | equivalent to selecting the "Close" item in the "Machine" menu of the GUI or 283 | pressing the window's close button, and then selecting "Save the machine 284 | state" in the dialog.) 285 | ``` 286 | 287 | #### `version` 288 | 289 | ``` 290 | Usage: 291 | vbox (version | --version) 292 | 293 | Description: 294 | Display the current program version. 295 | ``` 296 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vbox.sh", 3 | "version": "1.2.2", 4 | "lockfileVersion": 1 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vbox.sh", 3 | "version": "1.2.2", 4 | "description": "A streamlined interface for VBoxManage, the VirtualBox command line tool.", 5 | "global": true, 6 | "install": "make install", 7 | "bin": { 8 | "vbox": "./vbox" 9 | }, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/xwmx/vbox.git" 16 | }, 17 | "keywords": [ 18 | "vboxmanage", 19 | "virtualbox", 20 | "command-line", 21 | "bash", 22 | "virtual-machine", 23 | "vm", 24 | "cli", 25 | "shell", 26 | "prompt", 27 | "terminal", 28 | "vbox" 29 | ], 30 | "author": "William Melody", 31 | "license": "MIT", 32 | "bugs": { 33 | "url": "https://github.com/xwmx/vbox/issues" 34 | }, 35 | "homepage": "https://github.com/xwmx/vbox#readme" 36 | } 37 | -------------------------------------------------------------------------------- /vbox: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # _ 3 | # __ _| |__ _____ __ 4 | # \ \ / / '_ \ / _ \ \/ / 5 | # \ V /| |_) | (_) > < 6 | # \_/ |_.__/ \___/_/\_\ 7 | # 8 | # A streamlined interface for VBoxManage, the VirtualBox command line tool. 9 | # 10 | # https://github.com/xwmx/vbox 11 | # 12 | # Depends on: 13 | # VBoxManage, part of VirtualBox 14 | # 15 | # Bash Boilerplate: https://github.com/xwmx/bash-boilerplate 16 | # 17 | # Copyright (c) 2016 William Melody • hi@williammelody.com 18 | 19 | ############################################################################### 20 | # Strict Mode 21 | ############################################################################### 22 | 23 | set -o nounset 24 | set -o errexit 25 | set -o pipefail 26 | set -o noglob 27 | IFS=$'\n\t' 28 | 29 | ############################################################################### 30 | # Globals 31 | ############################################################################### 32 | 33 | # $_VERSION 34 | # 35 | # Manually set this to to current version of the program. Adhere to the 36 | # semantic versioning specification: http://semver.org 37 | _VERSION="1.2.2" 38 | 39 | # $DEFAULT_COMMAND 40 | # 41 | # The command to be run by default, when no command name is specified. If the 42 | # environment has an existing $DEFAULT_COMMAND set, then that value is used. 43 | DEFAULT_COMMAND="${DEFAULT_COMMAND:-help}" 44 | 45 | ############################################################################### 46 | # Debug 47 | ############################################################################### 48 | 49 | # _debug() 50 | # 51 | # Usage: 52 | # _debug printf "Debug info. Variable: %s\\n" "$0" 53 | # 54 | # A simple function for executing a specified command if the `$_USE_DEBUG` 55 | # variable has been set. The command is expected to print a message and 56 | # should typically be either `echo`, `printf`, or `cat`. 57 | __DEBUG_COUNTER=0 58 | _debug() { 59 | if [[ "${_USE_DEBUG:-"0"}" -eq 1 ]] 60 | then 61 | __DEBUG_COUNTER=$((__DEBUG_COUNTER+1)) 62 | # Prefix debug message with "bug (U+1F41B)" 63 | printf "🐛 %s " "${__DEBUG_COUNTER}" 64 | "${@}" 65 | printf "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\\n" 66 | fi 67 | } 68 | # debug() 69 | # 70 | # Usage: 71 | # debug "Debug info. Variable: $0" 72 | # 73 | # Print the specified message if the `$_USE_DEBUG` variable has been set. 74 | # 75 | # This is a shortcut for the _debug() function that simply echos the message. 76 | debug() { 77 | _debug echo "${@}" 78 | } 79 | 80 | ############################################################################### 81 | # Die 82 | ############################################################################### 83 | 84 | # _die() 85 | # 86 | # Usage: 87 | # _die printf "Error message. Variable: %s\\n" "$0" 88 | # 89 | # A simple function for exiting with an error after executing the specified 90 | # command. The command is expected to print a message and should typically 91 | # be either `echo`, `printf`, or `cat`. 92 | _die() { 93 | # Prefix die message with "cross mark (U+274C)", often displayed as a red x. 94 | printf "❌ " 95 | "${@}" 1>&2 96 | exit 1 97 | } 98 | # die() 99 | # 100 | # Usage: 101 | # die "Error message. Variable: $0" 102 | # 103 | # Exit with an error and print the specified message. 104 | # 105 | # This is a shortcut for the _die() function that simply echos the message. 106 | die() { 107 | _die echo "${@}" 108 | } 109 | 110 | ############################################################################### 111 | # Options 112 | # 113 | # NOTE: The `getops` builtin command only parses short options and BSD `getopt` 114 | # does not support long arguments (GNU `getopt` does), so the most portable 115 | # and clear way to parse options is often to just use a `while` loop. 116 | # 117 | # For a pure bash `getopt` function, try pure-getopt: 118 | # https://github.com/agriffis/pure-getopt 119 | # 120 | # More info: 121 | # http://wiki.bash-hackers.org/scripting/posparams 122 | # http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html 123 | # http://stackoverflow.com/a/14203146 124 | # http://stackoverflow.com/a/7948533 125 | # https://stackoverflow.com/a/12026302 126 | # https://stackoverflow.com/a/402410 127 | ############################################################################### 128 | 129 | # Get raw options for any commands that expect them. 130 | _RAW_OPTIONS="${*:-}" 131 | 132 | # Parse Options ############################################################### 133 | 134 | # Initialize $_COMMAND_ARGV array 135 | # 136 | # This array contains all of the arguments that get passed along to each 137 | # command. This is essentially the same as the program arguments, minus those 138 | # that have been filtered out in the program option parsing loop. This array 139 | # is initialized with $0, which is the program's name. 140 | _COMMAND_ARGV=("${0}") 141 | # Initialize $_CMD and `$_USE_DEBUG`, which can continue to be blank depending 142 | # on what the program needs. 143 | _CMD="" 144 | _USE_DEBUG=0 145 | 146 | while [[ ${#} -gt 0 ]] 147 | do 148 | __opt="${1}" 149 | shift 150 | case "${__opt}" in 151 | -h|--help) 152 | _CMD="help" 153 | ;; 154 | --version) 155 | _CMD="version" 156 | ;; 157 | --debug) 158 | _USE_DEBUG=1 159 | ;; 160 | *) 161 | # The first non-option argument is assumed to be the command name. 162 | # All subsequent arguments are added to $_COMMAND_ARGV. 163 | if [[ -n "${_CMD}" ]] 164 | then 165 | _COMMAND_ARGV+=("${__opt}") 166 | else 167 | _CMD="${__opt}" 168 | fi 169 | ;; 170 | esac 171 | done 172 | 173 | # Set $_COMMAND_PARAMETERS to $_COMMAND_ARGV, minus the initial element, $0. This 174 | # provides an array that is equivalent to $* and $@ within each command 175 | # function, though the array is zero-indexed, which could lead to confusion. 176 | # 177 | # Use `unset` to remove the first element rather than slicing (e.g., 178 | # `_COMMAND_PARAMETERS=("${_COMMAND_ARGV[@]:1}")`) because under bash 3.2 the 179 | # resulting slice is treated as a quoted string and doesn't easily get coaxed 180 | # into a new array. 181 | _COMMAND_PARAMETERS=(${_COMMAND_ARGV[*]}) 182 | unset "_COMMAND_PARAMETERS[0]" 183 | 184 | _debug printf "\${_CMD}: %s\\n" "${_CMD}" 185 | _debug printf "\${_RAW_OPTIONS} (one per line):\\n%s\\n" "${_RAW_OPTIONS}" 186 | _debug printf "\${_COMMAND_ARGV[*]}: %s\\n" "${_COMMAND_ARGV[*]}" 187 | _debug printf \ 188 | "\${_COMMAND_PARAMETERS[*]:-}: %s\\n" \ 189 | "${_COMMAND_PARAMETERS[*]:-}" 190 | 191 | ############################################################################### 192 | # Environment 193 | ############################################################################### 194 | 195 | # $_ME 196 | # 197 | # Set to the program's basename. 198 | _ME=$(basename "${0}") 199 | 200 | _debug printf "\${_ME}: %s\\n" "${_ME}" 201 | 202 | ############################################################################### 203 | # Load Commands 204 | ############################################################################### 205 | 206 | # Initialize $_DEFINED_COMMANDS array. 207 | _DEFINED_COMMANDS=() 208 | 209 | # _load_commands() 210 | # 211 | # Usage: 212 | # _load_commands 213 | # 214 | # Loads all of the commands sourced in the environment. 215 | _load_commands() { 216 | 217 | _debug printf "_load_commands(): entering...\\n" 218 | _debug printf "_load_commands() declare -F:\\n%s\\n" "$(declare -F)" 219 | 220 | # declare is a bash built-in shell function that, when called with the '-F' 221 | # option, displays all of the functions with the format 222 | # `declare -f function_name`. These are then assigned as elements in the 223 | # $function_list array. 224 | local _function_list 225 | _function_list=($(declare -F)) 226 | 227 | _debug printf \ 228 | "_load_commands() \${_function_list[@]}: %s\\n" \ 229 | "${_function_list[@]}" 230 | 231 | for __name in "${_function_list[@]}" 232 | do 233 | _debug printf \ 234 | "_load_commands() \${__name}: %s\\n" \ 235 | "${__name}" 236 | # Each element has the format `declare -f function_name`, so set the name 237 | # to only the 'function_name' part of the string. 238 | local _function_name 239 | _function_name=$(printf "%s" "${__name}" | awk '{ print $3 }') 240 | 241 | _debug printf \ 242 | "_load_commands() \${_function_name}: %s\\n" \ 243 | "${_function_name}" 244 | 245 | # Add the function name to the $_DEFINED_COMMANDS array unless it starts 246 | # with an underscore or is one of the desc(), debug(), or die() functions, 247 | # since these are treated as having 'private' visibility. 248 | if ! { [[ "${_function_name}" =~ ^_(.*) ]] || \ 249 | [[ "${_function_name}" == "desc" ]] || \ 250 | [[ "${_function_name}" == "debug" ]] || \ 251 | [[ "${_function_name}" == "die" ]] 252 | } 253 | then 254 | _DEFINED_COMMANDS+=("${_function_name}") 255 | fi 256 | done 257 | 258 | _debug printf \ 259 | "commands() \${_DEFINED_COMMANDS[*]:-}:\\n%s\\n" \ 260 | "${_DEFINED_COMMANDS[*]:-}" 261 | } 262 | 263 | ############################################################################### 264 | # Main 265 | ############################################################################### 266 | 267 | # _main() 268 | # 269 | # Usage: 270 | # _main 271 | # 272 | # The primary function for starting the program. 273 | # 274 | # NOTE: must be called at end of program after all commands have been defined. 275 | _main() { 276 | _debug printf "main(): entering...\\n" 277 | _debug printf "main() \${_CMD} (upon entering): %s\\n" "${_CMD}" 278 | 279 | # If $_CMD is blank, then set to `$DEFAULT_COMMAND` 280 | if [[ -z "${_CMD}" ]] 281 | then 282 | _CMD="${DEFAULT_COMMAND}" 283 | fi 284 | 285 | # Load all of the commands. 286 | _load_commands 287 | 288 | # If the command is defined, run it, otherwise return an error. 289 | if _contains "${_CMD}" "${_DEFINED_COMMANDS[*]:-}" 290 | then 291 | # Pass all comment arguments to the program except for the first ($0). 292 | ${_CMD} "${_COMMAND_PARAMETERS[@]:-}" 293 | else 294 | _die printf "Unknown command: %s\\n" "${_CMD}" 295 | fi 296 | } 297 | 298 | ############################################################################### 299 | # Utility Functions 300 | ############################################################################### 301 | 302 | # _function_exists() 303 | # 304 | # Usage: 305 | # _function_exists "possible_function_name" 306 | # 307 | # Returns: 308 | # 0 If a function with the given name is defined in the current environment. 309 | # 1 If not. 310 | # 311 | # Other implementations, some with better performance: 312 | # http://stackoverflow.com/q/85880 313 | _function_exists() { 314 | [ "$(type -t "${1}")" == 'function' ] 315 | } 316 | 317 | # _command_exists() 318 | # 319 | # Usage: 320 | # _command_exists "possible_command_name" 321 | # 322 | # Returns: 323 | # 0 If a command with the given name is defined in the current environment. 324 | # 1 If not. 325 | # 326 | # Information on why `hash` is used here: 327 | # http://stackoverflow.com/a/677212 328 | _command_exists() { 329 | hash "${1}" 2>/dev/null 330 | } 331 | 332 | # _contains() 333 | # 334 | # Usage: 335 | # _contains "$item" "${list[*]}" 336 | # 337 | # Returns: 338 | # 0 If the item is included in the list. 339 | # 1 If not. 340 | _contains() { 341 | local _test_list=(${*:2}) 342 | for __test_element in "${_test_list[@]:-}" 343 | do 344 | _debug printf "_contains() \${__test_element}: %s\\n" "${__test_element}" 345 | if [[ "${__test_element}" == "${1}" ]] 346 | then 347 | _debug printf "_contains() match: %s\\n" "${1}" 348 | return 0 349 | fi 350 | done 351 | return 1 352 | } 353 | 354 | # _join() 355 | # 356 | # Usage: 357 | # _join 358 | # 359 | # Examples: 360 | # _join , a "b c" d => a,b c,d 361 | # _join / var local tmp => var/local/tmp 362 | # _join , "${FOO[@]}" => a,b,c 363 | # 364 | # More Information: 365 | # http://stackoverflow.com/a/17841619 366 | _join() { 367 | local IFS="${1}" 368 | shift 369 | printf "%s\\n" "${*}" 370 | } 371 | 372 | # _command_argv_includes() 373 | # 374 | # Usage: 375 | # _command_argv_includes "an_argument" 376 | # 377 | # Returns: 378 | # 0 If the argument is included in `$_COMMAND_ARGV`, the program's command 379 | # argument list. 380 | # 1 If not. 381 | # 382 | # This is a shortcut for simple cases where a command wants to check for the 383 | # presence of options quickly without parsing the options again. 384 | _command_argv_includes() { 385 | _contains "${1}" "${_COMMAND_ARGV[*]}" 386 | } 387 | 388 | # _blank() 389 | # 390 | # Usage: 391 | # _blank "$an_argument" 392 | # 393 | # Returns: 394 | # 0 If the argument is not present or null. 395 | # 1 If the argument is present and not null. 396 | _blank() { 397 | [[ -z "${1:-}" ]] 398 | } 399 | 400 | # _present() 401 | # 402 | # Usage: 403 | # _present "$an_argument" 404 | # 405 | # Returns: 406 | # 0 If the argument is present and not null. 407 | # 1 If the argument is not present or null. 408 | _present() { 409 | [[ -n "${1:-}" ]] 410 | } 411 | 412 | # _interactive_input() 413 | # 414 | # Usage: 415 | # _interactive_input 416 | # 417 | # Returns: 418 | # 0 If the current input is interactive (eg, a shell). 419 | # 1 If the current input is stdin / piped input. 420 | _interactive_input() { 421 | [[ -t 0 ]] 422 | } 423 | 424 | # _piped_input() 425 | # 426 | # Usage: 427 | # _piped_input 428 | # 429 | # Returns: 430 | # 0 If the current input is stdin / piped input. 431 | # 1 If the current input is interactive (eg, a shell). 432 | _piped_input() { 433 | ! _interactive_input 434 | } 435 | 436 | # _emojify_status() 437 | # 438 | # Usage: 439 | # _emojify_status 440 | # echo | _emojify_status 441 | # 442 | # Description: 443 | # Add emoji to status messages. 444 | _emojify_status() { 445 | local _status_string="${1:-}" 446 | 447 | if [[ -n "${_status_string}" ]] 448 | then 449 | _status_string="${_status_string//aborted/❕ aborted}" 450 | _status_string="${_status_string//paused/⏸ paused}" 451 | _status_string="${_status_string//powered/⚫️ powered}" 452 | _status_string="${_status_string//running/✅ running}" 453 | _status_string="${_status_string//saved/💾 saved}" 454 | 455 | printf "%s\\n" "${_status_string}" 456 | else 457 | _die printf "Usage: _emojify_status \\n" 458 | fi 459 | } 460 | 461 | ############################################################################### 462 | # desc 463 | ############################################################################### 464 | 465 | # desc() 466 | # 467 | # Usage: 468 | # desc 469 | # desc --get 470 | # 471 | # Options: 472 | # --get Print the description for if one has been set. 473 | # 474 | # Examples: 475 | # ``` 476 | # desc "list" <. The 488 | # text can be passed as the second argument or as standard input. 489 | # 490 | # To make the text available to other functions, `desc()` assigns 491 | # the text to a variable with the format `$___desc_`. 492 | # 493 | # When the `--get` option is used, the description for is printed, if 494 | # one has been set. 495 | # 496 | # NOTE: 497 | # 498 | # The `read` form of assignment is used for a balance of ease of 499 | # implementation and simplicity. There is an alternative assignment form 500 | # that could be used here: 501 | # 502 | # var="$(cat <<'HEREDOC' 503 | # some message 504 | # HEREDOC 505 | # ) 506 | # 507 | # However, this form appears to require trailing space after backslases to 508 | # preserve newlines, which is unexpected. Using `read` simply requires 509 | # escaping backslashes, which is more common. 510 | desc() { 511 | _debug printf "desc() \${*}: %s\\n" "$@" 512 | [[ -z "${1:-}" ]] && _die printf "desc(): No command name specified.\\n" 513 | 514 | if [[ "${1}" == "--get" ]] 515 | then # get ------------------------------------------------------------------ 516 | [[ -z "${2:-}" ]] && _die printf "desc(): No command name specified.\\n" 517 | 518 | local _name="${2:-}" 519 | local _desc_var="___desc_${_name}" 520 | 521 | if [[ -n "${!_desc_var:-}" ]] 522 | then 523 | printf "%s\\n" "${!_desc_var}" 524 | else 525 | printf "No additional information for \`%s\`\\n" "${_name}" 526 | fi 527 | else # set ------------------------------------------------------------------ 528 | if [[ -n "${2:-}" ]] 529 | then # argument is present 530 | read -r -d '' "___desc_${1}" <] 569 | 570 | Description: 571 | Display help information for ${_ME} or a specified command. 572 | HEREDOC 573 | help() { 574 | if [[ "${1:-}" ]] 575 | then 576 | desc --get "${1}" 577 | else 578 | cat < < 583 | \_/ |_.__/ \___/_/\_\\ 584 | 585 | A streamlined interface for VBoxManage, the VirtualBox command line tool. 586 | 587 | Version: ${_VERSION} 588 | 589 | Usage: 590 | ${_ME} commands [--raw] 591 | ${_ME} config [--path] 592 | ${_ME} forwarding add 593 | ${_ME} forwarding list 594 | ${_ME} forwarding delete 595 | ${_ME} (help [] | -h | --help) 596 | ${_ME} kill ( | ) 597 | ${_ME} list [running | status] 598 | ${_ME} manage [] 599 | ${_ME} pause ( | ) 600 | ${_ME} reset ( | ) 601 | ${_ME} resume ( | ) 602 | ${_ME} show ( | ) 603 | ${_ME} start ( | ) [--headless] 604 | ${_ME} status [( | ) [--long|-l]] 605 | ${_ME} stop ( | ) 606 | ${_ME} (version | --version) 607 | 608 | Global Options: 609 | -h --help Display this help information. 610 | --version Display version information. 611 | 612 | Help: 613 | ${_ME} help [] 614 | 615 | Home: 616 | https://github.com/xwmx/vbox 617 | 618 | $(commands) 619 | HEREDOC 620 | fi 621 | } 622 | 623 | # Command List ################################################################ 624 | 625 | 626 | desc "commands" < 710 | ${_ME} forwarding list 711 | ${_ME} forwarding delete 712 | 713 | Subcommands: 714 | add Add a new port forwarding rule. 715 | list List forwarding rules. 716 | delete Delete the specified rule. 717 | 718 | Description: 719 | Manage port forwarding. 720 | 721 | Example: 722 | ${_ME} forwarding add "ubuntu-vm" "tcp5000" "5000" 723 | 724 | Equivalent of: 725 | VBoxManage controlvm ubuntu-vm \\\\ 726 | natpf1 "tcp5000,tcp,127.0.0.1,5000,,5000" 727 | HEREDOC 728 | # VBoxManage controlvm fields: 729 | # 730 | # [--natpf<1-N> [],tcp|udp,[], 731 | # ,[],] 732 | forwarding() { 733 | local _subcommand= 734 | local _arguments=() 735 | 736 | for arg in "${_COMMAND_ARGV[@]:-}" 737 | do 738 | case "${arg}" in 739 | add|list|delete) 740 | if [[ -z "${_subcommand}" ]] 741 | then 742 | _subcommand="${arg}" 743 | else 744 | _arguments+=("${arg}") 745 | fi 746 | ;; 747 | *) 748 | _arguments+=("${arg}") 749 | ;; 750 | esac 751 | done 752 | 753 | _debug printf "forwarding() \${_subcommand}: %s\\n" "${_subcommand:-}" 754 | if [[ -z "${_subcommand}" ]] 755 | then 756 | ${_ME} help forwarding 757 | exit 1 758 | fi 759 | 760 | _vm="${_arguments[1]:-}" 761 | _debug printf "forwarding() \${_vm}: %s\\n" "${_vm}" 762 | if [[ -z "${_vm}" ]] 763 | then 764 | _die printf "VM name missing.\\n" 765 | fi 766 | 767 | case "${_subcommand}" in 768 | add) 769 | local _name="${_arguments[2]:-}" 770 | local _port="${_arguments[3]:-}" 771 | 772 | _debug printf \ 773 | "forwarding() \${_arguments[2]:-}: %s\\n" \ 774 | "${_arguments[2]:-}" 775 | _debug printf \ 776 | "forwarding() \${_arguments[3]:-}: %s\\n" \ 777 | "${_arguments[3]:-}" 778 | 779 | if [[ -z "${_name}" ]] 780 | then 781 | _die printf "Rule name missing.\\n" 782 | fi 783 | if [[ -z "${_port}" ]] 784 | then 785 | _die printf "Port missing.\\n" 786 | fi 787 | 788 | VBoxManage controlvm "${_vm}" \ 789 | natpf1 "${_name},tcp,127.0.0.1,${_port},,${_port}" 790 | ;; 791 | list) 792 | VBoxManage showvminfo "${_vm}" | awk '/NIC/ && /Rule/ {print}' 793 | ;; 794 | delete) 795 | local _name="${_arguments[2]:-}" 796 | if [[ -z "${_name}" ]] 797 | then 798 | _die printf "Rule name missing.\\n" 799 | fi 800 | VBoxManage modifyvm "${_vm}" --natpf1 delete "${_name}" 801 | ;; 802 | esac 803 | } 804 | 805 | # ------------------------------------------------------------------------ kill 806 | 807 | desc "kill" < | ) 810 | 811 | Description: 812 | Command: \`VBoxManage controlvm poweroff\` 813 | 814 | Has the same effect on a virtual machine as pulling the power cable on a real 815 | computer. The state of the VM is not saved beforehand, and data may be lost. 816 | (This is equivalent to selecting the "Close" item in the "Machine" menu of 817 | the GUI or pressing the window's close button, and then selecting "Power off 818 | the machine" in the dialog.) 819 | HEREDOC 820 | kill() { 821 | local id="${1:-}" 822 | if [[ -z "${id}" ]] 823 | then 824 | _die printf "Name or UUID not provided.\\n" 825 | fi 826 | VBoxManage controlvm "${id}" poweroff 827 | } 828 | 829 | # ------------------------------------------------------------------------ list 830 | 831 | desc "list" <] 866 | 867 | Description: 868 | Alias for \`VBoxManage\`. 869 | 870 | VBoxManage Documentation: 871 | https://www.virtualbox.org/manual/ch08.html 872 | HEREDOC 873 | manage() { 874 | if [[ -z "${_COMMAND_PARAMETERS[*]:-}" ]] 875 | then 876 | VBoxManage 877 | else 878 | VBoxManage "${_COMMAND_PARAMETERS[@]:-}" 879 | fi 880 | } 881 | 882 | # ----------------------------------------------------------------------- pause 883 | 884 | desc "pause" < | ) 887 | 888 | Description: 889 | Command: \`VBoxManage controlvm pause\` 890 | 891 | Temporarily puts a virtual machine on hold, without changing its state for 892 | good. The VM window will be painted in gray to indicate that the VM is 893 | currently paused. (This is equivalent to selecting the "Pause" item in the 894 | "Machine" menu of the GUI.) 895 | HEREDOC 896 | pause() { 897 | local id="${1:-}" 898 | if [[ -z "${id}" ]] 899 | then 900 | _die printf "Name or UUID not provided.\\n" 901 | fi 902 | VBoxManage controlvm "${id}" pause 903 | } 904 | 905 | # ----------------------------------------------------------------------- reset 906 | 907 | desc "reset" < | ) 910 | 911 | Description: 912 | Command: \`VBoxManage controlvm reset\` 913 | 914 | Has the same effect on a virtual machine as pressing the "Reset" button on a 915 | real computer: a cold reboot of the virtual machine, which will restart and 916 | boot the guest operating system again immediately. The state of the VM is not 917 | saved beforehand, and data may be lost. (This is equivalent to selecting the 918 | "Reset" item in the "Machine" menu of the GUI.) 919 | HEREDOC 920 | reset() { 921 | local id="${1:-}" 922 | if [[ -z "${id}" ]] 923 | then 924 | _die printf "Name or UUID not provided.\\n" 925 | fi 926 | VBoxManage controlvm "${id}" reset 927 | } 928 | 929 | # ---------------------------------------------------------------------- resume 930 | 931 | desc "resume" < | ) 934 | 935 | Description: 936 | Command: \`VBoxManage controlvm resume\` 937 | 938 | Undo a previous pause command. (This is equivalent to selecting the "Resume" 939 | item in the "Machine" menu of the GUI.) 940 | HEREDOC 941 | resume() { 942 | local id="${1:-}" 943 | if [[ -z "${id}" ]] 944 | then 945 | _die printf "Name or UUID not provided.\\n" 946 | fi 947 | VBoxManage controlvm "${id}" resume 948 | } 949 | 950 | # ------------------------------------------------------------------------ show 951 | 952 | desc "show" < | ) 955 | 956 | Description: 957 | Command: \`VBoxManage showvminfo \` 958 | 959 | Show information about a particular virtual machine. 960 | HEREDOC 961 | show() { 962 | local id="${1:-}" 963 | if [[ -z "${id}" ]] 964 | then 965 | _die printf "Name or UUID not provided.\\n" 966 | fi 967 | VBoxManage showvminfo "${id}" 968 | } 969 | 970 | # ----------------------------------------------------------------------- start 971 | 972 | desc "start" < | ) [--headless] 975 | 976 | Description: 977 | Start the VM with the given name or UUID. 978 | HEREDOC 979 | start() { 980 | local headless= 981 | local id= 982 | 983 | for arg in "${_COMMAND_ARGV[@]:-}" 984 | do 985 | case "${arg}" in 986 | --headless) headless=1;; 987 | *) id="${arg}";; 988 | esac 989 | done 990 | 991 | if [[ -z "${id}" ]] 992 | then 993 | _die printf "Name or UUID not provided.\\n" 994 | fi 995 | 996 | if [[ -n "${headless}" ]] 997 | then 998 | VBoxManage startvm "${id}" --type headless 999 | else 1000 | VBoxManage startvm "${id}" 1001 | fi 1002 | } 1003 | 1004 | # ---------------------------------------------------------------------- status 1005 | 1006 | desc "status" < | ) [--long|-l]] 1009 | 1010 | Options: 1011 | -l --long Display long-form status information for the specified VM. 1012 | 1013 | Description: 1014 | When no argument has been passed, this acts as an alias for 1015 | \`${_ME} list status\` and displays the status for all of the VMs. When 1016 | passed a VM name or UUID, the status aka state of that VM is displayed. 1017 | HEREDOC 1018 | status() { 1019 | local id= 1020 | local status_string= 1021 | local long=0 1022 | 1023 | for arg in "${_COMMAND_ARGV[@]:1}" 1024 | do 1025 | case "${arg}" in 1026 | -l|--long) long=1;; 1027 | *) [[ -z "${id}" ]] && id="${arg}";; 1028 | esac 1029 | done 1030 | 1031 | if [[ -n "${id}" ]] 1032 | then 1033 | if ((long)) 1034 | then 1035 | status_string="$( 1036 | ${_ME} show "${id}" \ 1037 | | awk '/State:/ {for (i=2; i | ) 1061 | 1062 | Description: 1063 | Command: \`VBoxManage controlvm savestate\` 1064 | 1065 | Save the current state of the VM to disk and then stop the VM. (This is 1066 | equivalent to selecting the "Close" item in the "Machine" menu of the GUI or 1067 | pressing the window's close button, and then selecting "Save the machine 1068 | state" in the dialog.) 1069 | HEREDOC 1070 | stop() { 1071 | local id="${1:-}" 1072 | if [[ -z "${id}" ]] 1073 | then 1074 | _die printf "Name or UUID not provided.\\n" 1075 | fi 1076 | VBoxManage controlvm "${id}" savestate 1077 | } 1078 | 1079 | desc "halt" <