Как получить аргументы с флагами в Bash

Я знаю, что я легко могу установить такие параметры в bash:

$0 или $1

Я хочу иметь возможность использовать параметры флага, подобные этому, чтобы указать, для чего используются каждый параметр:

mysql -u user -h host

Каков наилучший способ получить значение -u param и -h param по флагом вместо позиции?

Ответ 1

Это идиома, которую я обычно использую:

while test $# -gt 0; do
  case "$1" in
    -h|--help)
      echo "$package - attempt to capture frames"
      echo " "
      echo "$package [options] application [arguments]"
      echo " "
      echo "options:"
      echo "-h, --help                show brief help"
      echo "-a, --action=ACTION       specify an action to use"
      echo "-o, --output-dir=DIR      specify a directory to store output in"
      exit 0
      ;;
    -a)
      shift
      if test $# -gt 0; then
        export PROCESS=$1
      else
        echo "no process specified"
        exit 1
      fi
      shift
      ;;
    --action*)
      export PROCESS='echo $1 | sed -e 's/^[^=]*=//g''
      shift
      ;;
    -o)
      shift
      if test $# -gt 0; then
        export OUTPUT=$1
      else
        echo "no output dir specified"
        exit 1
      fi
      shift
      ;;
    --output-dir*)
      export OUTPUT='echo $1 | sed -e 's/^[^=]*=//g''
      shift
      ;;
    *)
      break
      ;;
  esac
done

Ключевые моменты:

  • $# это количество аргументов
  • Цикл while просматривает все предоставленные аргументы, сопоставляя их значения внутри оператора case
  • Сдвиг убирает первого. Вы можете смещаться несколько раз внутри оператора case, чтобы принимать несколько значений.

Ответ 2

Этот пример использует встроенную команду Bash getopts и getopts из Руководства по стилю Google Shell:

a_flag=''
b_flag=''
files=''
verbose='false'

print_usage() {
  printf "Usage: ..."
}

while getopts 'abf:v' flag; do
  case "${flag}" in
    a) a_flag='true' ;;
    b) b_flag='true' ;;
    f) files="${OPTARG}" ;;
    v) verbose='true' ;;
    *) print_usage
       exit 1 ;;
  esac
done

Примечание. Если за символом следует двоеточие (например, f: :), ожидается, что этот параметр будет иметь аргумент.

Пример использования: ./script -v -a -b -f filename

Использование getopts имеет несколько преимуществ перед принятым ответом:

  • условие while гораздо более читаемо и показывает, какие параметры доступны
  • более чистый код; нет подсчета количества параметров и сдвига
  • Вы можете присоединиться к опциям (например, -a -b -c-abc)

Однако большим недостатком является то, что он не поддерживает длинные опции, а только одиночные опции -c.

Ответ 3

getopt - ваш друг.. простой пример:

function f () {
TEMP=`getopt --long -o "u:h:" "[email protected]"`
eval set -- "$TEMP"
while true ; do
    case "$1" in
        -u )
            user=$2
            shift 2
        ;;
        -h )
            host=$2
            shift 2
        ;;
        *)
            break
        ;;
    esac 
done;

echo "user = $user, host = $host"
}

f -u myself -h some_host

В каталоге /usr/bin должны быть различные примеры.

Ответ 4

Я думаю, что это послужило бы более простым примером того, чего вы хотите достичь. Нет необходимости использовать внешние инструменты. Встроенные инструменты Bash могут сделать эту работу за вас.

function DOSOMETHING {

   while test $# -gt 0; do
           case "$1" in
                -first)
                    shift
                    first_argument=$1
                    shift
                    ;;
                -last)
                    shift
                    last_argument=$1
                    shift
                    ;;
                *)
                   echo "$1 is not a recognized flag!"
                   return 1;
                   ;;
          esac
  done  

  echo "First argument : $first_argument";
  echo "Last argument : $last_argument";
 }

Это позволит вам использовать флаги, поэтому независимо от того, в каком порядке вы передаете параметры, вы получите правильное поведение.

Пример:

 DOSOMETHING -last "Adios" -first "Hola"

Выход:

 First argument : Hola
 Last argument : Adios

Вы можете добавить эту функцию в свой профиль или поместить ее в скрипт.

Спасибо!

Редактировать: сохранить это как файл и затем выполнить его как yourfile.sh -last "Adios" -first "Hola"

#!/bin/bash
while test $# -gt 0; do
           case "$1" in
                -first)
                    shift
                    first_argument=$1
                    shift
                    ;;
                -last)
                    shift
                    last_argument=$1
                    shift
                    ;;
                *)
                   echo "$1 is not a recognized flag!"
                   return 1;
                   ;;
          esac
  done  

  echo "First argument : $first_argument";
  echo "Last argument : $last_argument";

Ответ 5

Другой альтернативой было бы использовать что-то вроде приведенного ниже примера, который позволит вам использовать теги long - image или короткие -i, а также разрешить скомпилированные -i = "example.jpg" или отделить -i example.jpg методы передачи аргументов.

# declaring a couple of associative arrays
declare -A arguments=();  
declare -A variables=();

# declaring an index integer
declare -i index=1;

# any variables you want to use here
# on the left left side is argument label or key (entered at the command line along with it value) 
# on the right side is the variable name the value of these arguments should be mapped to.
# (the examples above show how these are being passed into this script)
variables["-gu"]="git_user";  
variables["--git-user"]="git_user";  
variables["-gb"]="git_branch";  
variables["--git-branch"]="git_branch";  
variables["-dbr"]="db_fqdn";  
variables["--db-redirect"]="db_fqdn";  
variables["-e"]="environment";  
variables["--environment"]="environment";

