Jak uzyskać argumenty z flagami w Bash

Wiem, że mogę łatwo uzyskać takie parametry w bash:

$0 lub $1

Chcę być w stanie użyć opcji znaczników takich jak ta, aby określić, do czego każdy parametr jest używany:

mysql -u user -h host

Jaki jest najlepszy sposób na uzyskanie -u param wartości i -h param wartości według flagi zamiast według pozycji?

 199
Author: codeforester, 2011-08-15

7 answers

To jest idiom, którego zwykle używam:

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

Kluczowe punkty to:

  • $# jest liczbą argumentów
  • while loop patrzy na wszystkie podane argumenty, dopasowując ich wartości wewnątrz instrukcji case
  • shift zabiera pierwszą. Wewnątrz instrukcji case można przesuwać wiele razy, aby przyjąć wiele wartości.
 220
Author: Flexo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-23 01:34:56

Ten przykład wykorzystuje wbudowany Bash getopts polecenie i pochodzi z Google Shell Style Guide :

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

Uwaga: Jeśli po znaku następuje dwukropek (np. f:), Opcja ta powinna mieć argument.

Przykładowe użycie: ./script -v -a -b -f filename

Korzystanie z getopts ma kilka zalet w stosunku do zaakceptowanej odpowiedzi:

  • warunek while jest o wiele bardziej czytelny i pokazuje jakie są akceptowane opcje
  • cleaner code; no zliczanie liczby parametrów i przesunięcie
  • możesz dołączyć opcje (np. -a -b -c-abc)

Jednak dużą wadą jest to, że nie obsługuje długich opcji, tylko opcje jednoznakowe.

 260
Author: Dennis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-09 09:17:04

Getopt jest twoim przyjacielem.. prosty przykład:

function f () {
TEMP=`getopt --long -o "u:h:" "$@"`
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

W katalogu /usr/bin powinny być różne przykłady.

 38
Author: Shizzmo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-15 20:06:23

Myślę, że posłużyłoby to jako prostszy przykład tego, co chcesz osiągnąć. Nie ma potrzeby korzystania z narzędzi zewnętrznych. Wbudowane narzędzia Bash mogą wykonać zadanie za Ciebie.

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";
 }

To pozwoli Ci używać FLAG, więc bez względu na kolejność przekazywania parametrów otrzymasz odpowiednie zachowanie.

Przykład:

 DOSOMETHING -last "Adios" -first "Hola"

Wyjście:

 First argument : Hola
 Last argument : Adios

Możesz dodać tę funkcję do swojego profilu lub umieścić ją wewnątrz skryptu.

Dzięki!

Edytuj : Zapisz to jako a plik a, a następnie wykonaj go jako 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";
 6
Author: Matias Barrios,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-16 13:31:52

Inną alternatywą byłoby użycie czegoś takiego jak Poniższy przykład, który pozwoliłby na użycie długich --image lub krótkich -i oraz pozwala na skompilowanie -i="przykład.jpg" lub oddzielny - i przykład.jpg metody przekazywania argumentów.

# 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's 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";

# $@ here represents all arguments passed in
for i in "$@"  
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";
 4
Author: Robert McMahan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-10 19:18:58

Najbardziej podoba mi się odpowiedź Roberta McMahona, ponieważ wydaje się, że najłatwiej jest przekształcić je w pliki nagłówkowe, których można użyć w dowolnym skrypcie. Ale wydaje się, że ma wadę z linią if [[ -n ${variables[$argument_label]} ]] rzucającą komunikat "variables: bad array subscript". Nie mam rep do komentowania, i wątpię, że to jest właściwa "poprawka", ale owinięcie tego if w if [[ -n $argument_label ]] ; then czyści to.

Oto kod, z którym skończyłem, jeśli znasz lepszy sposób, dodaj komentarz do odpowiedzi Roberta.

Include Plik "flags-declares.sh"

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

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

Dołącz plik "flags-arguments.sh"

# $@ here represents all arguments passed in
for i in "$@"
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;

Twój "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's 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";
 1
Author: Michael,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-01 23:00:39

Jeśli jesteś zaznajomiony z Pythonem argparse i nie masz nic przeciwko wywołaniu Pythona do analizy argumentów Basha, istnieje fragment kodu, który uważam za bardzo pomocny i bardzo łatwy w użyciu o nazwie argparse-bash https://github.com/nhoffman/argparse-bash

Przykład wziąć z ich example.sh skrypt:

#!/bin/bash

source $(dirname $0)/argparse.bash || exit 1
argparse "$@" <<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
 0
Author: Linh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-04 18:37:11