[home] [projects] [knowledge base] [manpages] [code] [markdown] [my ip]

Несколько полезных функций на Bourne Shell

Функции, описанные здесь можно применять в любой POSIX-совместимой оболочке. Протестировано в dash, bash, zsh. Примеры на этой странице будут обновляться.

1. Парсер аргументов в стиле GNU

optval() {
    # GNU-style command line options parser
    #
    # Usage: optval "$1" "$2"
    #
    # Set variables:
    #   OPT - option name
    #   VAL - value
    #   SFT - value for shift command

    OPT="${1%%=*}"; VAL="${1#*=}"; SFT=1

    if [ "$OPT" = "$VAL" ]; then
        if [ -n "$2" ] && [ "${2#"${2%%?}"}" != "-" ]; then
            VAL="$2"; SFT=2
        else
            unset VAL
        fi
    fi
    if [ -z "$VAL" ]; then
        echo Missing argument for option "$OPT" >&2
        exit 1
    fi
}

Эта функция позволит надёжно распарсить опции с обязательным аргументом. Ей по силам:

  • -o value

  • --option=value

  • --option=name=value

  • -option value

..и вариации.

Непосредственно в POSIX-оболочке работать с парсером будет несколько сложнее из-за отсутсвия массивов. Единственный доступный массив $@ здесь занят. NULL в качестве разделителя для списка позиционных аргументов использовать не получится, поэтому ниже пример с двоеточием. В Bash таких проблем нет. В POSIX-шелле лучше ограничиться встроенным getopts.

while [ "$#" -ne 0 ]; do
    case "$1" in
        -h|--help)
            echo "Usage: $0 [-chv] [--] arguments"
            exit 0
            ;;
        -v|--version)
            echo 1.0.0
            exit 0
            ;;
        -c|-c=*|--config|--config=*)
            optval "$1" "$2"
            config="$VAL"
            shift "$SFT"
            ;;
        --) # end of options processing
            shift
            break
            ;;
        -*)
            echo Unknown option "$1" >&2
            exit 1
            ;;
        *)  # Save positional arguments
            # In Bash better use ARGS+=("$1") instead
            ARGS="$1":"$ARGS"
            shift
    esac
done

# Set positional arguments. It is not needed in Bash
# In Bash you can use ARGS array directly or use set -- "${ARGS[@]}"
OLDIFS="$IFS"; IFS=:
# $arg must be unquoted!
# shellcheck disable=SC2086
for arg in $ARGS; do set -- $arg "$@"; done
IFS="$OLDIFS"

echo Config: "$config"
echo Positional arguments:
for arg in "$@"; do; echo : "$arg"; done

2. Интерактивный диалог

confirm() {
    # Confirmation interactive dialog
    #
    # Usage: confirm "message"

    [ -n "$ASSUME_NO" ] && return 1
    [ -n "$ASSUME_YES" ] && return 0

    while true; do
        printf '%s (y/n) ' "$*"
        read -r REPLY
        case "$REPLY" in
            [Yy]|[Yy][Ee][Ss])
                return 0
                ;;
            [Nn]|[Nn][Oo])
                return 1
                ;;
            *)
                echo Please, answer y or n
        esac
    done
}

Здесь предусмотрены переменные ASSUME_NO и ASSUME_YES для неинтерактивного режима. Автоматическое "Нет" и "Да" соответственно. Пример:

if confirm 'Proceed?'; then
    echo OK
else
    echo Abort
fi

3. Версия Python

Просто напечатает в STDOUT версию интерпретатора Python.

python_version() {
    # Get Python interpreter version in format 'major.minor.micro' e.g. '3.10.2'
    # Works with Python 2.x and 3.x.
    #
    # Usage: python_version [<path to interpreter>]

    "${1:-python}" -c "import sys; v=sys.version_info; \
        print('%s.%s.%s' % (v.major, v.minor, v.micro))"
}

4. Открыть редактор по умолчанию

default_editor() {
    # Edit file in user's default editor
    #
    # Usage: default_editor file

    if [ -n "$EDITOR" ]; then
        "$EDITOR" "$@"
        return 0
    fi

    if hash select-editor >/dev/null 2>&1; then
        if [ -f ~/.selected_editor ]; then
            . ~/.selected_editor
        else
            select-editor
            . ~/.selected_editor
        fi
        "$SELECTED_EDITOR" "$@"
        return 0
    fi

    if hash editor >/dev/null 2>&1; then
        editor "$@"
        return 0
    else
        vi "$@" # fallback to vi
        return 0
    fi
}

5. Простой URL-decoder

unescape_uri() {
    echo "$1" | sed 's/%/\\\\x/g' | xargs printf
}

6. Проверка на целое число

is_int() {
    if [ -n "$1" ] && [ "$1" -eq "$1" ] 2>/dev/null; then
        return 0
    else
        return 1
    fi
}