# [email protected] here represents all arguments passed in
for i in "[email protected]"  
do  
  arguments[$index]=$i;
  prev_index="$(expr $index - 1)";

  # this if block does something akin to "where $i contains ="
  # "%=*" here strips out everything from the = to the end of the argument leaving only the label
  if [[ $i == *"="* ]]
    then argument_label=${i%=*} 
    else argument_label=${arguments[$prev_index]}
  fi

  # this if block only evaluates to true if the argument label exists in the variables array
  if [[ -n ${variables[$argument_label]} ]]
    then
        # dynamically creating variables names using declare
        # "#$argument_label=" here strips out the label leaving only the value
        if [[ $i == *"="* ]]
            then declare ${variables[$argument_label]}=${i#$argument_label=} 
            else declare ${variables[$argument_label]}=${arguments[$index]}
        fi
  fi

  index=index+1;
done;

# then you could simply use the variables like so:
echo "$git_user";

Ответ 6

Мне нравится, что Роберт МакМахан ответит лучше всех здесь, так как кажется, что проще всего сделать из общих файлов для любого из ваших сценариев использование. Но, похоже, есть ошибка со строкой, if [[ -n ${variables[$argument_label]} ]] сообщение "переменные: неверный индекс массива". У меня нет представителя, чтобы комментировать, и я сомневаюсь, что это правильное "исправление", но оборачиваю это, if в if [[ -n $argument_label ]]; then if [[ -n $argument_label ]]; then очищает это.

Вот код, который я закончил, если вы знаете лучший способ, пожалуйста, добавьте комментарий к ответу Роберта.

Включить файл "flags-declares.sh"

# declaring a couple of associative arrays
declare -A arguments=();
declare -A variables=();

# declaring an index integer
declare -i index=1;

Включить файл "flags-arguments.sh"

# [email protected] here represents all arguments passed in
for i in "[email protected]"
do
  arguments[$index]=$i;
  prev_index="$(expr $index - 1)";

  # this if block does something akin to "where $i contains ="
  # "%=*" here strips out everything from the = to the end of the argument leaving only the label
  if [[ $i == *"="* ]]
    then argument_label=${i%=*}
    else argument_label=${arguments[$prev_index]}
  fi

  if [[ -n $argument_label ]] ; then
    # this if block only evaluates to true if the argument label exists in the variables array
    if [[ -n ${variables[$argument_label]} ]] ; then
      # dynamically creating variables names using declare
      # "#$argument_label=" here strips out the label leaving only the value
      if [[ $i == *"="* ]]
        then declare ${variables[$argument_label]}=${i#$argument_label=} 
        else declare ${variables[$argument_label]}=${arguments[$index]}
      fi
    fi
  fi

  index=index+1;
done;

Ваш "script.sh"

. bin/includes/flags-declares.sh

# any variables you want to use here
# on the left left side is argument label or key (entered at the command line along with it value) 
# on the right side is the variable name the value of these arguments should be mapped to.
# (the examples above show how these are being passed into this script)
variables["-gu"]="git_user";
variables["--git-user"]="git_user";
variables["-gb"]="git_branch";
variables["--git-branch"]="git_branch";
variables["-dbr"]="db_fqdn";
variables["--db-redirect"]="db_fqdn";
variables["-e"]="environment";
variables["--environment"]="environment";

. bin/includes/flags-arguments.sh

# then you could simply use the variables like so:
echo "$git_user";
echo "$git_branch";
echo "$db_fqdn";
echo "$environment";

Ответ 7

Если вы знакомы с Python argparse и не возражаете вызывать python для разбора аргументов bash, есть фрагмент кода, который я считаю действительно полезным и очень простым в использовании, называемый argparse-bash https://github.com/nhoffman/argparse-баш

Пример берут из их скрипта example.sh:

#!/bin/bash

source $(dirname $0)/argparse.bash || exit 1
argparse "[email protected]" <<EOF || exit 1
parser.add_argument('infile')
parser.add_argument('outfile')
parser.add_argument('-a', '--the-answer', default=42, type=int,
                    help='Pick a number [default %(default)s]')
parser.add_argument('-d', '--do-the-thing', action='store_true',
                    default=False, help='store a boolean [default %(default)s]')
parser.add_argument('-m', '--multiple', nargs='+',
                    help='multiple values allowed')
EOF

echo required infile: "$INFILE"
echo required outfile: "$OUTFILE"
echo the answer: "$THE_ANSWER"
echo -n do the thing?
if [[ $DO_THE_THING ]]; then
    echo " yes, do it"
else
    echo " no, do not do it"
fi
echo -n "arg with multiple values: "
for a in "${MULTIPLE[@]}"; do
    echo -n "[$a] "
done
echo

Ответ 8

Я предлагаю простой TLDR :; пример для непосвященных.

Создайте скрипт bash с именем helloworld.sh

#!/bin/bash

while getopts "n:" arg; do
  case $arg in
    n) Name=$OPTARG;;
  esac
done

echo "Hello $Name!"

После этого вы можете передать необязательный параметр -n.

Выполните скрипт как таковой:

$ bash helloworld.sh -n 'World'

Выход

$ Hello World!

Notes

Если вы хотите использовать несколько параметров:

  1. расширить while getops "n:" arg: do с помощью большего количества параметров, таких как while getops "n:o:p:" arg: do
  2. расширить регистр переключателя дополнительными переменными. Таких как o) Option=$OPTARG и p) Parameter=$OPTARG