├── LICENSE ├── README.md ├── my-alternatives-debian └── my-alternatives-redhat /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 TekWizely 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # My-Alternatives
(hacking update-alternatives to make local changes) 2 | [![MIT license](https://img.shields.io/badge/License-MIT-green.svg)](https://github.com/tekwizely/pre-commit-golang/blob/master/LICENSE) 3 | 4 | My-Alternatives is a light-weight wrapper over _update-alternatives_, offering user-level customizations. 5 | 6 | Supports _Debian_, _SUSE_*, and _RedHat_ 7 | 8 | * for suse support, use the debian version 9 | 10 | ----------------------- 11 | ### Easy to Get Started 12 | 13 | With my-alternatives, configuring custom alternatives is as easy as: 14 | 15 | _home configuration example_ 16 | ```shell 17 | # initialize my-alternatives 18 | # note: place this in your .profile 19 | $ eval "$( my-alternatives init )" 20 | 21 | # customize an alternative 22 | $ my-alternatives select 23 | ``` 24 | 25 | Your selections will be saved into your `HOME` configuration, and made active in any login shell that performs the initialization routine. 26 | 27 | **NOTE:** You can save the initialization routine in your `.profile` 28 | 29 | ### Per-Shell Configuration 30 | 31 | If you want to make a temporary change that only modifies your current shell session, you can initialize a `TEMP` configuration: 32 | 33 | _temp configuration example_ 34 | ```shell 35 | # initialize a temporary configuration 36 | # note: your HOME configuration must already be initialized 37 | $ my-alternatives init-tmp 38 | 39 | # customize an alternative for just the current shell 40 | $ my-alternatives select 41 | ``` 42 | 43 | ### Auto-Import 44 | 45 | The first time you `select` an alternative, my-alternatives automatically imports the alternative group into your configuration. 46 | 47 | When the `HOME` configuration is the active configuration, my-alternatives imports the group from the system-level configuration. 48 | 49 | When a `TEMP` configuration is active, my-alternatives first tries to import from your `HOME` configuration, falling back onto the system-level configuration if the group is not present. 50 | 51 | ### Manual Import 52 | 53 | If you want to import an alternative group into your active configuration without selecting an alternative, you can use the `import` command: 54 | 55 | _import example_ 56 | ```shell 57 | $ my-alternatives import 58 | ``` 59 | ----------- 60 | ## Commands 61 | 62 | Below are tables of the available commands for each supported OS 63 | 64 | ### Debian / SUSE 65 | 66 | **NOTE:** _SUSE_ uses a [rebranded](https://build.opensuse.org/package/view_file/openSUSE:Leap:15.2/update-alternatives/update-alternatives-suse.patch?expand=1) version of _Debian's_ udpate-alternatives. 67 | 68 | Until such time as the feature set of the two versions diverges, this project will **just** maintain the _Debian_ vesion. 69 | 70 | #### My-Alternatives-Debian Commands 71 | 72 | Below is the list of custom commands that _my-alternatives-debian_ implements: 73 | 74 | | Command | Description 75 | |--------------------|------------ 76 | | `init`, `shellenv` | Prepare the current shell session for user-level alternatives. 77 | | `init-tmp`, `tmp` | Configure the current shell session for temporary (short-lived) changes. 78 | | `rm-tmp` | Remove the temporary configuration from the current shell session, making the `HOME` configuration active. 79 | | `select`, `config` | Select the active alternative for a group. This is equivalent to `update-alternatives --config` with the adition of the auto-import logic. 80 | | `import` | Import an alternative group into the current configuration. 81 | | `add` | Add an alternative to a group within the current configuration. This is equivalent to `update-alternatives --install` but has _slightly_ different syntax. see `my-alternatives help add` for details. 82 | | `version` | Display my-alternatives version number. 83 | 84 | **NOTE:** See `my-alternatives help ` to learn about a specific command, including additional options. 85 | 86 | #### Debian Update-Alternatives Commands 87 | 88 | Below is the list of commands that are implemented as pass-through to the related _Debian_ update-alternatives command: 89 | 90 | | My-Alternatives Command | Update-Alternatives Command 91 | |-------------------------|---------------------------- 92 | | `select`, `config` | `--config` 93 | | `select-all`, `--config-all` | `--all` 94 | | `rm-alt` | `--remove` 95 | | `rm-group`, `rm-grp` | `--remove-all` 96 | | `query` | `--query` 97 | | `display` | `--display` 98 | | `list` | `--list` 99 | | `set` | `--set` 100 | | `auto` | `--auto` 101 | | `get-selections` | `--get-selections` 102 | | `set-selections` | `--set-selections` 103 | | `ua-help` | `--help` 104 | | `ua-version` | `--version` 105 | 106 | My-Alternatives will set the `--admindir` and `--altdir` options to point to your active configuration. 107 | 108 | All additional command-line options are passed-through, unmodified. 109 | 110 | **NOTE:** See `update-alternatives --help` or `man update-alternatives` to learn more about the various commands and their options. 111 | 112 | ### RedHat 113 | 114 | #### hacking The system 115 | 116 | _RedHat_ has its own implementation of update-alternatives, which is slightly different from the _Debian_ version. 117 | 118 | One **major** difference is that it does NOT support the `--query` option, meaning that there's no means of determining an anternative's full configuraiton using _just_ the tool's public API. 119 | 120 | In order to support this version, we have to use knowledge of the system's _private_ API. Namely: 121 | 122 | * Defaulting the "admin" directory to `/var/lib/alternatives` 123 | * Assuming the name and format of files in the admin directory 124 | * Defaulting the "alt" directory to `/etc/alternatives` 125 | * Assuming the name and nature of files in the alt directory 126 | 127 | #### My-Alternatives-RedHat Commands 128 | 129 | Below is the list of custom commands that _my-alternatives-redhat_ implements: 130 | 131 | | Command | Description 132 | |--------------------|------------ 133 | | `init`, `shellenv` | Prepare the current shell session for user-level alternatives. 134 | | `init-tmp`, `tmp` | Configure the current shell session for temporary (short-lived) changes. 135 | | `rm-tmp` | Remove the temporary configuration from the current shell session, making the `HOME` configuration active. 136 | | `select`, `config` | Select the active alternative for a group. This is equivalent to `update-alternatives --config` with the adition of the auto-import logic. 137 | | `import` | Import an alternative group into the current configuration. 138 | | `add` | Add an alternative to a group within the current configuration. This is equivalent to `update-alternatives --install` but has _slightly_ different syntax. see `my-alternatives help add` for details. 139 | | `add-child` | Add an child to an existing alternative for a group within the current configuration. This is equivalent to `update-alternatives --add-slave` but has _slightly_ different syntax. see `my-alternatives help add-child` for details. 140 | | `version` | Display my-alternatives version number. 141 | 142 | **NOTE:** See `my-alternatives help ` to learn about a specific command, including additional options. 143 | 144 | #### RedHat Update-Alternatives Commands 145 | 146 | Below is the list of commands that are implemented as pass-through to the related _RedHat_ update-alternatives command: 147 | 148 | | My-Alternatives Command | Update-Alternatives Command 149 | |-------------------------|---------------------------- 150 | | `select`, `config` | `--config` 151 | | `add-child` | `--add-slave` 152 | | `rm-alt` | `--remove` 153 | | `rm-group`, `rm-grp` | `--remove-all` 154 | | `rm-child` | `--remove-slave` 155 | | `display` | `--display` 156 | | `list` | `--list` 157 | | `set` | `--set` 158 | | `auto` | `--auto` 159 | | `ua-help` | `--help` 160 | | `ua-version` | `--version` 161 | 162 | My-Alternatives will set the `--admindir` and `--altdir` options to point to your active configuration. 163 | 164 | All additional command-line options are passed-through, unmodified. 165 | 166 | **NOTE:** See `update-alternatives --help` or `man update-alternatives` to learn more about the various commands and their options. 167 | 168 | ### Invoking Update-Alternatives Directly 169 | 170 | If you need to pass a specific command to update-alternatives, you can do so using the `ua` command: 171 | 172 | _invoke update-alternatives directly_ 173 | ```shell 174 | $ my-alternatives ua --display pager 175 | ``` 176 | 177 | My-Alternatives will set the `--admindir` and `--altdir` options to point to your active configuration. 178 | 179 | All additional command-line options are passed-through, unmodified. 180 | 181 | **NOTE:** See `update-alternatives --help` or `man update-alternatives` to learn more about available commands and their options. 182 | 183 | ------------- 184 | ## Installing 185 | 186 | ### Releases 187 | 188 | See the [Releases](https://github.com/TekWizely/my-alternatives/releases) page for downloadable archives of versioned releases. 189 | 190 | ### Git 191 | 192 | ``` 193 | git clone git://github.com/TekWizely/my-alternatives.git 194 | ``` 195 | 196 | ### Renaming the Scripts 197 | 198 | Depending on how you acquire the files, the scripts may be named by their OS flavor, i.e: 199 | * Debian : `my-alternatives-debian` 200 | * RedHat : `my-alternatives-redhat` 201 | 202 | Feel free to rename them as desired. Personally, I rename the script **AND** set up a few convenient aliases: 203 | ```shell 204 | $ cp my-alternatives-debian ~/bin/my-alternatives 205 | $ alias ma="my-alternatives" 206 | $ alias mua="my-alternatives ua" 207 | ``` 208 | 209 | --------------- 210 | ## Contributing 211 | 212 | To contribute to My-Alternatives, follow these steps: 213 | 214 | 1. Fork this repository. 215 | 2. Create a branch: `git checkout -b `. 216 | 3. Make your changes and commit them: `git commit -m ''` 217 | 4. Push to the original branch: `git push origin /` 218 | 5. Create the pull request. 219 | 220 | Alternatively see the GitHub documentation on [creating a pull request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request). 221 | 222 | ---------- 223 | ## Contact 224 | 225 | If you want to contact me you can reach me at TekWizely@gmail.com. 226 | 227 | ---------- 228 | ## License 229 | 230 | The `tekwizely/my-alternatives` project is released under the [MIT](https://opensource.org/licenses/MIT) License. See `LICENSE` file. 231 | 232 | ---------------- 233 | ##### Misc Notes 234 | 235 | As of `v0.7.0`, my-alternatives has been split (and renamed) into two scripts: `my-alternatives-debian` and `my-alternatives-redhat`. 236 | 237 | As of `v0.6.0`, my-alternatives is a complete re-write. As much as possible, commands are implemented as pass-through to _update-alternatives_, pointing to your active configuration. 238 | 239 | My-Alternatives does not require _root / sudo_ privileges to use, as it creates and maintains user-owned configuration directories. 240 | 241 | **CAVEATS:** 242 | - Written in _Bash_ 243 | - Requires the following system tools: 244 | - `update-alternatives` 245 | - `readlink` (redhat version) 246 | - `mktemp` (debian version) 247 | - `umask` 248 | - `dirname` 249 | - `basename` 250 | - `manpath` _(optional)_ 251 | - Utilizes tmp files / directories 252 | - Sets `umask 077` for safety 253 | - Tested on: 254 | - Ubuntu 255 | - `Ubuntu 20.04.3 LTS` 256 | - `Debian update-alternatives version 1.19.7.` 257 | - `Bash 5.0.17(1)-release` 258 | - openSUSE 259 | - `openSUSE Leap 15.3` 260 | - `SUSE update-alternatives version 1.19.0.4.` 261 | - `Bash 4.4.23(1)-release` 262 | - CentOS 263 | - `CentOS Linux release 8.4.2105` 264 | - `alternatives version 1.13` 265 | - `GNU bash, version 4.4.19(1)-release` 266 | -------------------------------------------------------------------------------- /my-alternatives-debian: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | ####################################################################### 3 | # SPDX-License-Identifier: MIT 4 | # Copyright (c) 2021 TekWizely & co-authors 5 | # 6 | # Use of this source code is governed by the MIT license. 7 | # See the accompanying LICENSE file, if present, or visit: 8 | # https://opensource.org/licenses/MIT 9 | ####################################################################### 10 | VERSION="v0.7.0-debian" 11 | 12 | set -Eeuo pipefail 13 | umask 077 # We use tmp files. Try to protect the user 14 | 15 | function usage() { 16 | # Need to escape $ and \ 17 | cat <<-USAGE 18 | my-alternatives (${VERSION}) 19 | 20 | a wrapper for update-alternatives to enable user-level configuration. 21 | also supports temporary (per-shell) changes. 22 | 23 | usage: 24 | 25 | eval "\$( my-alternatives [--shell ] [alt_home] )" 26 | prepare the current shell session for user-level alternatives 27 | NOTE: place this in your .profile 28 | my-alternatives [tmp_dir] 29 | configure the current shell session for temporary (short-lived) changes 30 | my-alternatives 31 | remove the temporary configuration from the current shell session 32 | my-alternatives [option ...] 33 | invoke a command 34 | my-alternatives help 35 | learn more about a specific command 36 | 37 | custom commands: 38 | 39 | import 40 | import the alternative group into the current configuration 41 | when the default configuration is active, imports from system-level alternatives 42 | when a tmporary configuration is active, first tries the default configuration, then system-level alternatives 43 | add ... 44 | add an alternative to the group within the current configuration 45 | see 'help add' for usage details 46 | select | config 47 | select the active alternative for group 48 | will first invoke 'import ' if the group is not already available in the current configuration 49 | version 50 | display my-alternatives version number 51 | 52 | renamed pass-through commands: 53 | 54 | select-all | config-all 55 | invoke 'update-alternatives --all' 56 | rm-alt | rmalt 57 | invoke 'update-alternatives --remove' 58 | rm-group | rmgrp 59 | invoke 'update-alternatives --remove-all' 60 | 61 | these have been renamed to reduce ambiguity 62 | 63 | same-name pass-through commands: 64 | 65 | query | display | list | set | auto | get-selections | set-selections 66 | 67 | invoke 'update-alternatives --' 68 | 69 | update-alternatives helper commands: 70 | 71 | ua [option ...] 72 | invoke 'update-alternatives [option ...]' pointing to the currently-configured alternatives 73 | ua-help 74 | invoke 'update-alternatives --help' 75 | ua-version 76 | invoke 'update-alternatives --version' 77 | 78 | USAGE 79 | exit 2 80 | } 81 | 82 | function version() { 83 | printf "%s\n" "${VERSION}" 84 | } 85 | 86 | function main() { 87 | 88 | case "${1:-}" in 89 | -h | --help) 90 | usage 91 | ;; 92 | help) 93 | shift 94 | case "${1:-}" in 95 | init) 96 | help_init 97 | ;; 98 | shellenv) # alias 99 | help_alias "init" "$1" 100 | ;; 101 | init-tmp) 102 | help_init_tmp 103 | ;; 104 | init-temp | tmp | temp) # aliases 105 | help_alias "init-tmp" "$1" 106 | ;; 107 | rm-tmp) 108 | help_rm_tmp 109 | ;; 110 | rm-temp | rmtmp | rmtemp | remove-tmp | remove-temp | removetmp | removetemp) # aliases 111 | help_alias "rm-tmp" "$1" 112 | ;; 113 | import) 114 | help_import 115 | ;; 116 | add) 117 | help_add 118 | ;; 119 | select) 120 | help_select 121 | ;; 122 | config) # alias 123 | help_alias "select" "$1" 124 | ;; 125 | version) 126 | version 127 | ;; 128 | # 129 | # renamed update-alternatives pass-through 130 | # 131 | select-all | config-all) 132 | help_passthrough "all" "$1" 133 | ;; 134 | rm-alt | rmalt | remove-alt | removealt) 135 | help_passthrough "remove" "$1" 136 | ;; 137 | rm-group | rmgroup | rm-grp | rmgrp | remove-group | removegroup | remove-grp | removegrp) 138 | help_passthrough "remove-all" "$1" 139 | ;; 140 | # 141 | # update-alternatives pass-through 142 | # 143 | query | display | list | set | auto | get-selections | set-selections) 144 | help_passthrough "$1" 145 | ;; 146 | # 147 | # update-alternatives helpers 148 | # 149 | ua) 150 | help_update_alternatives 151 | ;; 152 | ua-help) 153 | help_update_alternatives_help 154 | ;; 155 | ua-version) 156 | help_update_alternatives_version 157 | ;; 158 | *) 159 | if [ -z "${1:-}" ]; then 160 | printf "usage: help \n" >&2 161 | else 162 | printf "error: unrecognized command: %s\n" "${1}" >&2 163 | fi 164 | printf "\nsee 'my-alternatives --help' for list of commands\n" >&2 165 | exit 2 166 | ;; 167 | esac 168 | ;; 169 | init | shellenv) 170 | shift 171 | do_init "$@" 172 | ;; 173 | init-tmp | init-temp | tmp | temp) 174 | shift 175 | do_init_tmp "$@" 176 | ;; 177 | rm-tmp | rm-temp | rmtmp | rmtemp | remove-tmp | remove-temp | removetmp | removetemp) 178 | shift 179 | do_rm_tmp "$@" 180 | ;; 181 | import) 182 | shift 183 | do_import "$@" 184 | ;; 185 | add) 186 | shift 187 | do_add "$@" 188 | ;; 189 | select | config) 190 | shift 191 | do_select "$@" 192 | ;; 193 | version) 194 | version 195 | ;; 196 | # 197 | # renamed update-alternatives pass-through 198 | # 199 | select-all | config-all) 200 | shift 201 | do_update_alternatives "--all" "$@" 202 | ;; 203 | rm-alt | rmalt | remove-alt | removealt) 204 | shift 205 | do_update_alternatives "--remove" "$@" 206 | ;; 207 | rm-group | rmgroup | rm-grp | rmgrp | remove-group | removegroup | remove-grp | removegrp) 208 | shift 209 | do_update_alternatives "--remove-all" "$@" 210 | ;; 211 | # 212 | # update-alternatives pass-through 213 | # 214 | query | display | list | set | auto | get-selections | set-selections) 215 | local cmd="${1}" 216 | shift 217 | do_update_alternatives "--${cmd}" "$@" 218 | ;; 219 | # 220 | # update-alternatives helpers 221 | # 222 | ua) 223 | shift 224 | do_update_alternatives "$@" 225 | ;; 226 | ua-help) 227 | shift 228 | command update-alternatives --help "$@" 229 | ;; 230 | ua-version) 231 | shift 232 | command update-alternatives --version "$@" 233 | ;; 234 | *) 235 | if [ -z "${1:-}" ]; then 236 | printf "usage: my-alternatives [option ...]\n" >&2 237 | else 238 | printf "error: unrecognized command: %s\n" "${1}" >&2 239 | fi 240 | printf "\nsee 'my-alternatives --help' for list of commands\n" >&2 241 | exit 2 242 | ;; 243 | esac 244 | } 245 | 246 | function help_init() { 247 | cat <<-'USAGE' 248 | usage: eval "$( my-alternatives [--shell ] [alt_home] )" 249 | 250 | prepares the current shell session for user-level alternatives. 251 | 252 | if [alt_home] is provided, it will be configured as MY_ALTS_HOME 253 | 254 | when [alt_home] is NOT provided, the following logic will be used determine MY_ALTS_HOME: 255 | 256 | * check if $MY_ALTS_HOME already defined 257 | * check $XDG_CONFIG_HOME. if present, use $XDG_CONFIG_HOME/my-alternatives 258 | * finally default to ~/.config/my-alternatives 259 | 260 | when [--shell ] is NOT provided, checks $SHELL. 261 | 262 | NOTE: Currently only 'bash' is supported. 263 | 264 | creates the MY_ALTS_HOME directory and required sub-directories if they don't already exist. 265 | 266 | finally, configures PATH and MANPATH. 267 | 268 | example: 269 | 270 | # prepare shell with default MY_ALTS_HOME logic 271 | # NOTE: you can place this in your .profile 272 | $ eval "$( command my-alternatives init )" 273 | 274 | # = ~/.config/my-alternatives 275 | export MY_ALTS_HOME="" 276 | export PATH="/bin${PATH:+:$PATH}" 277 | export MANPATH="/man:${MANPATH:-}" 278 | 279 | more info: 280 | 281 | see 'my-alternatives --help' to learn more about configuring your own alternatives. 282 | 283 | see 'update-alternatives --help' or 'man update-alternatives' to learn more about alternatives. 284 | 285 | USAGE 286 | exit 2 287 | } 288 | 289 | function do_init() { 290 | local alt_home shell 291 | while (($#)); do 292 | case "${1}" in 293 | --shell) 294 | if [ -z "${2:-}" ]; then 295 | printf "my-alternatives: error: no value provided for --shell option. see 'my-alternatives help init' for usage\n" >&2 296 | exit 2 297 | fi 298 | # basename as a courtesy in case they use $SHELL/etc 299 | # 300 | shell="$(basename "${2}")" 301 | shift 2 302 | ;; 303 | *) 304 | # If alt_home already defined 305 | # 306 | if [ -n "${alt_home:-}" ]; then 307 | printf "my-alternatives: error: unrecognized option: %s. see 'my-alternatives help init' for usage\n" "${1}" >&2 308 | exit 2 309 | fi 310 | alt_home="${1}" 311 | shift 312 | ;; 313 | esac 314 | done 315 | # If --shell not provided, check $SHELL 316 | # 317 | if [ -z "${shell:-}" ]; then 318 | shell="$(basename "${SHELL:-}")" 319 | if [ -z "${shell}" ]; then 320 | printf "my-alternatives: error: Unable to determine shell. see 'my-alternatives help init' for usage\n" >&2 321 | exit 2 322 | fi 323 | fi 324 | # Confirm shell supported 325 | # 326 | if [[ "${shell}" != "bash" ]]; then 327 | printf "my-alternatives: error: unrecognized shell: %s. see 'my-alternatives help init' for usage\n" "${shell}" >&2 328 | exit 2 329 | fi 330 | # 331 | # set from specified values or default logic 332 | # 333 | MY_ALTS_HOME="${alt_home:-${MY_ALTS_HOME:-${XDG_CONFIG_HOME:-${HOME:?}/.config}/my-alternatives}}" 334 | # print assignments seperately as I don't want to escape ALL the other $ :) 335 | # 336 | printf '_MY_ALTS_HOME="%s"\n' "${MY_ALTS_HOME:?}" 337 | # path clean up adapted from: https://stackoverflow.com/a/2108540/1822537 338 | # 339 | cat <<-'INIT' 340 | if [ -n "${_MY_ALTS_HOME}:-" ] && command mkdir -p -- "${_MY_ALTS_HOME}"; then 341 | command mkdir -p -- "${_MY_ALTS_HOME}/bin" 342 | command mkdir -p -- "${_MY_ALTS_HOME}/man" 343 | command mkdir -p -- "${_MY_ALTS_HOME}/man/man"{1..9} 344 | command mkdir -p -- "${_MY_ALTS_HOME}/admin" 345 | command mkdir -p -- "${_MY_ALTS_HOME}/alts" 346 | # clean up PATH and add to front 347 | PATH=":${PATH:-}:" 348 | PATH="${PATH/":${_MY_ALTS_HOME}/bin:"/:}" 349 | PATH="${PATH%:}" 350 | PATH="${PATH#:}" 351 | PATH="${_MY_ALTS_HOME}/bin${PATH:+:$PATH}" 352 | # clean up MANPATH and add to front 353 | MANPATH=":${MANPATH:-}:" 354 | MANPATH="${MANPATH/":${_MY_ALTS_HOME}/man:"/:}" 355 | MANPATH="${MANPATH%:}" 356 | MANPATH="${MANPATH#:}" 357 | MANPATH="${_MY_ALTS_HOME}/man:${MANPATH:-}" 358 | # add helper function 359 | my-alternatives() { 360 | case "${1:-}" in 361 | # some commands require eval 362 | init | shellenv | \ 363 | init-tmp | init-temp | tmp | temp | \ 364 | rm-tmp | rm-temp | rmtmp | rmtemp | remove-tmp | remove-temp | removetmp | removetemp) 365 | eval "$( command my-alternatives "$@" )" 366 | ;; 367 | *) 368 | command my-alternatives "$@" 369 | ;; 370 | esac 371 | } 372 | # finally, export variables 373 | command export MY_ALTS_HOME="${_MY_ALTS_HOME}" 374 | fi 375 | command unset _MY_ALTS_HOME 376 | INIT 377 | } 378 | 379 | function help_init_tmp() { 380 | cat <<-'USAGE' 381 | usage: my-alternatives [tmp_dir] 382 | 383 | configures the current shell session for temporary (short-lived) changes. 384 | 385 | if [tmp_dir] is provided, it will be configured as MY_ALTS_TMP 386 | 387 | when [tmp_dir] is NOT provided, the following logic will be used determine MY_ALTS_TMP: 388 | 389 | * check if $MY_ALTS_TMP already defined 390 | * check XDG_CACHE_HOME. if present, use XDG_CACHE_HOME/my-alternatives/ 391 | * finally default to ~/.config/my-alternatives/ 392 | 393 | creates the MY_ALTS_TMP directory and required sub-directories if they don't already exist. 394 | 395 | finally, configures PATH and MANPATH. 396 | 397 | example: 398 | 399 | $ my-alternatives init-tmp 400 | 401 | # = ~/.config/my-alternatives/abcd123 402 | export MY_ALTS_TMP="" 403 | export PATH="/bin${PATH:+:$PATH}" 404 | export MANPATH="/man:${MANPATH:-}" 405 | 406 | more info: 407 | 408 | see 'my-alternatives --help' to learn more about configuring your own alternatives. 409 | 410 | see 'update-alternatives --help' or 'man update-alternatives' to learn more about alternatives. 411 | 412 | USAGE 413 | exit 2 414 | } 415 | 416 | function do_init_tmp() { 417 | # Ensure alt_home configured 418 | # 419 | if [ -z "${MY_ALTS_HOME:-}" ]; then 420 | printf "my-alternatives: error: shell session not confgured. see 'my-alternatives --help' for usage\n" >&2 421 | exit 2 422 | fi 423 | local alt_tmp 424 | while (($#)); do 425 | case "${1}" in 426 | *) 427 | # If alt_root already defined 428 | # 429 | if [ -n "${alt_tmp:-}" ]; then 430 | printf "my-alternatives: error: unrecognized option: %s. see 'my-alternatives help init-tmp' for usage\n" "${1}" >&2 431 | exit 2 432 | fi 433 | alt_tmp="${1}" 434 | shift 435 | ;; 436 | esac 437 | done 438 | # create temp folder if no alt_tmp or MY_ALTS_TMP given 439 | # 440 | MY_ALTS_TMP="${alt_tmp:-${MY_ALTS_TMP:-}}" 441 | if [ -z "${MY_ALTS_TMP}" ]; then 442 | while :; do 443 | MY_ALTS_TMP="${XDG_CACHE_HOME:-"${HOME:?}/.cache"}/my-alternatives/${RANDOM}${RANDOM}" 444 | [ -d "${MY_ALTS_TMP}" ] || break 445 | done 446 | fi 447 | # print assignment seperately as I don't want to escape ALL the other $ :) 448 | # 449 | printf '_MY_ALTS_TMP="%s"\n' "${MY_ALTS_TMP:?}" 450 | cat <<-'TMP' 451 | if [ -n "${_MY_ALTS_TMP}:-" ] && command mkdir -p -- "${_MY_ALTS_TMP}"; then 452 | command mkdir -p -- "${_MY_ALTS_TMP}/bin" 453 | command mkdir -p -- "${_MY_ALTS_TMP}/man" 454 | command mkdir -p -- "${_MY_ALTS_TMP}/man/man"{1..9} 455 | command mkdir -p -- "${_MY_ALTS_TMP}/admin" 456 | command mkdir -p -- "${_MY_ALTS_TMP}/alts" 457 | # clean up PATH and add to front 458 | PATH=":${PATH:-}:" 459 | PATH="${PATH/":${_MY_ALTS_TMP}/bin:"/:}" 460 | PATH="${PATH%:}" 461 | PATH="${PATH#:}" 462 | PATH="${_MY_ALTS_TMP}/bin${PATH:+:$PATH}" 463 | # clean up MANPATH and add to front 464 | MANPATH=":${MANPATH:-}:" 465 | MANPATH="${MANPATH/":${_MY_ALTS_TMP}/man:"/:}" 466 | MANPATH="${MANPATH%:}" 467 | MANPATH="${MANPATH#:}" 468 | MANPATH="${_MY_ALTS_TMP}/man:${MANPATH:-}" 469 | # finally, export tmp variable 470 | command export MY_ALTS_TMP="${_MY_ALTS_TMP}" 471 | fi 472 | command unset _MY_ALTS_TMP 473 | TMP 474 | } 475 | 476 | function help_rm_tmp() { 477 | cat <<-'USAGE' 478 | usage: my-alternatives rm-tmp 479 | 480 | removes the temporary configuration from the current shell session. 481 | 482 | restores the curent session to the default user-level alternatives. 483 | 484 | removes the temporary directory referenced by MY_ALTS_TMP 485 | 486 | removes MY_ALTS_TMP from both PATH and MANPATH 487 | 488 | finall, unsets the MY_ALTS_TMP variable 489 | 490 | more info: 491 | 492 | see 'my-alternatives --help' to learn more about creating a temporary configuration. 493 | 494 | see 'update-alternatives --help' or 'man update-alternatives' to learn more about alternatives. 495 | 496 | USAGE 497 | exit 2 498 | } 499 | 500 | function do_rm_tmp() { 501 | # Ensure alt_home configured 502 | # 503 | if [ -z "${MY_ALTS_HOME:-}" ]; then 504 | printf "my-alternatives: error: shell session not confgured. see 'my-alternatives --help' for usage\n" >&2 505 | exit 2 506 | fi 507 | # No further arguments expected 508 | # 509 | if [[ $# -gt 0 ]]; then 510 | printf "my-alternatives: error: unrecognized option: %s. see 'my-alternatives help rm-tmp' for usage\n" "${1}" >&2 511 | exit 2 512 | fi 513 | cat <<-'RMTMP' 514 | if [ -n "${MY_ALTS_TMP:-}" ]; then 515 | # clean up temp dir 516 | if [ -d "${MY_ALTS_TMP}" ]; then 517 | command rm -rf "${MY_ALTS_TMP}/" 518 | fi 519 | # clean up PATH 520 | PATH=":${PATH:-}:" 521 | PATH="${PATH/":${MY_ALTS_TMP}/bin:"/:}" 522 | PATH="${PATH%:}" 523 | PATH="${PATH#:}" 524 | # clean up MANPATH 525 | MANPATH=":${MANPATH:-}:" 526 | MANPATH="${MANPATH/":${MY_ALTS_TMP}/man:"/:}" 527 | MANPATH="${MANPATH%:}" 528 | MANPATH="${MANPATH#:}" 529 | fi 530 | # finally, clean up tmp variable 531 | command unset MY_ALTS_TMP 532 | RMTMP 533 | } 534 | 535 | function help_import() { 536 | cat <<-'USAGE' 537 | usage: my-alternatives import 538 | 539 | imports an alternative group into the currently-configured alternatives. 540 | 541 | when the default alternatives (ie. MY_ALTS_HOME) are active: 542 | * tries to import from the system-level alternatives 543 | 544 | when temporary alternatives (i.e MY_ALTS_TMP) are active: 545 | * first tries to import from the default alternatives 546 | * finally tries to import from the system-level alternatives 547 | 548 | more info: 549 | 550 | see 'my-alternatives --help' to learn more about configuring your own alternatives. 551 | 552 | see 'update-alternatives --help' or 'man update-alternatives' to learn more about alternatives. 553 | 554 | USAGE 555 | exit 2 556 | } 557 | 558 | function do_import() { 559 | # Ensure alt_home configured 560 | # 561 | if [ -z "${MY_ALTS_HOME:-}" ]; then 562 | printf "my-alternatives: error: shell session not confgured. see 'my-alternatives --help' for usage\n" >&2 563 | exit 2 564 | fi 565 | local alt_root="${MY_ALTS_TMP:-${MY_ALTS_HOME}}" 566 | local name 567 | while (($#)); do 568 | case "${1}" in 569 | *) 570 | # If name already defined 571 | # 572 | if [ -n "${name:-}" ]; then 573 | printf "my-alternatives: error: unrecognized option: %s. see 'my-alternatives help import' for usage\n" "${1}" >&2 574 | exit 2 575 | fi 576 | name="${1}" 577 | shift 578 | ;; 579 | esac 580 | done 581 | # No name argument 582 | # 583 | if [[ -z "${name:-}" ]]; then 584 | printf "my-alternatives: error: no name provided. see 'my-alternatives help import' for usage\n" >&2 585 | exit 1 586 | fi 587 | local from # for status message 588 | local data_file 589 | # Create temp file 590 | # 591 | if ! data_file="$(command mktemp -q -t "my-alts-${name}-query.XXXXXXXX")"; then 592 | printf "my-alternatives: error: unable to create temp file\n" >&2 593 | exit 2 594 | fi 595 | # sanity check 596 | if [ ! -f "${data_file}" ]; then 597 | printf "my-alternatives: error: unable to create temp file\n" >&2 598 | exit 2 599 | fi 600 | # If tmp is active try import from home 601 | # 602 | if [ "${alt_root}" = "${MY_ALTS_TMP:-}" ]; then 603 | # does NOT exit on error - checks data_file for success 604 | command update-alternatives \ 605 | --altdir "${MY_ALTS_HOME}/alts" --admindir "${MY_ALTS_HOME}/admin" \ 606 | --query "${name}" \ 607 | >"${data_file}" 2>/dev/null || true 608 | 609 | if [ -s "${data_file}" ]; then 610 | from="MY_ALTS_HOME: ${MY_ALTS_HOME}" # for status message 611 | fi 612 | fi 613 | if [ -z "${from:-}" ]; then 614 | # final fallback = update-alternatives default 615 | # exits on error 616 | command update-alternatives --query "${name}" >"${data_file}" 617 | 618 | if [ -s "${data_file}" ]; then 619 | from="system-level alternative" # for status message 620 | fi 621 | fi 622 | # final sanity check 623 | # 624 | if [ -z "${from:-}" ]; then 625 | printf "my-alternatives: error: unable to locate alternative group with name %s\n" "${name}" >&2 626 | rm "${data_file}" 627 | exit 2 628 | fi 629 | 630 | alt_parse_query "${data_file}" || return $? 631 | rm "${data_file}" 632 | 633 | if [ -z "${ALT_GROUP_LINK}" ]; then 634 | printf "my-alternatives: error: unable to determine provided location\n" >&2 635 | exit 2 636 | fi 637 | 638 | printf "importing alternative group %s%s from %s\n" "${ALT_GROUP_NAME}" "${ALT_GROUP_STATUS:+" in ${ALT_GROUP_STATUS} mode"}" "${from}" 639 | 640 | local alt_group_link_rel 641 | alt_group_link_rel="$(get_my_alt_rel_path "${ALT_GROUP_LINK}")" 642 | if [ "${alt_group_link_rel}" == "${ALT_GROUP_LINK}" ]; then 643 | printf "my-alternatives: error: unable to determine relative path for primary link: %s\n" "${ALT_GROUP_LINK}" >&2 644 | exit 2 645 | fi 646 | ALT_GROUP_LINK="${alt_root}${alt_group_link_rel}" 647 | local a=0 648 | while [[ $a -lt ${#ALT_GROUP_VALUES[@]} ]]; do 649 | local alt_value="${ALT_GROUP_VALUES[$a]}" 650 | local alt_priority="${ALT_GROUP_PRIORITIES[$a]}" 651 | local child_args=() 652 | local s=0 653 | while [[ $s -lt ${#ALT_GROUP_SLAVE_NAMES[@]} ]]; do 654 | local child_name="${ALT_GROUP_SLAVE_NAMES[$s]}" 655 | local child_link="${ALT_GROUP_SLAVE_LINKS[$s]}" 656 | local child_link_rel 657 | child_link_rel="$(get_my_alt_rel_path "${child_link}")" 658 | if [ "${child_link_rel}" == "${child_link}" ]; then 659 | printf " !!! warning: Unable to determine type (bin/man/etc) for %s\n" "${child_name} @ ${child_link} - skipping" >&2 660 | else 661 | child_link="${alt_root}${child_link_rel}" 662 | local child_value_ref="ALT_GROUP_VALUE_${a}_SLAVE_${s}_VALUE" 663 | local child_value="${!child_value_ref}" 664 | # Did we find a child value? 665 | # 666 | if [[ -n "${child_value}" ]]; then 667 | child_args+=("--slave" "${child_link}" "${child_name}" "${child_value}") 668 | else 669 | printf " !!! warning: Unable to determine type (bin/man/etc) for %s\n" "${child_name} @ ${child_link} - skipping" >&2 670 | fi 671 | fi 672 | ((s += 1)) 673 | done 674 | 675 | printf " ... importing alternative %s with priority %s\n" "${alt_value}" "${alt_priority}" 676 | command update-alternatives \ 677 | --altdir "${alt_root}/alts" --admindir "${alt_root}/admin" \ 678 | --install "${ALT_GROUP_LINK}" "${ALT_GROUP_NAME}" "${alt_value}" "${alt_priority}" \ 679 | "${child_args[@]}" \ 680 | >/dev/null || return $? 681 | 682 | ((a += 1)) 683 | done 684 | # Set current value if manual mode 685 | # 686 | if [ "${ALT_GROUP_STATUS}" = "manual" ] && [ ! "${ALT_GROUP_CURRENT_VALUE:-none}" = "none" ]; then 687 | printf " ... marking %s as active alternative\n" "${ALT_GROUP_CURRENT_VALUE}" 688 | command update-alternatives \ 689 | --altdir "${alt_root}/alts" --admindir "${alt_root}/admin" \ 690 | --set "${ALT_GROUP_NAME}" "${ALT_GROUP_CURRENT_VALUE}" \ 691 | >/dev/null || return $? 692 | fi 693 | # finished 694 | printf "\n" 695 | } 696 | 697 | function help_add() { 698 | cat <<-'USAGE' 699 | usage: my-alternatives add [--child ]... 700 | 701 | adds an alternative to a group within the currently-configured alternatives. 702 | The group is created if it does not already exist. 703 | 704 | NOTE: This is generally a pass-through to 'update-alternatives --install', BUT the argument syntax is slightly diffrent: 705 | 706 | * the order of and arguments are inverted 707 | * uses --child (vs --slave) for secondary files 708 | * arguments should be relative 709 | ex: bin/pager 710 | ex: man/man1/pager.1.gz 711 | 712 | options: 713 | 714 | 715 | the name of the alternative group. 716 | used when referring to the group in other commands. 717 | the name must be unique within the currently-configured alternatives. 718 | ex: pager 719 | ex: pager.1.gz 720 | 721 | 722 | path provided by the group. 723 | path should be relative. 724 | ex: bin/pager 725 | ex: man/man1/pager.1.gz 726 | 727 | 728 | the path of the alternative being introduced. 729 | ex: /usr/bin/less 730 | ex: /usr/share/man/man1/less.1.gz 731 | 732 | 733 | priority of this alternative. 734 | alternatives with larger values have higher priority in automatic mode. 735 | 736 | --child 737 | adds a secondary entry. 738 | optional, but if given, all arguments are required. 739 | 740 | more info: 741 | 742 | see 'my-alternatives --help' to learn more about configuring your own alternatives. 743 | 744 | see 'update-alternatives --help' or 'man update-alternatives' to learn more about the '--install' command. 745 | 746 | USAGE 747 | exit 2 748 | } 749 | 750 | function do_add() { 751 | # Ensure alt_home configured 752 | # 753 | if [ -z "${MY_ALTS_HOME:-}" ]; then 754 | printf "my-alternatives: error: shell session not confgured. see 'my-alternatives --help' for usage\n" >&2 755 | exit 2 756 | fi 757 | local alt_root="${MY_ALTS_TMP:-${MY_ALTS_HOME}}" 758 | # Build update-alternatives --install args 759 | # 760 | local args=("--install") 761 | local providing_name providing_path 762 | # primary group-name 763 | # 764 | if [ -z "${1:-}" ]; then 765 | printf "my-alternatives: error: primary group-name not provided. see 'my-alternatives help add' for usage\n" >&2 766 | exit 1 767 | fi 768 | providing_name="${1}" 769 | shift 770 | # primary group-path 771 | # 772 | if [ -z "${1:-}" ]; then 773 | printf "my-alternatives: error: primary group-path not provided. see 'my-alternatives help add' for usage\n" >&2 774 | exit 1 775 | fi 776 | if [[ "${1}" =~ ^/(bin|man)/ ]]; then 777 | providing_path="${alt_root}${1}" 778 | elif [[ "${1}" =~ ^(bin|man)/ ]]; then 779 | providing_path="${alt_root}/${1}" 780 | else 781 | local group_path 782 | group_path="$(get_my_alt_rel_path "${1}")" 783 | if [ "${group_path}" == "${1}" ]; then 784 | printf "my-alternatives: error: unable to determine relative path for group-path: %s\n" "${group_path}" >&2 785 | exit 2 786 | fi 787 | providing_path="${alt_root}${group_path}" 788 | fi 789 | shift 790 | 791 | # add to args (inverted) 792 | args+=("${providing_path}" "${providing_name}") 793 | 794 | # primary alt-path 795 | # 796 | if [ -z "${1:-}" ]; then 797 | printf "my-alternatives: error: primary alt-path not provided. see 'my-alternatives help add' for usage\n" >&2 798 | exit 1 799 | fi 800 | args+=("${1}") 801 | shift 802 | # alt-priority 803 | # 804 | if [ -z "${1:-}" ]; then 805 | printf "my-alternatives: error: alt-priority not provided. see 'my-alternatives help install' for usage\n" >&2 806 | exit 1 807 | fi 808 | args+=("${1}") 809 | shift 810 | # children 811 | # 812 | while (($#)); do 813 | case "${1}" in 814 | --child) 815 | args+=("--slave") 816 | shift 817 | # secondary group-name 818 | # 819 | if [ -z "${1:-}" ]; then 820 | printf "my-alternatives: error: secondary group-name not provided. see 'my-alternatives help add' for usage\n" >&2 821 | exit 1 822 | fi 823 | providing_name="${1}" 824 | shift 825 | # secondary group-path 826 | # 827 | if [ -z "${1:-}" ]; then 828 | printf "my-alternatives: error: secondary group-path not provided. see 'my-alternatives help add' for usage\n" >&2 829 | exit 1 830 | fi 831 | if [[ "${1}" =~ ^/(bin|man)/ ]]; then 832 | providing_path="${alt_root}${1}" 833 | elif [[ "${1}" =~ ^(bin|man)/ ]]; then 834 | providing_path="${alt_root}/${1}" 835 | else 836 | local group_path 837 | group_path="$(get_my_alt_rel_path "${1}")" 838 | if [ "${group_path}" == "${1}" ]; then 839 | printf "my-alternatives: error: unable to determine relative path for group-path: %s\n" "${group_path}" >&2 840 | exit 2 841 | fi 842 | providing_path="${alt_root}${group_path}" 843 | fi 844 | shift 845 | 846 | # add to args (inverted) 847 | args+=("${providing_path}" "${providing_name}") 848 | 849 | # secondary alt-path 850 | # 851 | if [ -z "${1:-}" ]; then 852 | printf "my-alternatives: error: secondary alt-path not provided. see 'my-alternatives help add' for usage\n" >&2 853 | exit 1 854 | fi 855 | args+=("${1}") 856 | shift 857 | ;; 858 | *) 859 | printf "my-alternatives: error: unrecognized option: %s. see 'my-alternatives help add' for usage\n" "${1}" >&2 860 | exit 2 861 | ;; 862 | esac 863 | done 864 | 865 | command update-alternatives \ 866 | --altdir "${alt_root}/alts" --admindir "${alt_root}/admin" \ 867 | "${args[@]}" \ 868 | >/dev/null || return $? 869 | } 870 | 871 | function help_select() { 872 | cat <<-'USAGE' 873 | usage: my-alternatives 1082 | 1083 | shows alternatives for the group and asks the user to select which one to use. 1084 | 1085 | calls 'import ' if the group is not present in the currently-configured alternatives. 1086 | 1087 | more info: 1088 | 1089 | see 'my-alternatives --help' to learn more about the 'import' command. 1090 | 1091 | see 'update-alternatives --help' or 'man update-alternatives' to learn more about the '--config' command. 1092 | 1093 | USAGE 1094 | exit 2 1095 | } 1096 | 1097 | function do_select() { 1098 | # Ensure alt_home configured 1099 | # 1100 | if [ -z "${MY_ALTS_HOME:-}" ]; then 1101 | printf "my-alternatives: error: shell session not confgured. see 'my-alternatives --help' for usage\n" >&2 1102 | exit 2 1103 | fi 1104 | local alt_root="${MY_ALTS_TMP:-${MY_ALTS_HOME}}" 1105 | # No name argument 1106 | # 1107 | if [[ -z "${1:-}" ]]; then 1108 | printf "my-alternatives: error: no name provided. see 'my-alternatives help select' for usage\n" >&2 1109 | exit 1 1110 | fi 1111 | local name="${1}" 1112 | shift 1113 | # No further arguments expected 1114 | # 1115 | if [[ $# -gt 0 ]]; then 1116 | printf "my-alternatives: error: unrecognized option: %s. see 'my-alternatives help select' for usage\n" "${1}" >&2 1117 | exit 2 1118 | fi 1119 | # need to import? 1120 | # 1121 | if ! alt_group_exists "${name}"; then 1122 | do_import "${name}" || return $? 1123 | fi 1124 | command update-alternatives \ 1125 | --altdir "${alt_root}/alts" --admindir "${alt_root}/admin" \ 1126 | "--config" "${name}" 1127 | } 1128 | 1129 | ## 1130 | # help_alias 1131 | # Generates help text for the various command aliases 1132 | # 1133 | # $1 = command 1134 | # $2 = alias 1135 | # 1136 | function help_alias() { 1137 | # Need to escape $ and \ 1138 | cat <<-USAGE 1139 | ${2} is an alias for the '${1}' command. 1140 | 1141 | see 'my-alternatives help ${1}' for usage. 1142 | 1143 | USAGE 1144 | exit 2 1145 | } 1146 | 1147 | ## 1148 | # help_passthrough 1149 | # Generates help text for the various pass-through commands 1150 | # 1151 | # $1 = update-alternatives command 1152 | # $2 = my-alternatives command (optional) 1153 | # 1154 | function help_passthrough() { 1155 | # Need to escape $ and \ 1156 | cat <<-USAGE 1157 | usage: my-alternatives ${2:-${1}} [option ...] 1158 | 1159 | invokes update-alternatives --${1} pointing to the currently-configured alternatives. 1160 | 1161 | all options are passed through, unmodified. 1162 | 1163 | this is equivilent to: 1164 | 1165 | # ="\${MY_ALTS_TMP:-\${MY_ALTS_HOME}}" 1166 | update-alternatives --altdir "/alts" --admindir "/admin" --${1} [option ...] 1167 | 1168 | more info: 1169 | 1170 | see 'update-alternatives --help' or 'man update-alternatives' to learn more about the '--${1}' command. 1171 | 1172 | USAGE 1173 | exit 2 1174 | } 1175 | 1176 | function help_update_alternatives_help() { 1177 | cat <<-'USAGE' 1178 | usage: my-alternatives ua-help 1179 | 1180 | convenience command for invoking: 1181 | 1182 | update-alternatives --help 1183 | 1184 | USAGE 1185 | exit 2 1186 | } 1187 | 1188 | function help_update_alternatives_version() { 1189 | cat <<-'USAGE' 1190 | usage: my-alternatives ua-version 1191 | 1192 | convenience command for invoking: 1193 | 1194 | update-alternatives --version 1195 | 1196 | USAGE 1197 | exit 2 1198 | } 1199 | 1200 | function help_update_alternatives() { 1201 | cat <<-'USAGE' 1202 | usage: my-alternatives ua [option ...] 1203 | 1204 | invokes update-alternatives pointing to the currently-configured alternatives. 1205 | 1206 | all options are passed through, unmodified. 1207 | 1208 | this is equivilent to: 1209 | 1210 | # ="\${MY_ALTS_TMP:-\${MY_ALTS_HOME}}" 1211 | update-alternatives --altdir "/alts" --admindir "/admin" [option ...] 1212 | 1213 | more info: 1214 | 1215 | see 'my-alternatives --help' to learn more about configuring your own alternatives. 1216 | 1217 | see 'update-alternatives --help' or 'man update-alternatives' to learn more about alternatives. 1218 | 1219 | USAGE 1220 | exit 2 1221 | } 1222 | 1223 | function do_update_alternatives() { 1224 | # Ensure alt_home configured 1225 | # 1226 | if [ -z "${MY_ALTS_HOME:-}" ]; then 1227 | printf "my-alternatives: error: shell session not confgured. see 'my-alternatives --help' for usage\n" >&2 1228 | exit 2 1229 | fi 1230 | local alt_root="${MY_ALTS_TMP:-${MY_ALTS_HOME}}" 1231 | # Invoke update-alternatives 1232 | # 1233 | command update-alternatives \ 1234 | --altdir "${alt_root}/alts" \ 1235 | --admindir "${alt_root}/admin" \ 1236 | "$@" 1237 | } 1238 | 1239 | ## 1240 | # alt_group_exists 1241 | # Checks if a named alt group exists in the configured alt root 1242 | # NOTE: Assumes knowledge of the configuration's "admin" directory 1243 | # $1 = name 1244 | # 1245 | # returns 0 if exist 1 otherwise 1246 | # 1247 | function alt_group_exists() { 1248 | local alt_root="${MY_ALTS_TMP:-${MY_ALTS_HOME:?}}" 1249 | if [ -f "${alt_root}/admin/${1}" ]; then 1250 | return 0 1251 | fi 1252 | return 1 1253 | } 1254 | 1255 | ## 1256 | # is_dir_in_path 1257 | # Checks if the provided entry exists in the provided colon-separated list 1258 | # 1259 | # $1 = path ( or any colon-sepearated list ) 1260 | # $2 = dir ( entry to look for ) 1261 | # 1262 | # Returns 0 if found 1 otherwise 1263 | # 1264 | # exmaple: 1265 | # 1266 | # is_dir_in_path "${PATH}" /usr/local/bin && echo YES || echo NO 1267 | # 1268 | function is_dir_in_path() { 1269 | # 0 = success 1270 | # Most likely to be in middle, least likely to match full path 1271 | # 1272 | [[ "$1" == *":$2:"* || "$1" == "$2:"* || "$1" == *":$2" || "$1" == "$2" ]] && return 0 || return 1 1273 | } 1274 | 1275 | ## 1276 | # $1 = file (assumed to represent a file ie last element removed for path checks) 1277 | # 1278 | function get_my_alt_rel_path() { 1279 | local file path 1280 | if [ "${1}" == "" ]; then 1281 | printf "" 1282 | return 1283 | else 1284 | file="$(basename "$1")" 1285 | path="$(dirname "$1")" 1286 | if [ "${file}" == "" ] || [ "${path}" == "." ] || [ "${path}" == "/" ]; then 1287 | printf "%s" "$1" 1288 | return 1289 | fi 1290 | fi 1291 | # Check $PATH 1292 | # 1293 | if is_dir_in_path "${PATH}" "${path}"; then 1294 | printf "/bin/%s" "${file}" 1295 | return 1296 | fi 1297 | # Check manpath 1298 | # 1299 | if command -v manpath &>/dev/null; then 1300 | local _manpath 1301 | _manpath="$(command manpath 2>/dev/null)" 1302 | while [ "${path}" != "." ] && [ "${path}" != "/" ]; do 1303 | if is_dir_in_path "${_manpath}" "${path}"; then 1304 | printf "/man/%s" "${file}" 1305 | return 1306 | fi 1307 | file="$(command basename "${path}")/${file}" 1308 | path="$(command dirname "${path}")" 1309 | done 1310 | fi 1311 | # Unknown 1312 | # 1313 | printf "%s" "$1" 1314 | } 1315 | 1316 | # ############################################################################## 1317 | # BEGIN alternatives_parse_admin_redhat.bash v1.0.0 1318 | # ############################################################################## 1319 | 1320 | ## 1321 | # alt_parse_admin_redhat - Parses RedHat's update-alternatives admin files 1322 | # 1323 | # adapted from: 1324 | # 1325 | # https://github.com/fedora-sysv/chkconfig/blob/master/alternatives.c#L264 1326 | # 1327 | # $1 = file to read from (can be process sub) 1328 | # $2 = debug|trace 1329 | # 1330 | # example: 1331 | # 1332 | # alt_parse_admin_redhat <( cat /var/lib/alternatives/pager ) 1333 | # 1334 | function alt_parse_admin_redhat() { 1335 | # Reset global vars 1336 | # 1337 | ALT_GROUP_LINK= 1338 | #ALT_GROUP_NAME= 1339 | ALT_GROUP_SLAVE_NAMES=() 1340 | ALT_GROUP_SLAVE_LINKS=() 1341 | ALT_GROUP_STATUS= # auto | manual 1342 | #ALT_GROUP_CURRENT_VALUE= 1343 | #ALT_GROUP_CURRENT_INDEX= 1344 | ALT_GROUP_BEST_VALUE= 1345 | ALT_GROUP_BEST_INDEX= 1346 | ALT_GROUP_VALUES=() 1347 | ALT_GROUP_PRIORITIES=() 1348 | ALT_GROUP_FAMILIES=() 1349 | ALT_GROUP_INIT_SCRIPTS=() 1350 | # TODO Consider moving to serialized arrays for slave values 1351 | #ALT_GROUP_VALUE_${v}_SLAVE_${s}_VALUE 1352 | unset _ALT_TMP_SLAVE_INDEX 1353 | 1354 | _ALT_PARSE_ADMIN_STATE="STATUS" 1355 | local IFS line lineno=0 # 1 on first use 1356 | while IFS="" read -r line || [ -n "${line}" ]; do 1357 | ((lineno += 1)) 1358 | # Keeps looping on return code 3, otherwise returns on !0 1359 | # 1360 | local return_code 1361 | while :; do 1362 | if [ "${2:-}" == "trace" ]; then 1363 | printf "# line NO : %s\n" "${lineno}" 1364 | printf "# line TEXT: '%s'\n" "${line}" 1365 | printf "# state : %s\n" "${_ALT_PARSE_ADMIN_STATE}" 1366 | printf "# ---------\n" 1367 | fi 1368 | _alt_parse_admin_state_"${_ALT_PARSE_ADMIN_STATE}" "${line}" 1369 | return_code=$? 1370 | [[ $return_code -eq 3 ]] || break 1371 | done 1372 | [[ $return_code -eq 0 ]] || return $return_code 1373 | done <"${1}" 1374 | # Final state should be one of "eof candidate" states 1375 | # 1376 | if [ ! "${_ALT_PARSE_ADMIN_STATE}" = "MAYBE_ALT_START" ] && [ ! "${_ALT_PARSE_ADMIN_STATE}" = "MAYBE_ALT_SLAVE_VALUE" ]; then 1377 | printf "unexpected parse state: %s\n" "${_ALT_PARSE_ADMIN_STATE}" >&2 1378 | return 2 1379 | fi 1380 | if [ "${2:-}" == "trace" ] || [ "${2:-}" == "debug" ]; then 1381 | #printf "ALT_GROUP_NAME : %s\n" "${ALT_GROUP_NAME}" 1382 | printf "ALT_GROUP_LINK : %s\n" "${ALT_GROUP_LINK}" 1383 | printf "ALT_GROUP_SLAVE_NAMES : %s\n" "${ALT_GROUP_SLAVE_NAMES[*]}" 1384 | printf "ALT_GROUP_SLAVE_LINKS : %s\n" "${ALT_GROUP_SLAVE_LINKS[*]}" 1385 | printf "ALT_GROUP_STATUS : %s\n" "${ALT_GROUP_STATUS}" 1386 | #printf "ALT_GROUP_CURRENT_VALUE: %s\n" "${ALT_GROUP_CURRENT_VALUE}" 1387 | #printf "ALT_GROUP_CURRENT_INDEX: %s\n" "${ALT_GROUP_CURRENT_INDEX}" 1388 | printf "ALT_GROUP_BEST_VALUE : %s\n" "${ALT_GROUP_BEST_VALUE}" 1389 | printf "ALT_GROUP_BEST_INDEX : %s\n" "${ALT_GROUP_BEST_INDEX}" 1390 | printf "ALT_GROUP_VALUES : %s\n" "${ALT_GROUP_VALUES[*]}" 1391 | printf "ALT_GROUP_PRIORITIES : %s\n" "${ALT_GROUP_PRIORITIES[*]}" 1392 | printf "ALT_GROUP_FAMILIES : %s\n" "${ALT_GROUP_FAMILIES[*]}" 1393 | printf "ALT_GROUP_INIT_SCRIPTS : %s\n" "${ALT_GROUP_INIT_SCRIPTS[*]}" 1394 | local v=0 1395 | while [[ $v -lt ${#ALT_GROUP_VALUES[@]} ]]; do 1396 | local s=0 1397 | while [[ $s -lt ${#ALT_GROUP_SLAVE_NAMES[@]} ]]; do 1398 | local alt_slave_value_ref="ALT_GROUP_VALUE_${v}_SLAVE_${s}_VALUE" 1399 | printf "${alt_slave_value_ref} : %s\n" "${!alt_slave_value_ref}" 1400 | ((s += 1)) 1401 | done 1402 | ((v += 1)) 1403 | done 1404 | fi 1405 | } 1406 | 1407 | function _alt_parse_admin_state_STATUS() { 1408 | local regex='^((auto)|(manual))$' 1409 | if [[ "${1}" =~ $regex ]]; then 1410 | ALT_GROUP_STATUS="${BASH_REMATCH[1]}" 1411 | _ALT_PARSE_ADMIN_STATE="LINK" 1412 | return 0 1413 | else 1414 | printf "unable to parse Status field: %s\n" "${1}" >&2 1415 | return 2 1416 | fi 1417 | } 1418 | 1419 | function _alt_parse_admin_state_LINK() { 1420 | local regex="^(/.+)$" 1421 | if [[ "${1}" =~ $regex ]]; then 1422 | ALT_GROUP_LINK="${BASH_REMATCH[1]}" 1423 | _ALT_PARSE_ADMIN_STATE="MAYBE_SLAVE_NAME" 1424 | return 0 1425 | else 1426 | printf "unable to parse Primary Link field: %s\n" "${1}" >&2 1427 | return 2 1428 | fi 1429 | } 1430 | 1431 | function _alt_parse_admin_state_MAYBE_SLAVE_NAME() { 1432 | if [ -n "${1}" ]; then 1433 | _ALT_PARSE_ADMIN_STATE="SLAVE_NAME" 1434 | return 3 # re-check line 1435 | else 1436 | # blank line separates names/links from alternatives 1437 | # so consume the blank line, expect at least 1 alternative 1438 | # 1439 | _ALT_PARSE_ADMIN_STATE="ALT_VALUE" 1440 | return 0 1441 | fi 1442 | } 1443 | 1444 | function _alt_parse_admin_state_SLAVE_NAME() { 1445 | local regex="^([^/].*)$" 1446 | if [[ "${1}" =~ $regex ]]; then 1447 | ALT_GROUP_SLAVE_NAMES+=("${BASH_REMATCH[1]}") 1448 | _ALT_PARSE_ADMIN_STATE="SLAVE_LINK" 1449 | return 0 1450 | else 1451 | printf "unable to parse Slave Name field: %s\n" "${1}" >&2 1452 | return 2 1453 | fi 1454 | } 1455 | 1456 | function _alt_parse_admin_state_SLAVE_LINK() { 1457 | # Official source does not confirm leading / here 1458 | local regex="^(.+)$" 1459 | if [[ "${1}" =~ $regex ]]; then 1460 | ALT_GROUP_SLAVE_LINKS+=("${BASH_REMATCH[1]}") 1461 | _ALT_PARSE_ADMIN_STATE="MAYBE_SLAVE_NAME" 1462 | return 0 1463 | else 1464 | printf "unable to parse Slave Link field: %s\n" "${1}" >&2 1465 | return 2 1466 | fi 1467 | } 1468 | 1469 | # eof candidate 1470 | function _alt_parse_admin_state_MAYBE_ALT_START() { 1471 | _ALT_PARSE_ADMIN_STATE="ALT_VALUE" 1472 | return 3 # re-check line 1473 | } 1474 | 1475 | function _alt_parse_admin_state_ALT_VALUE() { 1476 | local regex="^(/.+)$" 1477 | if [[ "${1}" =~ $regex ]]; then 1478 | ALT_GROUP_VALUES+=("${BASH_REMATCH[1]}") 1479 | _ALT_PARSE_ADMIN_STATE="ALT_PRIORITY" 1480 | return 0 1481 | else 1482 | printf "unable to parse Alternative Value field: %s\n" "${1}" >&2 1483 | return 2 1484 | fi 1485 | } 1486 | 1487 | function _alt_parse_admin_state_ALT_PRIORITY() { 1488 | # Official source accepts \s* but likely always receives \s+ 1489 | local regex='^(@(.+)@)?([0-9]+)[[:space:]]*(.*)$' 1490 | if [[ "${1}" =~ $regex ]]; then 1491 | local alt_priority="${BASH_REMATCH[3]}" 1492 | local alt_index=${#ALT_GROUP_PRIORITIES[@]} # assign before += for 0-based value 1493 | ALT_GROUP_FAMILIES+=("${BASH_REMATCH[2]:-}") 1494 | ALT_GROUP_PRIORITIES+=("${alt_priority}") 1495 | ALT_GROUP_INIT_SCRIPTS+=("${BASH_REMATCH[4]:-}") 1496 | # We have to derive "BEST" manually, based in alternative priorities 1497 | # 1498 | if [ -z "${ALT_GROUP_BEST_INDEX}" ] || [[ "${ALT_GROUP_PRIORITIES[${ALT_GROUP_BEST_INDEX}]}" -lt "${alt_priority}" ]]; then 1499 | ALT_GROUP_BEST_INDEX="${alt_index}" 1500 | ALT_GROUP_BEST_VALUE="${ALT_GROUP_VALUES[${alt_index}]}" 1501 | fi 1502 | _ALT_PARSE_ADMIN_STATE="MAYBE_ALT_SLAVE_VALUE" 1503 | return 0 1504 | else 1505 | printf "unable to parse Alternative Priority field: %s\n" "${1}" >&2 1506 | return 2 1507 | fi 1508 | } 1509 | 1510 | # eof candidate 1511 | function _alt_parse_admin_state_MAYBE_ALT_SLAVE_VALUE() { 1512 | : "${_ALT_TMP_SLAVE_INDEX:=0}" # Reset to 0 on first slave of each alternative 1513 | if [[ "${_ALT_TMP_SLAVE_INDEX}" -lt ${#ALT_GROUP_SLAVE_LINKS[@]} ]]; then 1514 | local regex="^(/.+)$" 1515 | if [[ "${1}" =~ $regex ]]; then 1516 | # Slave value order matches slave name order - no need to lookup 1517 | # 1518 | local slave_value="${BASH_REMATCH[1]}" 1519 | local alt_value_index=$((${#ALT_GROUP_VALUES[@]} - 1)) 1520 | local slave_value_ref="ALT_GROUP_VALUE_${alt_value_index}_SLAVE_${_ALT_TMP_SLAVE_INDEX}_VALUE" 1521 | declare -g "${slave_value_ref}"="${slave_value}" 1522 | _ALT_TMP_SLAVE_INDEX=$((_ALT_TMP_SLAVE_INDEX + 1)) 1523 | # Leave status unchanged 1524 | return 0 1525 | else 1526 | printf "unable to parse Alternative Slave Value field: %s\n" "${1}" >&2 1527 | return 2 1528 | fi 1529 | else 1530 | unset _ALT_TMP_SLAVE_INDEX 1531 | _ALT_PARSE_ADMIN_STATE="MAYBE_ALT_START" 1532 | return 3 # re-check line 1533 | fi 1534 | } 1535 | 1536 | # ############################################################################## 1537 | # END alternatives_parse_admin_redhat.bash 1538 | # ############################################################################## 1539 | 1540 | # Only process main logic if not being sourced (ie tested) 1541 | # 1542 | (return 0 2>/dev/null) || main "$@" 1543 | --------------------------------------------------------------------------------