#!/bin/bash ### Copyright 1999-2021. Plesk International GmbH. All rights reserved. # This script is designed to find suspicious customizations on Plesk servers that may lead to issues. # It should be run on a server with Plesk installed. This script makes no changes on the server. [ -n "$BASH" ] || { echo "This script should be executed using /bin/bash" >&2 exit 3 } detect_vz() { [ -z "$PLESK_VZ_RESULT" ] || return $PLESK_VZ_RESULT PLESK_VZ_RESULT=1 PLESK_VZ=0 PLESK_VE_HW_NODE=0 PLESK_VZ_TYPE= local issue_file="/etc/issue" local vzcheck_file="/proc/self/status" [ -f "$vzcheck_file" ] || return 1 local env_id=`sed -ne 's|^envID\:[[:space:]]*\([[:digit:]]\+\)$|\1|p' "$vzcheck_file"` [ -n "$env_id" ] || return 1 if [ "$env_id" = "0" ]; then # Either VZ/OpenVZ HW node or unjailed CloudLinux PLESK_VE_HW_NODE=1 return 1 fi if grep -q "CloudLinux" "$issue_file" >/dev/null 2>&1 ; then return 1 fi if [ -f "/proc/vz/veredir" ]; then PLESK_VZ_TYPE="vz" elif [ -d "/proc/vz" ]; then PLESK_VZ_TYPE="openvz" fi PLESK_VZ=1 PLESK_VZ_RESULT=0 return 0 } # detects lxc and docker containers detect_lxc() { [ -z "$PLESK_LXC_RESULT" ] || return $PLESK_LXC_RESULT PLESK_LXC_RESULT=1 PLESK_LXC=0 if { [ -f /proc/1/cgroup ] && grep -q 'docker\|lxc' /proc/1/cgroup; } || \ { [ -f /proc/1/environ ] && cat /proc/1/environ | tr \\0 \\n | grep -q "container=lxc"; }; then PLESK_LXC_RESULT=0 PLESK_LXC=1 fi return "$PLESK_LXC_RESULT" } detect_virtualization() { detect_vz detect_lxc local is_docker="`[ -f "/.dockerenv" ] && echo yes || :`" local systemd_detect_virt_ct="`/usr/bin/systemd-detect-virt -c 2>/dev/null | grep -v '^none$' || :`" local systemd_detect_virt_vm="`/usr/bin/systemd-detect-virt -v 2>/dev/null | grep -v '^none$' || :`" local virt_what="`/usr/sbin/virt-what 2>/dev/null | xargs || :`" if [ -n "$is_docker" ]; then echo "docker $virt_what" elif [ "$PLESK_VZ" = "1" ]; then echo "${PLESK_VZ_TYPE:-virtuozzo}" elif [ "$PLESK_LXC" = "1" ]; then echo "lxc $virt_what" elif [ -n "$systemd_detect_virt_ct" ]; then echo "$systemd_detect_virt_ct $systemd_detect_virt_vm" elif [ -n "$virt_what" ]; then echo "$virt_what" elif [ -n "$systemd_detect_virt_vm" ]; then echo "$systemd_detect_virt_vm" fi } # Check that first argument is in array (other arguments) is_in_array() { local needle="$1" shift # everything else is haystack for elem in $@; do [ "$elem" = "$needle" ] && return 0 done return 1 } # A set of functions for work with config variables # $1 is config file name, $2 is variable name, $3 is variable value conf_getvar() { cat $1 | perl -n -e '$p="'$2'"; print $1 if m/^$p\s+(.*)/' } plesk_full_version() { # "`plesk_full_version`" = "`plesk_release_version`.`plesk_hotfix_version`" # Beware: this shows information from a file, which changes during plesk-release package upgrade head -n1 /etc/plesk-release | cut -d' ' -f1 } plesk_release_version() { plesk_full_version | cut -d. -f-3 } ### Copyright 1999-2021. Plesk International GmbH. All rights reserved. # --- formatting helpers --- is_fmt_supported() { # Checks if output can be safely formatted # Note that end users, including checks, should use $NO_FMT instead [ -t 1 -a "0`tput colors 2>/dev/null`" -ge 4 ] } esc_seq() { # Outputs escape sequence to format text according to accent [ -z "$NO_FMT" ] || return 0 local accent="$1" case "$accent" in normal) tput sgr0 ;; ok) tput bold ;; good) tput bold; tput setaf 2 ;; sus|warning) tput bold; tput setaf 3 ;; bad|error) tput bold; tput setaf 1 ;; *) tput sgr0; tput setab 1 ;; esac } fmt() { # Outputs text formatted according to accent local accent="$1" shift echo "`esc_seq "$accent"`$*`esc_seq normal`" } strip_fmt() { # Strips escape sequences from piped in input sed "s,\x1B\[[0-9;]*[a-zA-Z],,g" } has_fmt() { # Checks if argument is formatted echo "$1" | grep -q $'\x1B' } join_array() { # Outputs arguments joined with 1-character separator (the first argument) local IFS="$1" shift echo "$*" } # --- result level processing helpers --- # Constants to check result level against LEVEL_ALWAYS=0 LEVEL_WARNINGS=1 LEVEL_ERRORS=2 LEVEL_NEVER=3 result_level() { # Returns check result level. 0 - success. Result is comparable to $LEVEL_* values. local result="$1" case "$result" in good|ok|normal) return 0 ;; sus|warning) return 1 ;; bad|error) return 2 ;; *) return 3 ;; esac } is_result_level_matched() { local level="$1" local target_level="$2" [ "$level" -ge "$target_level" ] } should_show_result_anyway() { [ -n "$SHOW_ALWAYS" -a "$opt_result_level" -lt "$LEVEL_NEVER" ] } fold_result_worst() { # Compares two RESULT values and outputs the worst one local accumulator="$1" local value="$2" local best_to_worst=('' good ok normal sus warning bad error) for i in "${!best_to_worst[@]}"; do if [ "$accumulator" = "${best_to_worst[$i]}" ]; then if is_in_array "$value" "${best_to_worst[@]:$i}"; then accumulator="$value" fi break fi done echo "$accumulator" } # --- console format reporter --- console_report_begin() { ACCUMULATED_RESULT= } console_report_check() { ACCUMULATED_RESULT="`fold_result_worst "$ACCUMULATED_RESULT" "$RESULT"`" [ -n "$DO_SHOW_RESULT" ] || should_show_result_anyway || return 0 has_fmt "$OUTPUT" || OUTPUT="`fmt "$RESULT" "$OUTPUT"`" [ -n "$DO_SHOW_LEGEND" ] || LEGEND= printf "%20s: %s%s\n" "${NAME}" "${OUTPUT}" "${LEGEND:+ ($LEGEND)}" if [ -n "$DO_SHOW_DESCRIPTION" -a -n "$DESCRIPTION" ]; then echo "$DESCRIPTION" | sed 's|^| |' fi } console_report_end() { local executed_checks="$1" local total_checks="$2" if [ "$executed_checks" -ne "$total_checks" ]; then ACCUMULATED_RESULT="`fold_result_worst "$ACCUMULATED_RESULT" "error"`" echo >&2 echo >&2 "Last check requested termination. Use --keep-going to ignore this." fi result_level "$ACCUMULATED_RESULT" } # --- tap format reporter --- tap_report_begin() { local total_checks="$#" echo "1..$total_checks" } tap_report_check() { local check_number="$1" local ok="ok" ! [ -n "$DO_SHOW_RESULT" ] || ok="not ok" [ -n "$DO_SHOW_LEGEND" ] || LEGEND= { printf "%s %d - %s: %s%s\n" "$ok" "$check_number" "$NAME" "$OUTPUT" "${LEGEND:+ ($LEGEND)}" if [ -n "$DO_SHOW_DESCRIPTION" -a -n "$DESCRIPTION" ]; then echo "$DESCRIPTION" | sed 's|^|# |' fi } | strip_fmt } tap_report_end() { local executed_checks="$1" local total_checks="$2" [ "$executed_checks" -eq "$total_checks" ] || echo "Bail out! Last check requested termination." } # --- engine --- result() { # Assigns and accumulates check result (can be called several times in one check) # RESULT is set to the worst one between calls # OUTPUT is concatenated between calls # LEGEND is deduplicated and joined between calls local result="$1" local output="$2" local legend="$3" RESULT="`fold_result_worst "$RESULT" "$result"`" OUTPUT+="$output" if [ -z "$LEGEND" -o -n "${LEGEND##*$legend*}" ]; then LEGEND+="${LEGEND:+, }$legend" fi } run_check() { # Executes one check local check="$1" # Check output parameters NAME= DESCRIPTION= SHOW_ALWAYS= RESULT= OUTPUT= LEGEND= "$check" RETURN_CODE="$?" # Advisory visibility flags DO_SHOW_RESULT= DO_SHOW_LEGEND= DO_SHOW_DESCRIPTION= result_level "$RESULT" local level="$?" ! is_result_level_matched "$level" "$opt_result_level" || DO_SHOW_RESULT="yes" ! is_result_level_matched "$level" "$opt_legend_level" || DO_SHOW_LEGEND="yes" ! is_result_level_matched "$level" "$opt_description_level" || DO_SHOW_DESCRIPTION="yes" } run_checks() { # Executes and reports result of a series of checks local reporter="${1:-console}" local keep_going="$2" shift 2 local NAME DESCRIPTION SHOW_ALWAYS RESULT OUTPUT LEGEND local DO_SHOW_RESULT DO_SHOW_LEGEND DO_SHOW_DESCRIPTION RETURN_CODE local i=0 : console_report_begin console_report_check console_report_end : tap_report_begin tap_report_check tap_report_end "${reporter}_report_begin" "$@" for check in "$@"; do (( ++i )) run_check "$check" "${reporter}_report_check" "$i" "$check" [ "$RETURN_CODE" -eq 0 -o -n "$keep_going" ] || break done "${reporter}_report_end" "$i" "$#" } ### Copyright 1999-2021. Plesk International GmbH. All rights reserved. # --- Plesk-specific helpers --- psa_conf() { local var="$1" local prod_conf_t="/etc/psa/psa.conf" local value="`conf_getvar "$prod_conf_t" "$var"`" [ -n "$value" ] || value="`conf_getvar "$prod_conf_t.default" "$var"`" echo "$value" } ### Copyright 1999-2021. Plesk International GmbH. All rights reserved. # --- general Plesk checks (non-subsystem-specific state) --- check_plesk_psa_conf() { local prod_conf_t="/etc/psa/psa.conf" local default_prod_conf_t="$prod_conf_t.default" local diff="` diff -uw "$default_prod_conf_t" "$prod_conf_t" \ | sed '0,/^@@/ d; /^ / d' | sed -n '/^.[[:alpha:]]/ p' `" local changes="`echo "$diff" | cut -c 2- | awk '{print $1}' | sort -u | xargs`" NAME="psa.conf" DESCRIPTION="Customization of many variables in $prod_conf_t isn't fully supported." if [ -n "$changes" ]; then DESCRIPTION+="${NL}Make sure custom values are valid and do not cause issues." DESCRIPTION+="${NL}Some variables may be customized as a result of Plesk updates." DESCRIPTION+="${NL}Here's the diff (-default setting, +current setting):${NL}$diff" result sus "$changes" "customized" else result ok "default" fi } ### Copyright 1999-2021. Plesk International GmbH. All rights reserved. # --- system and Plesk summary checks (always visible) --- plesk_version_text() { # Human-readable Plesk version plesk version | sed -n 's|^\s*Product version:\s*|| p' } os_version_text() { # Human-readable OS version and arch (on Plesk Obsidian or Plesk Onyx) { plesk version | sed -n 's|^\s*OS version:\s*|| p' plesk version | sed -n 's|^\s*Architecture:\s*|| p' } | xargs } previous_version() { # Decrements last component of the argument version local ver_head="${1%.*}" local ver_tail="${1##*.}" echo "$ver_head.$(( ver_tail - 1))" } check_summary_plesk_version() { local version_text="`plesk_version_text`" local release_version="`plesk_release_version`" local dev_version='18.0.36' local released_version="`previous_version "$dev_version"`" local prev_released_version="`previous_version "$released_version"`" local supported_versions=("$dev_version" "$released_version" "$prev_released_version" "17.8.11" "17.5.3" "17.0.17") NAME="Plesk release" SHOW_ALWAYS="yes" if [ "$release_version" = "$dev_version" ]; then result normal "$version_text" "dev" elif [ "$release_version" = "$released_version" ]; then result ok "$version_text" "latest" elif is_in_array "$release_version" "${supported_versions[@]}"; then result sus "$version_text" "supported" else result bad "${version_text:-N/A}" "not supported" [ "0${release_version%%.*}" -ge 17 ] || { DESCRIPTION="Many of the next checks expect that Plesk Obsidian or Onyx is installed" return 1 } fi } check_summary_os_version() { local version_text="`os_version_text`" local os_name="`plesk sbin osdetect -vendor`" local os_version="`plesk sbin osdetect -short-version`" local os_arch="`plesk sbin osdetect -arch`" local supported_oses=( {CentOS,RedHat,CloudLinux,VZLinux}-7-x86_64 {CentOS,RedHat,CloudLinux,AlmaLinux}-8-x86_64 Debian-{9,10}-x86_64 Ubuntu-{16,18,20}.04-x86_64 ) NAME="OS version" SHOW_ALWAYS="yes" if is_in_array "$os_name-$os_version-$os_arch" "${supported_oses[@]}"; then result ok "$version_text" "supported" else result bad "${version_text:-N/A}" "not supported" return 1 fi } check_summary_virtualization() { local virtualizations=(`detect_virtualization`) # Feel free to add to the lists if something is missing. See also: # https://docs.plesk.com/release-notes/obsidian/software-requirements/#sv # https://people.redhat.com/~rjones/virt-what/virt-what.txt # https://www.freedesktop.org/software/systemd/man/systemd-detect-virt.html local supported=(vmware xen xen-hvm vz openvz virtuozzo parallels kvm microsoft hyperv) local limited_support=(docker lxc) NAME="Virtualization" SHOW_ALWAYS="yes" local sep= local accent legend for virt in "${virtualizations[@]}"; do if is_in_array "$virt" "${limited_support[@]}"; then accent=sus legend="`fmt sus "limited support"`" elif is_in_array "$virt" "${supported[@]}"; then accent=ok legend="`fmt ok "supported"`" else accent=bad legend="`fmt bad "not supported"`" fi result "$accent" "$sep`fmt "$accent" "$virt"`" "$legend" sep=" " done } check_summary_plesk_load() { local HTTPD_VHOSTS_D="`psa_conf HTTPD_VHOSTS_D`" local PLESK_MAILNAMES_D="`psa_conf PLESK_MAILNAMES_D`" local doms="`find ${HTTPD_VHOSTS_D:-/_}/system -mindepth 1 -maxdepth 1 -type d | wc -l`" local subs="`find ${HTTPD_VHOSTS_D:-/_} -mindepth 1 -maxdepth 1 -type d | grep -Ev '/(\.skel|chroot|system|default)$' | wc -l`" local mbxs="`{ test -x /usr/bin/msmtp || find ${PLESK_MAILNAMES_D:-/_} -mindepth 3 -maxdepth 3 -name Maildir; } | wc -l`" NAME="Plesk load" SHOW_ALWAYS="yes" threshold() { local value="$1" local sus_threshold="$2" local bad_threshold="$3" if [ -n "$bad_threshold" ] && [ "$value" -ge "$bad_threshold" ]; then echo "bad" elif [ -n "$sus_threshold" ] && [ "$value" -ge "$sus_threshold" ]; then echo "sus" else echo "ok" fi } # Thresholds below are rather arbitrary, feel free to adjust local dom_result="`threshold "$doms" 300 5000`" local sub_result="`threshold "$subs" 300 5000`" local mbx_result="`threshold "$mbxs" 1000`" result "$dom_result" "`fmt $dom_result $doms` domains, " result "$sub_result" "`fmt $sub_result $subs` subscriptions, " result "$mbx_result" "`fmt $mbx_result $mbxs` mailboxes" } ### Copyright 1999-2021. Plesk International GmbH. All rights reserved. # --- system checks (general system properties and state) --- check_umask() { local current_umask="`umask`" local expected_umask="0022" NAME="umask" DESCRIPTION="Non-standard umask may result in permission issues (expected: $expected_umask)" if [ "$current_umask" = "$expected_umask" ]; then result ok "$current_umask" else result sus "$current_umask" fi } check_path() { local expected_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" # The following accounts for OSes with merged /usr (maps paths via readlink and deduplicates) local path_diff="` diff -U100 \ <(echo "$expected_path" | tr ':' '\n' | xargs -n1 readlink -f | awk '!a[$0]++') \ <(echo "$PATH" | tr ':' '\n' | xargs -n1 readlink -f | awk '!a[$0]++') \ | sed '0,/^@@/ d' `" local old_IFS="$IFS" local IFS=$'\n' local path_diff_items=($path_diff) IFS="$old_IFS" NAME="PATH" DESCRIPTION="Non-standard \$PATH may result in inaccessible binaries or use of wrong binaries" if [ "${#path_diff_items[@]}" -eq 0 ]; then result ok "$PATH" "`fmt ok "present path"`" return 0 fi local sep= local accent legend local last_path="`readlink -f "${expected_path##*:}"`" local beyond_last= for diff_item in "${path_diff_items[@]}"; do local change="`echo "$diff_item" | cut -c 1`" local path="`echo "$diff_item" | cut -c 2-`" case "$change" in '-') accent=bad; legend="`fmt "$accent" "${NO_FMT:+$change}missing path"`" ;; '+') accent=sus; legend="`fmt "$accent" "${NO_FMT:+$change}extra path"`" if [ -n "$beyond_last" ]; then change='#'; accent=normal; legend="${NO_FMT:+$change}extra trailing path" fi ;; ' ') accent=ok; legend="`fmt "$accent" "present path"`" ;; *) accent=; legend= ;; esac result "$accent" "$sep`fmt "$accent" ${NO_FMT:+$change}"$path"`" "$legend" sep=":" ! [ "$last_path" = "$path" ] || beyond_last="yes" done } check_user_beancounters() { local ubc="/proc/user_beancounters" NAME="user_beancounters" DESCRIPTION="Failures indicate this virtual server attempted to exceed allocated resource limits." DESCRIPTION+="${NL}See https://wiki.openvz.org/Resource_shortage" if [ ! -r "$ubc" ]; then result normal "N/A" return 0 fi # If ran on VZ HW node, this will check all containers # See also: https://wiki.openvz.org/Proc/user_beancounters local failures="`cat "$ubc" | sed -n '3,$ p' | awk '($NF != 0) {print $(NF - 5)}' | sort -u | xargs`" if [ -n "$failures" ]; then result bad "$failures" "failed resources" else result ok "OK" "no failures" fi } ### Copyright 1999-2021. Plesk International GmbH. All rights reserved. # List of all checks CHECKS=( check_summary_plesk_version check_summary_os_version check_summary_virtualization check_summary_plesk_load check_umask check_path check_user_beancounters check_plesk_psa_conf ) unset GREP_OPTIONS # --- initialization --- init_check_constants() { NL=$'\n' if [ -f "/etc/debian_version" -a -d "/opt/psa" ]; then PRODUCT_ROOT_D="/opt/psa" elif [ -f "/etc/redhat-release" -a -d "/usr/local/psa" ]; then PRODUCT_ROOT_D="/usr/local/psa" fi } # --- parse options --- usage() { if [ -n "$*" ]; then echo "audit: $*" >&2 fi cat << EOT Usage: audit [OPTION ...] audit --list [--include ...] [--exclude ...] curl -fsSL https://autoinstall.plesk.com/audit | bash -s -- [OPTION ...] wget -qO- https://autoinstall.plesk.com/audit | bash -s -- [OPTION ...] Output control: --format Select output format. Default is 'console', which is intended for human use. 'tap' is Test Anything Protocol format, intended for automated use. This option also influences exit code and default verbosity settings. --color Whether to display output in color. Default is 'auto'. -l, --list Instead of running checks, list them. -h, --help Show this help message. Verbosity control: -v, --verbose Be more verbose. Can be specified multiple times. -q, --quiet Be less verbose. Can be specified multiple times. --result WHEN When to show result of checks. Result of some checks is shown always (unless this is 'never'). 'tap' format always shows all results, so instead this option controls which results are treated as failed. --legend WHEN When to show legend for checks. --description WHEN When to show description for checks. WHEN is : 'always' or 'on' show for all checks, regardless of result. 'warnings' show for checks that result in warning or error. 'errors' show for checks that result in error. 'never' or 'off' show for none of the checks, regardless or result. Checks control: -i, --include "regexp ..." Include checks that match listed regular expressions. Multiple space-separated regexps can be specified and the option can be specified multiple times. See the --list output for the list of checks to filter. -e, --exclude "regexp ..." Exclude checks that match listed regular expressions. Works similar to the --include option. Exclude filters are applied after include filters. --keep-going Continue executing checks even if one of them requested termination. EOT exit 3 } parse_level_value() { # Parses level option value into numeric one local option="$1" local value="$2" case "$value" in always|on) return "$LEVEL_ALWAYS" ;; warnings) return "$LEVEL_WARNINGS" ;; errors) return "$LEVEL_ERRORS" ;; never|off) return "$LEVEL_NEVER" ;; *) usage "invalid '$option' option value '$value'" ;; esac } set_default_level_values() { # Assigns default values for $opt_*_level based on $opt_format and $opt_verbosity local result_level legend_level description_level case "$opt_format" in console) result_level=$LEVEL_WARNINGS legend_level=$LEVEL_ALWAYS description_level=$LEVEL_WARNINGS ! [ "$opt_verbosity" -gt 0 ] || result_level=$LEVEL_ALWAYS ! [ "$opt_verbosity" -gt 1 ] || description_level=$LEVEL_ALWAYS ! [ "$opt_verbosity" -lt 0 ] || legend_level=$LEVEL_NEVER ! [ "$opt_verbosity" -lt 0 ] || description_level=$LEVEL_NEVER ! [ "$opt_verbosity" -lt -1 ] || result_level=$LEVEL_ERRORS ! [ "$opt_verbosity" -lt -2 ] || result_level=$LEVEL_NEVER ;; tap) result_level=$LEVEL_WARNINGS legend_level=$LEVEL_NEVER description_level=$LEVEL_ALWAYS ! [ "$opt_verbosity" -gt 0 ] || legend_level=$LEVEL_ALWAYS ! [ "$opt_verbosity" -lt 0 ] || description_level=$LEVEL_NEVER ;; *) usage "invalid '--format' option value '$opt_format'" ;; esac : ${opt_result_level:=$result_level} : ${opt_legend_level:=$legend_level} : ${opt_description_level:=$description_level} } set_color_formatting() { # Initializes $NO_FMT, which defines whether color formatting is disabled case "$opt_color" in auto) [ "$opt_format" = "console" ] && is_fmt_supported && NO_FMT= || NO_FMT="yes" ;; never) NO_FMT="yes" ;; always) NO_FMT= ;; *) usage "invalid '--color' option value '$opt_color'" ;; esac } opt_format="console" opt_color="auto" opt_result_level= opt_legend_level= opt_description_level= opt_verbosity=0 opt_keep_going= opt_include=() opt_exclude=() TEMP=`getopt -o i:e:vqlh \ --long format:,color:,result:,legend:,description:,verbose,quiet,keep-going,include:,exclude:,list,help \ -n audit -- "$@"` || usage eval set -- "$TEMP" while [ "$#" -gt 0 ]; do case "$1" in --format) opt_format="$2" shift 2 ;; --color) opt_color="$2" shift 2 ;; --result) parse_level_value "$1" "$2" opt_result_level="$?" shift 2 ;; --legend) parse_level_value "$1" "$2" opt_legend_level="$?" shift 2 ;; --description) parse_level_value "$1" "$2" opt_description_level="$?" shift 2 ;; -v|--verbose) (( ++opt_verbosity )) || : shift ;; -q|--quiet) (( --opt_verbosity )) || : shift ;; --keep-going) opt_keep_going="yes" shift ;; -i|--include) set -o noglob opt_include+=($2) set +o noglob shift 2 ;; -e|--exclude) set -o noglob opt_exclude+=($2) set +o noglob shift 2 ;; -l|--list) opt_list="yes" shift ;; -h|--help) usage ;; --) shift ;; *) usage "unhandled argument '$1'" ;; esac done set_default_level_values set_color_formatting opt_include="`join_array '|' "${opt_include[@]}"`" opt_exclude="`join_array '|' "${opt_exclude[@]}"`" # --- execute --- [ -z "$opt_include" ] || CHECKS=(`printf "%s\n" "${CHECKS[@]}" | grep -E "$opt_include"`) [ -z "$opt_exclude" ] || CHECKS=(`printf "%s\n" "${CHECKS[@]}" | grep -Ev "$opt_exclude"`) if [ -n "$opt_list" ]; then echo "${CHECKS[@]}" | xargs -n1 -r else init_check_constants run_checks "$opt_format" "$opt_keep_going" "${CHECKS[@]}" fi