1# systemctl(1) completion -*- shell-script -*- 2# SPDX-License-Identifier: LGPL-2.1-or-later 3# 4# This file is part of systemd. 5# 6# Copyright © 2010 Ran Benita 7 8__systemctl() { 9 local mode=$1; shift 1 10 systemctl $mode --full --legend=no --no-pager --plain "$@" 2>/dev/null 11} 12 13__systemd_properties() { 14 {{ROOTLIBEXECDIR}}/systemd --dump-bus-properties 15} 16 17__contains_word () { 18 local w word=$1; shift 19 for w in "$@"; do 20 [[ $w = "$word" ]] && return 21 done 22} 23 24{% raw -%} 25__filter_units_by_properties () { 26 local mode=$1 properties=$2; shift 2 27 local units=("$@") 28 local props i p n 29 local names= count=0 30 31 IFS=$',' read -r -a p < <(echo "Names,$properties") 32 n=${#p[*]} 33 readarray -t props < \ 34 <(__systemctl $mode show --property "Names,$properties" -- "${units[@]}") 35 36 for ((i=0; i < ${#props[*]}; i++)); do 37 if [[ -z ${props[i]} ]]; then 38 if (( count == n )) && [[ -n $names ]]; then 39 echo $names 40 fi 41 names= 42 count=0 43 else 44 (( count++ )) 45 if [[ ${props[i]%%=*} == 'Names' ]]; then 46 names=${props[i]#*=} 47 fi 48 fi 49 done 50 if (( count == n )) && [[ -n $names ]]; then 51 echo $names 52 fi 53} 54{% endraw %} 55 56__get_all_units () { { __systemctl $1 list-unit-files "$2*"; __systemctl $1 list-units --all "$2*"; } \ 57 | { while read -r a b; do echo " $a"; done; }; } 58__get_non_template_units() { { __systemctl $1 list-unit-files "$2*"; __systemctl $1 list-units --all "$2*"; } \ 59 | { while read -r a b; do [[ $a =~ @\. ]] || echo " $a"; done; }; } 60__get_template_names () { __systemctl $1 list-unit-files "$2*" \ 61 | { while read -r a b; do [[ $a =~ @\. ]] && echo " ${a%%@.*}@"; done; }; } 62__get_active_units () { __systemctl $1 list-units "$2*" \ 63 | { while read -r a b; do echo " $a"; done; }; } 64 65__get_not_masked_unit_files() { 66 # filter out masked, not-found, or template units. 67 __systemctl $1 list-unit-files --state enabled,enabled-runtime,linked,linked-runtime,static,indirect,disabled,generated,transient "$2*" | \ 68 { while read -r a b; do [[ $a =~ @\. ]] || echo " $a"; done; } 69} 70 71__get_startable_units () { 72 __filter_units_by_properties $1 ActiveState=inactive,CanStart=yes $( 73 { __get_not_masked_unit_files $1 $2 74 # get inactive template units 75 __systemctl $1 list-units --state inactive,failed "$2*" | \ 76 { while read -r a b c; do [[ $b == "loaded" ]] && echo " $a"; done; } 77 } | sort -u ) 78} 79__get_restartable_units () { 80 # filter out masked and not-found 81 __filter_units_by_properties $1 CanStart=yes $( 82 { __get_not_masked_unit_files $1 $2 83 __get_active_units $1 $2 84 } | sort -u ) 85} 86 87__get_stoppable_units () { 88 # filter out masked and not-found 89 local units=$( 90 { __get_not_masked_unit_files $1 $2 91 __get_active_units $1 $2 92 } | sort -u ) 93 __filter_units_by_properties $1 ActiveState=active,CanStop=yes $units 94 __filter_units_by_properties $1 ActiveState=reloading,CanStop=yes $units 95 __filter_units_by_properties $1 ActiveState=activating,CanStop=yes $units 96} 97 98__get_reloadable_units () { 99 # filter out masked and not-found 100 __filter_units_by_properties $1 ActiveState=active,CanReload=yes $( 101 { __get_not_masked_unit_files $1 $2 102 __get_active_units $1 $2 103 } | sort -u ) 104} 105 106__get_failed_units () { __systemctl $1 list-units "$2*" \ 107 | { while read -r a b c d; do [[ $c == "failed" ]] && echo " $a"; done; }; } 108__get_enabled_units () { __systemctl $1 list-unit-files "$2*" \ 109 | { while read -r a b c ; do [[ $b == "enabled" ]] && echo " $a"; done; }; } 110__get_disabled_units () { __systemctl $1 list-unit-files "$2*" \ 111 | { while read -r a b c ; do [[ $b == "disabled" ]] && echo " $a"; done; }; } 112__get_masked_units () { __systemctl $1 list-unit-files "$2*" \ 113 | { while read -r a b c ; do [[ $b == "masked" ]] && echo " $a"; done; }; } 114__get_all_unit_files () { { __systemctl $1 list-unit-files "$2*"; } | { while read -r a b; do echo " $a"; done; }; } 115 116__get_machines() { 117 local a b 118 { machinectl list-images --full --no-legend --no-pager; machinectl list --full --no-legend --no-pager; } | \ 119 { while read a b; do echo " $a"; done; } 120} 121 122_systemctl () { 123 local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} 124 local i verb comps mode cur_orig 125 126 local -A OPTS=( 127 [STANDALONE]='--all -a --reverse --after --before --defaults --force -f --full -l --global 128 --help -h --no-ask-password --no-block --legend=no --no-pager --no-reload --no-wall --now 129 --quiet -q --system --user --version --runtime --recursive -r --firmware-setup 130 --show-types --plain --failed --value --fail --dry-run --wait' 131 [ARG]='--host -H --kill-who --property -p --signal -s --type -t --state --job-mode --root 132 --preset-mode -n --lines -o --output -M --machine --message --timestamp --check-inhibitors' 133 ) 134 135 if __contains_word "--user" ${COMP_WORDS[*]}; then 136 mode=--user 137 elif __contains_word "--global" ${COMP_WORDS[*]}; then 138 mode=--user 139 else 140 mode=--system 141 fi 142 143 if __contains_word "$prev" ${OPTS[ARG]}; then 144 case $prev in 145 --signal|-s) 146 _signals 147 return 148 ;; 149 --type|-t) 150 comps=$(__systemctl $mode -t help) 151 ;; 152 --state) 153 comps=$(__systemctl $mode --state=help) 154 ;; 155 --job-mode) 156 comps='fail replace replace-irreversibly isolate 157 ignore-dependencies ignore-requirements flush' 158 ;; 159 --kill-who) 160 comps='all control main' 161 ;; 162 --root) 163 comps=$(compgen -A directory -- "$cur" ) 164 compopt -o filenames 165 ;; 166 --host|-H) 167 comps=$(compgen -A hostname) 168 ;; 169 --property|-p) 170 comps=$(__systemd_properties) 171 ;; 172 --preset-mode) 173 comps='full enable-only disable-only' 174 ;; 175 --output|-o) 176 comps=$( systemctl --output=help 2>/dev/null ) 177 ;; 178 --machine|-M) 179 comps=$( __get_machines ) 180 ;; 181 --timestamp) 182 comps='pretty us µs utc us+utc µs+utc' 183 ;; 184 --check-inhibitors) 185 comps='auto yes no' 186 ;; 187 esac 188 COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) 189 return 0 190 fi 191 192 if [[ "$cur" = -* ]]; then 193 COMPREPLY=( $(compgen -W '${OPTS[*]}' -- "$cur") ) 194 return 0 195 fi 196 197 local -A VERBS=( 198 [ALL_UNITS]='cat mask' 199 [NONTEMPLATE_UNITS]='is-active is-failed is-enabled status show preset help list-dependencies edit set-property revert' 200 [ENABLED_UNITS]='disable' 201 [DISABLED_UNITS]='enable' 202 [REENABLABLE_UNITS]='reenable' 203 [FAILED_UNITS]='reset-failed' 204 [STARTABLE_UNITS]='start' 205 [STOPPABLE_UNITS]='stop condstop kill try-restart condrestart' 206 [ISOLATABLE_UNITS]='isolate' 207 [RELOADABLE_UNITS]='reload condreload try-reload-or-restart force-reload' 208 [RESTARTABLE_UNITS]='restart reload-or-restart' 209 [TARGET_AND_UNITS]='add-wants add-requires' 210 [MASKED_UNITS]='unmask' 211 [JOBS]='cancel' 212 [ENVS]='set-environment unset-environment import-environment' 213 [STANDALONE]='daemon-reexec daemon-reload default 214 emergency exit halt hibernate hybrid-sleep 215 suspend-then-hibernate kexec list-jobs list-sockets 216 list-timers list-units list-unit-files poweroff 217 reboot rescue show-environment suspend get-default 218 is-system-running preset-all' 219 [FILE]='link switch-root bind mount-image' 220 [TARGETS]='set-default' 221 [MACHINES]='list-machines' 222 [LOG_LEVEL]='log-level' 223 [LOG_TARGET]='log-target' 224 [SERVICE_WATCHDOGS]='service-watchdogs' 225 ) 226 227 for ((i=0; i < COMP_CWORD; i++)); do 228 if __contains_word "${COMP_WORDS[i]}" ${VERBS[*]} && 229 ! __contains_word "${COMP_WORDS[i-1]}" ${OPTS[ARG]}; then 230 verb=${COMP_WORDS[i]} 231 break 232 fi 233 done 234 235 # When trying to match a unit name with certain special characters in its name (i.e 236 # foo\x2dbar:01) they get (un)escaped by bash along the way, thus causing any possible 237 # match to fail. 238 # The following condition solves two cases: 239 # 1) We're trying to complete an already escaped unit name part, 240 # i.e foo\\x2dba. In this case we need to unescape the name, so it 241 # gets properly matched with the systemctl output (i.e. foo\x2dba). 242 # However, we need to keep the original escaped name as well for the 243 # final match, as the completion machinery does the unescaping 244 # automagically. 245 # 2) We're trying to complete an unescaped (literal) unit name part, 246 # i.e. foo\x2dba. That means we don't have to do the unescaping 247 # required for correct matching with systemctl's output, however, 248 # we need to escape the name for the final match, where the completion 249 # expects the string to be escaped. 250 cur_orig=$cur 251 if [[ $cur =~ '\\' ]]; then 252 cur="$(echo $cur | xargs echo)" 253 else 254 cur_orig="$(printf '%q' $cur)" 255 fi 256 257 if [[ -z ${verb-} ]]; then 258 comps="${VERBS[*]}" 259 260 elif __contains_word "$verb" ${VERBS[ALL_UNITS]}; then 261 comps=$( __get_all_units $mode "$cur" ) 262 compopt -o filenames 263 264 elif __contains_word "$verb" ${VERBS[NONTEMPLATE_UNITS]}; then 265 comps=$( __get_non_template_units $mode "$cur" ) 266 compopt -o filenames 267 268 elif __contains_word "$verb" ${VERBS[ENABLED_UNITS]}; then 269 comps=$( __get_enabled_units $mode "$cur" ) 270 compopt -o filenames 271 272 elif __contains_word "$verb" ${VERBS[DISABLED_UNITS]}; then 273 comps=$( __get_disabled_units $mode "$cur"; 274 __get_template_names $mode "$cur") 275 compopt -o filenames 276 277 elif __contains_word "$verb" ${VERBS[REENABLABLE_UNITS]}; then 278 comps=$( __get_disabled_units $mode "$cur"; 279 __get_enabled_units $mode "$cur"; 280 __get_template_names $mode "$cur") 281 compopt -o filenames 282 283 elif __contains_word "$verb" ${VERBS[STARTABLE_UNITS]}; then 284 comps=$( __get_startable_units $mode "$cur" ) 285 compopt -o filenames 286 287 elif __contains_word "$verb" ${VERBS[RESTARTABLE_UNITS]}; then 288 comps=$( __get_restartable_units $mode "$cur" ) 289 compopt -o filenames 290 291 elif __contains_word "$verb" ${VERBS[STOPPABLE_UNITS]}; then 292 comps=$( __get_stoppable_units $mode "$cur" ) 293 compopt -o filenames 294 295 elif __contains_word "$verb" ${VERBS[RELOADABLE_UNITS]}; then 296 comps=$( __get_reloadable_units $mode "$cur" ) 297 compopt -o filenames 298 299 elif __contains_word "$verb" ${VERBS[ISOLATABLE_UNITS]}; then 300 comps=$( __filter_units_by_properties $mode AllowIsolate=yes \ 301 $( __get_non_template_units $mode "$cur" ) ) 302 compopt -o filenames 303 304 elif __contains_word "$verb" ${VERBS[FAILED_UNITS]}; then 305 comps=$( __get_failed_units $mode "$cur" ) 306 compopt -o filenames 307 308 elif __contains_word "$verb" ${VERBS[MASKED_UNITS]}; then 309 comps=$( __get_masked_units $mode "$cur" ) 310 compopt -o filenames 311 312 elif __contains_word "$verb" ${VERBS[TARGET_AND_UNITS]}; then 313 if __contains_word "$prev" ${VERBS[TARGET_AND_UNITS]} \ 314 || __contains_word "$prev" ${OPTS[STANDALONE]}; then 315 comps=$( __systemctl $mode list-unit-files --type target --all "$cur*" \ 316 | { while read -r a b; do echo " $a"; done; } ) 317 else 318 comps=$( __get_all_unit_files $mode "$cur" ) 319 fi 320 compopt -o filenames 321 322 elif __contains_word "$verb" ${VERBS[STANDALONE]}; then 323 comps='' 324 325 elif __contains_word "$verb" ${VERBS[JOBS]}; then 326 comps=$( __systemctl $mode list-jobs | { while read -r a b; do echo " $a"; done; } ) 327 328 elif [ "$verb" = 'unset-environment' ]; then 329 comps=$( __systemctl $mode show-environment \ 330 | while read -r line; do echo " ${line%%=*}"; done ) 331 compopt -o nospace 332 333 elif [ "$verb" = 'set-environment' ]; then 334 comps=$( __systemctl $mode show-environment \ 335 | while read -r line; do echo " ${line%%=*}="; done ) 336 compopt -o nospace 337 338 elif [ "$verb" = 'import-environment' ]; then 339 COMPREPLY=( $(compgen -A variable -- "$cur_orig") ) 340 return 0 341 342 elif __contains_word "$verb" ${VERBS[FILE]}; then 343 comps=$( compgen -A file -- "$cur" ) 344 compopt -o filenames 345 346 elif __contains_word "$verb" ${VERBS[TARGETS]}; then 347 comps=$( __systemctl $mode list-unit-files --type target --full --all "$cur*" \ 348 | { while read -r a b; do echo " $a"; done; } ) 349 elif __contains_word "$verb" ${VERBS[LOG_LEVEL]}; then 350 comps='debug info notice warning err crit alert emerg' 351 elif __contains_word "$verb" ${VERBS[LOG_TARGET]}; then 352 comps='console journal kmsg journal-or-kmsg null' 353 elif __contains_word "$verb" ${VERBS[SERVICE_WATCHDOGS]}; then 354 comps='on off' 355 fi 356 357 COMPREPLY=( $(compgen -o filenames -W '$comps' -- "$cur_orig") ) 358 return 0 359} 360 361complete -F _systemctl systemctl 362