Почему OPTIND портит мои позиционные параметры? - PullRequest
0 голосов
/ 03 мая 2020

У меня есть эта функция:

    sgrep () 
{ 
    local OPTIND;
    if getopts i o; then
        grep --color=auto -P -in "$1" "$2";
        shift $((OPTIND-1));
    else
        grep --color=auto -P -n "$1" "$2";
    fi | sed -E -n 's/^([0-9]+).*/\1/p' | xargs -I{} vim +"{}" "$2";
    stty sane
}

Он должен использовать grep с учетом регистра, если он вызывается с -i. Но когда это так, положенное -i вместо search string и search string вместо somefile:

$ set -x
$ sgrep 'somesearch' 'somefile'
---#output---
+ sgrep -i 'somesearch' 'somefile'
+ local OPTIND
+ sed -E -n 's/^([0-9]+).*/\1/p'
+ getopts i o
+ grep --color=auto -P -in -i 'somesearch'

При вызове grep принимает $1 ( которая должна быть строкой поиска), как -i, поэтому строка поиска находится вместо file и, следовательно, не вызывается (с уважением. Ожидание файла или стандартного ввода - как в grep без указания файла). Я думал, что $((OPTIND-1)) изменит одну опцию в соответствии с этим Объясните команду оболочки: shift $ (($ optind - 1)) , но это не так. 1) Может кто-нибудь объяснить? + небольшое объяснение $OPTIND в моем случае было бы также хорошо. 2) последний вопрос: почему || exit 1 | не выходит перед другим каналом при сбое grep?

Ответы [ 2 ]

2 голосов
/ 03 мая 2020

Вопрос 1, getopts проблемы:

Как я уже говорил в комментарии, вам нужно сделать OPTIND и opt локальными для функции, чтобы она не наследовала значения от предыдущих запусков функции. Чтобы понять, почему это так, позвольте мне начать с вашей исходной функции (с первой версии вашего вопроса) и добавить некоторые инструменты в виде команд echo, чтобы показать, как все меняется в процессе работы:

sgrep () 
{ 
    echo "Starting sgrep, OPTIND='$OPTIND', opt='$opt', args=$*" >&2
    if getopts "i" i; then
        opt="-i";
        shift $((OPTIND-1));
        echo "Parsed -$i flag, OPTIND='$OPTIND', opt='$opt', args=$*" >&2
    fi;
    echo "Done parsing,   OPTIND='$OPTIND', opt='$opt', args=$*" >&2
    # grep --color=auto -P ${opt} "$1" "$2" || exit 1 | sed -E -n 's/^([0-9]+)/\1/p' | xargs -I{} vim +"{}" "$2";
}

... и попробуйте запустить сначала без флага -i:

$ sgrep 'somesearch' 'somefile'
Starting sgrep, OPTIND='1', opt='', args=somesearch somefile
Done parsing,   OPTIND='1', opt='', args=somesearch somefile

И все заработало нормально! После синтаксического анализа opt пусто (как и должно быть), и «somesearch» и «somefile» остаются в списке аргументов для передачи в grep.

Я должен объяснить немного о OPTIND, однако, прежде чем идти дальше. getopts предназначен для многократного запуска итераций по аргументам флага (или опции), а OPTIND является частью того, как он отслеживает, где находится при обработке списка аргументов. В частности, это номер аргумента next , который необходимо проверить, чтобы определить, является ли он флагом (и обработать его, если он есть). В этом случае он начинается с 1 (т. Е. $1 - следующий аргумент для проверки) и остается там, потому что $1 - это обычный аргумент, а не флаг.

Кстати, если вы ' Сделал бы shift $((OPTIND-1)) после обработки как обычно, он сделал бы shift 0, что бы все обнулять аргументы флага из списка аргументов. Как и должно быть. (С другой стороны, если у вас есть al oop и поместить shift в l oop, это изменит список аргументов из-под getopts, заставив его потерять свое место и получить очень Вот почему вы ставите shift после l oop.)

Хорошо, давайте попробуем с реальным флагом:

$ sgrep -i 'somesearch' 'somefile'
Starting sgrep, OPTIND='1', opt='', args=-i somesearch somefile
Parsed -i flag, OPTIND='2', opt='-i', args=somesearch somefile
Done parsing,   OPTIND='2', opt='-i', args=somesearch somefile

