bash как убрать параметры из параметров после обработки

9

Я помню, как где-то видел bashскрипт, использующий caseи shiftдля просмотра списка позиционных параметров, разбора флагов и опций с аргументами, когда он их встречает, и удаления их после анализа, чтобы оставить только пустые аргументы, которые позже обрабатываются остальной частью скрипт.

Например, при синтаксическом анализе командной строки cp -R file1 -t /mybackup file2 -fон сначала будет проходить по параметрам, распознавать, что пользователь запросил переход в каталоги -R, указал цель -t /mybackupи принудительно выполнять копирование -f, и удалить их из списка параметров, оставив программа для обработки file1 file2в качестве оставшихся аргументов.

Но я, кажется, не могу вспомнить / выяснить, какой сценарий я когда-либо видел. Я просто хотел бы иметь возможность сделать это. Я гуглял по разным сайтам и добавлял список соответствующих страниц, которые я изучал.

Вопрос на этом веб-сайте специально задавался о «опционах, не зависящих от порядка», но как в единственном ответе, так и в ответе на вопрос, на который он был поставлен, не рассматриваются случаи, подобные описанным выше, когда варианты смешиваются с обычными аргументами, которые, как я полагаю, были причина для человека, чтобы конкретно упомянуть , независимые от порядка варианты.

Поскольку bashвстроенная getoptsфункция, по - видимому, останавливается на первом аргументе, не являющемся опцией, она кажется недостаточной в качестве решения. Вот почему на странице Wooledge BashFAQ (см. Ниже) объясняется, как изменить порядок аргументов. Но я бы хотел избежать создания нескольких массивов, если список аргументов довольно длинный.

Так shiftкак не поддерживает выталкивание отдельных аргументов из середины списка параметров, я не уверен, какой простой способ реализовать то, что я спрашиваю.

Я хотел бы услышать, есть ли у кого-нибудь решения для удаления аргументов из середины списка параметров без создания нового массива.

Страницы, которые я уже видел:

Джамадагни
источник
1
Когда мне нужно что-то сложное, я переключаюсь с bash на Perl.
Чороба

Ответы:

9

POSIXly, синтаксический анализ параметров должен останавливаться на --или на первом аргументе без опции (или аргументе без опции), в зависимости от того, что наступит раньше. Так в

cp -R file1 -t /mybackup file2 -f

что по адресу file1, так cpрекурсивно должны скопировать все file1, -t, /mybackupи file2в -fкаталог.

getopt(3)Однако GNU (который GNU cpиспользует для разбора опций (и здесь вы используете GNU, cpпоскольку вы используете -tопцию, специфичную для GNU )), если не установлена $POSIXLY_CORRECTпеременная окружения, принимает опции после аргументов. Так что это фактически эквивалентно синтаксическому анализу стиля опции POSIX:

cp -R -t /mybackup -f -- file1 file2

getoptsОболочка встроенная, даже в GNU оболочке ( bash) обрабатывает только стиль POSIX. Он также не поддерживает длинные опции или опции с необязательными аргументами.

Если вы хотите проанализировать параметры так же, как это cpделает GNU , вам нужно использовать GNU getopt(3)API. Для этого, если в Linux вы можете использовать расширенную getoptутилиту из util-linux( эта расширенная версия getoptкоманды также была перенесена в некоторые другие Unices, такие как FreeBSD ).

Это getoptпереставит параметры каноническим способом, что позволит вам просто проанализировать его с помощью while/caseцикла.

$ getopt -n "$0" -o t:Rf -- -Rf file1 -t/mybackup file2
 -R -f -t '/mybackup' -- 'file1' 'file2'

Вы обычно используете его как:

parsed_options=$(
  getopt -n "$0" -o t:Rf -- "$@"
) || exit
eval "set -- $parsed_options"
while [ "$#" -gt 0 ]; do
  case $1 in
    (-[Rf]) shift;;
    (-t) shift 2;;
    (--) shift; break;;
    (*) exit 1 # should never be reached.
  esac
done
echo "Now, the arguments are $*"

Также обратите внимание, что это getoptбудет анализировать параметры так же, как GNU cp. В частности, он поддерживает длинные параметры (и ввод их сокращенно) и учитывает $POSIXLY_CORRECTпеременные среды (которые при установке отключают поддержку параметров после аргументов) так же, как GNU cp.

Обратите внимание, что использование gdb и печать getopt_long()получаемых аргументов может помочь в создании параметров для getopt(1):

