Вопрос 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