Снова , он работал правильно! Он проанализировал -i, установил opt соответственно, увеличил OPTIND до 2, так что если бы у вас было все oop, он бы изучил второй аргумент, нашел бы его обычный аргумент и остановил l oop. А затем shift $((OPTIND-1)) сместил аргумент с одним флагом, оставив не флаговые аргументы для передачи grep.

Давайте попробуем еще раз, с тем же флагом:

$ sgrep -i 'somesearch' 'somefile'
Starting sgrep, OPTIND='2', opt='-i', args=-i somesearch somefile
Done parsing,   OPTIND='2', opt='-i', args=-i somesearch somefile

Упс, теперь все пошло не так, и это потому, что он унаследовал OPTIND и opt от предыдущего запуска. OPTIND значение 2 говорит getopts, что оно уже проверено $1 и больше не нужно его обрабатывать; он смотрит на $2, видит, что он не начинается с -, поэтому это не флаг, поэтому он возвращает false, и if не запускается, а аргумент флага не удаляется. Между тем, opt все еще установлен на "-i" с последнего запуска.

Именно поэтому именно поэтому getopts не работает для вас. Чтобы доказать это, давайте изменим функцию, сделав обе переменные локальными:

sgrep ()
{
    local OPTIND opt    # <- This is the only change here
    echo "Starting sgrep, OPTIND='$OPTIND', opt='$opt', args=$*" >&2
    if getopts "i" i; then
        opt="-i";
        shift $((OPTIND-1));
        echo "Parsed -$i flag, OPTIND='$OPTIND', opt='$opt', args=$*" >&2
    fi;
    echo "Done parsing,   OPTIND='$OPTIND', opt='$opt', args=$*" >&2
    # grep --color=auto -P ${opt} "$1" "$2" || exit 1 | sed -E -n 's/^([0-9]+)/\1/p' | xargs -I{} vim +"{}" "$2";
}

И попробуйте:

$ sgrep -i 'somesearch' 'somefile'
Starting sgrep, OPTIND='', opt='', args=-i somesearch somefile
Parsed -i flag, OPTIND='2', opt='-i', args=somesearch somefile
Done parsing,   OPTIND='2', opt='-i', args=somesearch somefile
$ sgrep -i 'somesearch' 'somefile'
Starting sgrep, OPTIND='', opt='', args=-i somesearch somefile
Parsed -i flag, OPTIND='2', opt='-i', args=somesearch somefile
Done parsing,   OPTIND='2', opt='-i', args=somesearch somefile

Теперь все начинается немного странно, потому что OPTIND пусто вместо 1, но на самом деле это не проблема, потому что getopts предполагает, что он должен начинаться с 1. Таким образом, он анализирует аргумент, устанавливает opt (который не не наследовал поддельное значение от ранее), и убирает флаг из списка аргументов.

Однако есть проблема. Предположим, мы пропустили недопустимый (/ неподдерживаемый) флаг:

$ sgrep -k 'somesearch' 'somefile'
Starting sgrep, OPTIND='', opt='', args=-k somesearch somefile
-bash: illegal option -- k
Parsed -? flag, OPTIND='2', opt='-i', args=somesearch somefile
Done parsing,   OPTIND='2', opt='-i', args=somesearch somefile

Упс. Так как getopts обработал аргумент, начинающийся с -, он напечатал ошибку, но пошел дальше и возвратил true с переменной i, установленной в "?" указать на наличие проблемы. Ваша система не проверяла это, она просто предполагала, что это должно быть -i.

Теперь позвольте мне показать вам стандартную (рекомендованную) версию с while l oop и case на флаге с обработчиком ошибок. Я также взял на себя смелость убрать одиночные точки с запятой в конце строк, потому что они бесполезны в оболочке:

sgrep ()
{
    local OPTIND opt
    echo "Starting sgrep, OPTIND='$OPTIND', opt='$opt', args=$*" >&2
    while getopts "i" i; do
        case "$i" in
            i )
                opt="-$i"
                echo "Parsed -$i flag, OPTIND='$OPTIND', opt='$opt', args=$*" >&2
                ;;
            * )
                return 1 ;;
        esac
    done
    shift $((OPTIND-1))
    echo "Done parsing,   OPTIND='$OPTIND', opt='$opt', args=$*" >&2
    # grep --color=auto -P ${opt} "$1" "$2" || exit 1 | sed -E -n 's/^([0-9]+)/\1/p' | xargs -I{} vim +"{}" "$2"
}

И запустил это:

$ sgrep 'somesearch' 'somefile'
Starting sgrep, OPTIND='', opt='', args=somesearch somefile
Done parsing,   OPTIND='1', opt='', args=somesearch somefile
$ sgrep -i 'somesearch' 'somefile'
Starting sgrep, OPTIND='', opt='', args=-i somesearch somefile
Parsed -i flag, OPTIND='2', opt='-i', args=-i somesearch somefile
Done parsing,   OPTIND='2', opt='-i', args=somesearch somefile
$ sgrep 'somesearch' 'somefile'
Starting sgrep, OPTIND='', opt='', args=somesearch somefile
Done parsing,   OPTIND='1', opt='', args=somesearch somefile
$ sgrep -i 'somesearch' 'somefile'
Starting sgrep, OPTIND='', opt='', args=-i somesearch somefile
Parsed -i flag, OPTIND='2', opt='-i', args=-i somesearch somefile
Done parsing,   OPTIND='2', opt='-i', args=somesearch somefile

.. .Парсинг работает как положено даже при многократных запусках. Проверьте обработку ошибок:

$ sgrep -k 'somesearch' 'somefile'
Starting sgrep, OPTIND='', opt='', args=-k somesearch somefile
-bash: illegal option -- k

И так как есть al oop, он обрабатывает несколько флагов (даже если есть только один определенный флаг):

$ sgrep -i -i -i -i 'somesearch' 'somefile'
Starting sgrep, OPTIND='', opt='', args=-i -i -i -i somesearch somefile
Parsed -i flag, OPTIND='2', opt='-i', args=-i -i -i -i somesearch somefile
Parsed -i flag, OPTIND='3', opt='-i', args=-i -i -i -i somesearch somefile
Parsed -i flag, OPTIND='4', opt='-i', args=-i -i -i -i somesearch somefile
Parsed -i flag, OPTIND='5', opt='-i', args=-i -i -i -i somesearch somefile
Done parsing,   OPTIND='5', opt='-i', args=somesearch somefile

Теперь вы можете жаловаться, что для такой простой задачи написано много кода (всего один возможный флаг!), И вы были бы правы. Но это в основном шаблон; вам не нужно каждый раз писать все это, просто скопируйте стандартный пример, заполните строку опций и кейсы для их обработки, и это почти все. Если бы это было не в функции, у вас не было бы команды local, и вы бы использовали exit 1 вместо return 1 для спасения, но это все.

Если вы действительно хотите, чтобы это было просто, просто используйте if [ "$1" = "-i" ], и не связывайтесь со сложностями использования getopts.

Вопрос 2, почему || exit 1 | не завершается раньше, чем другой канал при сбое grep ?:

С этим подходом на самом деле есть три проблемы: во-первых, для выхода из функции вы используете return вместо exit.

Во-вторых, оболочка анализирует каналы с более высоким приоритетом, чем || поэтому команда была обработана как:

    grep --color=auto -P ${opt} "$1" "$2"
Logical or'ed with:
    exit 1 | sed -E -n 's/^([0-9]+)/\1/p' | xargs -I{} vim +"{}" "$2"

Вместо

    grep --color=auto -P ${opt} "$1" "$2" || exit 1
Piped to:
    sed -E -n 's/^([0-9]+)/\1/p' | xargs -I{} vim +"{}" "$2"

В-третьих, и, самое главное, элементы конвейера выполняются в подпроцессах. Для команд оболочки, таких как exit и return, это означает, что они выполняются в подоболочках, и запуск exit или return (или break или ...) в подоболочке не влияет на родительский объект. оболочка (то есть та, которая выполняет функцию). Это означает, что вы ничего не можете сделать в конвейере, чтобы функция возвращала напрямую.

В этом случае, я думаю, ваш лучший вариант - что-то вроде:

grep ... | otherstuff
if [ "${PIPESTATUS[0]}" -ne 0 ]; then
    return 1
fi
0 голосов
/ 03 мая 2020

Я изменил позиционные параметры на один больший:

sgrep () 
    { 
        local OPTIND;
        if getopts i o; then
            grep --color=auto -P -in "$2" "$3";
            shift $((OPTIND-1));
        else
            grep --color=auto -P -n "$1" "$2";
        fi | sed -E -n 's/^([0-9]+).*/\1/p' | xargs -I{} vim +"{}" "$2";
        stty sane
    }

Что работает, но мне это не нравится. В одной ветви я должен использовать более крупные позиции из-за опции, но в другой ветви без используемой опции позиции не меняются. Это грязно

Я пытался поставить shift $((OPTIND-1)) сразу после then, но безрезультатно.

...