(gdb) bt
#0  getopt_long (argc=2, argv=0x7fffffffdae8, options=0x4171a6 "abdfHilLnprst:uvxPRS:T", long_options=0x417c20, opt_index=0x0) at getopt1.c:64
(gdb) set print pretty on
(gdb) p *long_options@40
$10 = {{
    name = 0x4171fb "archive",
    has_arg = 0,
    flag = 0x0,
    val = 97
  }, {
    name = 0x417203 "attributes-only",
[...]

Тогда вы можете использовать getoptкак:

getopt -n cp -o abdfHilLnprst:uvxPRS:T -l archive... -- "$@"

Помните, что cpсписок поддерживаемых опций GNU может меняться от одной версии к другой, и getoptвы не сможете проверить, передаете ли вы, например, допустимое значение для --sparseопции.

Стефан Шазелас
источник
@all: спасибо за ваши ответы. @Stephane: есть ли причина, по которой вы используете while [ "$#" -gt 0 ]io just while (($#))? Это просто чтобы избежать башизма?
Джамадагни
Да, хотя (($#))это скорее кшизм .
Стефан Шазелас
1

Таким образом, каждый раз, когда getoptsобрабатывается аргумент, он не ожидает, что он устанавливает переменную оболочки $OPTINDна следующее число в списке аргументов, которое он должен обработать и возвращает значение, отличное от 0. Если $OPTINDзадано значение 1, getoptsуказывается POSIX для принятия новый список аргументов. Таким образом, он просто наблюдает за getoptsвозвратом, сохраняет приращения счетчика плюс $OPTINDдля любого неудачного возврата, смещает ошибочные аргументы и сбрасывает $OPTINDкаждую неудачную попытку. Вы можете использовать его как opts "$@"- хотя вы хотите настроить caseцикл или сохранить его в переменную и изменить этот раздел на eval $case.

opts() while getopts :Rt:f opt || {
             until command shift "$OPTIND" || return 0
                   args=$args' "${'"$((a=${a:-0}+$OPTIND))"'}"'
                   [ -n "${1#"${1#-}"}" ]
             do OPTIND=1; done 2>/dev/null; continue
};     do case "$opt"  in
             R) Rflag=1      ;;
             t) tflag=1      ;
                targ=$OPTARG ;;
             f) fflag=1      ;;
       esac; done

Во время работы он устанавливает $argsкаждый аргумент, который getoptsне обрабатывается ... так что ...

set -- -R file1 -t /mybackup file2 -f
args= opts "$@"; eval "set -- $args"
printf %s\\n "$args"
printf %s\\n "$@"         
printf %s\\n "$Rflag" "$tflag" "$fflag" "$targ"

ВЫВОД

 "${2}" "${5}"
file1
file2
1
1
1
/mybackup

Это работает bash, dash, zsh, ksh93, mksh... ну, я перестал пытаться в этой точке. В каждой оболочке также есть $[Rtf]flagи $targ. Дело в том, что все числа аргументов, которые getoptsне хотели обрабатывать, остались.

Изменение стиля параметров также не имело значения. Это работало как -Rf -t/mybackupили -R -f -t /mybackup. Он работал в середине списка, в конце списка или в начале списка ...

Тем не менее, самый лучший способ - просто добавить --конец списка параметров в список аргументов, а затем выполнить его shift "$(($OPTIND-1))"в конце getoptsобработки. Таким образом вы удаляете все обработанные параметры и сохраняете конец списка аргументов.

Одна вещь, которую мне нравится делать, - переводить длинные варианты в короткие - и я делаю это очень похожим образом, поэтому этот ответ пришел легко - до того, как я бегу getopts.

i=0
until [ "$((i=$i+1))" -gt "$#" ]
do case "$1"                   in
--Recursive) set -- "$@" "-R"  ;;
--file)      set -- "$@" "-f"  ;;
--target)    set -- "$@" "-t"  ;;
*)           set -- "$@" "$1"  ;;
esac; shift; done
mikeserv
источник
Конвертирование длинных опций также будет конвертировать не опционы (как cp -t --file fooили cp -- --file foo) и не справится с опциями, введенными сокращенно ( --fi...), или с --target=/destсинтаксисом.
Стефан Шазелас
@ StéphaneChazelas - длинная конвертируемая вещь - всего лишь пример - она ​​явно не очень хорошо построена. Я заменил getoptsвещь намного более простой функцией.
mikeserv
@ StéphaneChazelas - пожалуйста, посмотрите еще раз. В то время как длинный вариант комментария остается оправданным, первое - и сопровождающее его отрицательное голосование - уже не так, как я думаю. Кроме того, я думаю, что это opts()имеет какое-то отношение к вашему собственному ответу, так как opts()работает в одном цикле - касаясь каждого аргумента, но один раз - и делает это надежно (насколько я могу судить) , переносимым и без единой подоболочки.
mikeserv
сброс OPTIND - это то, что я называю началом другого цикла getopts. В сущности, что это разбор несколько командных строк / наборов параметров ( -R, -t /mybackup, -f). Сейчас я оставлю свой отрицательный голос, поскольку он все еще запутан, вы используете $aнеинициализированный и args= opts..., скорее всего, оставите argsнеустановленным (или установите его на то, что было раньше) после optsвозвращения во многих оболочках (включая bash).
Стефан Шазелас
@ StéphaneChazelas - это включает в себя bash- я ненавижу это. На мой взгляд, если функция является текущей функцией оболочки, которую следует сохранить. Я обращаюсь к этим вещам - поскольку текущая функция, с большим количеством тестов, может быть надежно настроена для обработки всех случаев и даже для пометки аргумента предыдущим параметром, но я не согласна с тем, что она запутана . Как написано, это самый простой и самый прямой способ выполнения своей задачи, который я могу себе представить. testВ этом случае shiftмало смысла, когда в каждом неудачном shiftслучае вы должны return. Это не предназначено, чтобы сделать иначе.
mikeserv