Cách lấy đối số bằng cờ trong Bash


283

Tôi biết rằng tôi có thể dễ dàng nhận được các tham số định vị như thế này trong bash:

$0 hoặc là $1

Tôi muốn có thể sử dụng các tùy chọn cờ như thế này để chỉ định cho từng tham số được sử dụng:

mysql -u user -h host

Cách tốt nhất để có được -u paramgiá trị và -h paramgiá trị bằng cờ thay vì theo vị trí là gì?


2
Nó có thể là một ý tưởng tốt để yêu cầu / kiểm tra qua tại unix.stackexchange.com cũng
MRR0GERS

8
google cho "bash getopts" - rất nhiều hướng dẫn.
glenn jackman

89
@ glenn-jackman: Tôi chắc chắn sẽ google nó ngay bây giờ khi tôi biết tên. Vấn đề của google là - đặt câu hỏi - bạn nên biết 50% câu trả lời.
Stann

Câu trả lời:


291

Đây là thành ngữ tôi thường sử dụng:

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

Điểm chính là:

  • $# là số lượng đối số
  • vòng lặp while xem xét tất cả các đối số được cung cấp, khớp với các giá trị của chúng bên trong một câu lệnh tình huống
  • ca làm việc đầu tiên đi. Bạn có thể thay đổi nhiều lần bên trong một câu lệnh tình huống để lấy nhiều giá trị.

3
Những gì --action*--output-dir*trường hợp làm gì?
Lucio

1
Họ chỉ lưu các giá trị họ nhận được vào môi trường.
Flexo

22
@Lucio Bình luận siêu cũ, nhưng thêm nó trong trường hợp người khác từng truy cập trang này. * (Ký tự đại diện) dành cho trường hợp ai đó gõ --action=[ACTION]cũng như trường hợp ai đó gõ--action [ACTION]
Cooper

2
Tại sao *)bạn phá vỡ ở đó, bạn không nên thoát hoặc bỏ qua các tùy chọn xấu? Nói cách khác -bad -o dir, -o dirphần không bao giờ được xử lý.
newguy ngày

@newguy câu hỏi hay. Tôi nghĩ rằng tôi đã cố gắng để họ rơi vào một thứ khác
Flexo

427

Ví dụ này sử dụng getoptslệnh tích hợp của Bash và từ Hướng dẫn về Phong cách 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

Lưu ý: Nếu một ký tự được theo sau bởi dấu hai chấm (ví dụ f:), tùy chọn đó dự kiến ​​sẽ có một đối số.

Ví dụ sử dụng: ./script -v -a -b -f filename

Sử dụng getopts có một số lợi thế so với câu trả lời được chấp nhận:

  • điều kiện while dễ đọc hơn rất nhiều và hiển thị các tùy chọn được chấp nhận là gì
  • mã sạch hơn; không đếm số lượng tham số và dịch chuyển
  • bạn có thể tham gia các tùy chọn (ví dụ -a -b -c-abc)

Tuy nhiên, một nhược điểm lớn là nó không hỗ trợ các tùy chọn dài, chỉ có các tùy chọn một ký tự.


48
Người ta tự hỏi tại sao câu trả lời này, sử dụng một bash dựng sẵn, không phải là câu trả lời hàng đầu
Will Barnwell

13
Đối với hậu thế: dấu hai chấm sau trong 'abf: v' biểu thị rằng -f lấy một đối số bổ sung (tên tệp trong trường hợp này).
zahbaz

1
Tôi đã phải thay đổi dòng lỗi thành này:?) printf '\nUsage: %s: [-a] aflag [-b] bflag\n' $0; exit 2 ;;
Andy

7
Bạn có thể thêm một lưu ý về dấu hai chấm? Trong đó sau mỗi chữ cái, không có dấu hai chấm có nghĩa là không có arg, một dấu hai chấm có nghĩa là một arg và hai dấu hai chấm có nghĩa là arg tùy chọn?
limasxgoesto0

3
@WillBarnwell người ta cần lưu ý rằng nó đã được thêm 3 năm sau khi câu hỏi được hỏi, trong khi câu trả lời hàng đầu đã được thêm vào cùng ngày.
rbennell

47

getopt là bạn của bạn .. một ví dụ đơn giản:

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

Cần có nhiều ví dụ khác nhau trong thư mục / usr / bin của bạn.


3
Một ví dụ rộng hơn có thể được tìm thấy trong thư mục /usr/share/doc/util-linux/examples, ít nhất là trên các máy Ubuntu.
Serge Stroobandt

10

Tôi nghĩ rằng điều này sẽ phục vụ như một ví dụ đơn giản hơn về những gì bạn muốn đạt được. Không cần sử dụng các công cụ bên ngoài. Bash được xây dựng trong các công cụ có thể làm công việc cho bạn.

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

Điều này sẽ cho phép bạn sử dụng cờ để cho dù bạn đang chuyển thứ tự tham số nào, bạn sẽ có hành vi phù hợp.

Thí dụ :

 DOSOMETHING -last "Adios" -first "Hola"

Đầu ra:

 First argument : Hola
 Last argument : Adios

Bạn có thể thêm chức năng này vào hồ sơ của bạn hoặc đặt nó bên trong một tập lệnh.

Cảm ơn!

Chỉnh sửa: Lưu tệp này dưới dạng tệp aa và sau đó thực hiện dưới dạng tệp 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";

Tôi sử dụng mã trên & khi chạy nó không in gì cả. ./hello.sh DOSOMETHING -last "Adios" -first "Hola"
dinu0101

@ dinu0101 Đây là một chức năng. Không phải là một kịch bản. Bạn nên sử dụng nó dưới dạng DOSOMETHING -last "Adios" -first "Hola"
Matias Barrios

Cảm ơn @Matias. Hiểu. Làm thế nào để chạy bên trong script.
dinu0101

1
Cảm ơn bạn rất nhiều @Matias
dinu0101

2
Sử dụng return 1;với các ví dụ đầu ra cuối cùng can only 'return' from a function or sourced scripttrên macOS. Chuyển sang exit 1;làm việc như mong đợi mặc dù.
Mattias

5

Một lựa chọn khác là sử dụng một cái gì đó giống như dưới đây ví dụ mà sẽ cho phép bạn sử dụng lâu --image hay ngắn -i thẻ và cũng cho phép biên soạn -i = "example.jpg" hoặc riêng example.jpg -i phương pháp đi qua trong lập luận .

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

3

Tôi thích câu trả lời của Robert McMahan tốt nhất ở đây vì có vẻ như dễ dàng nhất để có thể chia thành các tệp bao gồm các tệp cho bất kỳ tập lệnh nào của bạn sử dụng. Nhưng nó dường như có một lỗ hổng với dòng if [[ -n ${variables[$argument_label]} ]]ném thông điệp, "biến: đăng ký mảng xấu". Tôi không có đại diện để bình luận, và tôi nghi ngờ đây là 'sửa chữa' thích hợp, nhưng gói nó iftrong việc if [[ -n $argument_label ]] ; thenlàm sạch nó.

Đây là mã tôi đã kết thúc, nếu bạn biết cách tốt hơn xin vui lòng thêm một nhận xét vào câu trả lời của Robert.

Bao gồm tệp "flags-declares.sh"

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

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

Bao gồm tệp "flags-argument.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;

"Script.sh" của bạn

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

3

Nếu bạn đã quen thuộc với Python argparse và không ngại gọi python để phân tích các đối số bash, có một đoạn mã tôi thấy thực sự hữu ích và siêu dễ sử dụng được gọi là argparse-bash https://github.com/nhoffman/ argparse-bash

Ví dụ lấy từ tập lệnh example.sh của họ:

#!/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

2

Tôi đề xuất một TLDR đơn giản:; ví dụ cho người không khởi xướng.

Tạo một tập lệnh bash gọi là helloworld.sh

#!/bin/bash

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

echo "Hello $Name!"

Sau đó, bạn có thể truyền một tham số tùy chọn -nkhi thực thi tập lệnh.

Thực thi tập lệnh như vậy:

$ bash helloworld.sh -n 'World'

Đầu ra

$ Hello World!

Ghi chú

Nếu bạn muốn sử dụng nhiều tham số:

  1. mở rộng while getops "n:" arg: dovới nhiều thông số hơn như while getops "n:o:p:" arg: do
  2. mở rộng trường hợp chuyển đổi với bài tập biến thêm. Chẳng hạn như o) Option=$OPTARGp) Parameter=$OPTARG

1
#!/bin/bash

if getopts "n:" arg; then
  echo "Welcome $OPTARG"
fi

Lưu nó dưới dạng sample.sh và thử chạy

sh sample.sh -n John

trong thiết bị đầu cuối của bạn.


1

Tôi gặp khó khăn khi sử dụng getopts với nhiều cờ, vì vậy tôi đã viết mã này. Nó sử dụng một biến phương thức để phát hiện các cờ và sử dụng các cờ đó để gán các đối số cho các biến.

Lưu ý rằng, nếu một cờ không nên có đối số, có thể thực hiện một số thứ khác ngoài cài đặt CURRENTFLAG.

    for MYFIELD in "$@"; do

        CHECKFIRST=`echo $MYFIELD | cut -c1`

        if [ "$CHECKFIRST" == "-" ]; then
            mode="flag"
        else
            mode="arg"
        fi

        if [ "$mode" == "flag" ]; then
            case $MYFIELD in
                -a)
                    CURRENTFLAG="VARIABLE_A"
                    ;;
                -b)
                    CURRENTFLAG="VARIABLE_B"
                    ;;
                -c)
                    CURRENTFLAG="VARIABLE_C"
                    ;;
            esac
        elif [ "$mode" == "arg" ]; then
            case $CURRENTFLAG in
                VARIABLE_A)
                    VARIABLE_A="$MYFIELD"
                    ;;
                VARIABLE_B)
                    VARIABLE_B="$MYFIELD"
                    ;;
                VARIABLE_C)
                    VARIABLE_C="$MYFIELD"
                    ;;
            esac
        fi
    done

0

Vì vậy, đây là giải pháp của tôi. Tôi muốn có thể xử lý các cờ boolean mà không có dấu gạch nối, với một dấu gạch nối và với hai dấu gạch nối cũng như gán tham số / giá trị với một và hai dấu gạch nối.

# Handle multiple types of arguments and prints some variables
#
# Boolean flags
# 1) No hyphen
#    create   Assigns `true` to the variable `CREATE`.
#             Default is `CREATE_DEFAULT`.
#    delete   Assigns true to the variable `DELETE`.
#             Default is `DELETE_DEFAULT`.
# 2) One hyphen
#      a      Assigns `true` to a. Default is `false`.
#      b      Assigns `true` to b. Default is `false`.
# 3) Two hyphens
#    cats     Assigns `true` to `cats`. By default is not set.
#    dogs     Assigns `true` to `cats`. By default is not set.
#
# Parameter - Value
# 1) One hyphen
#      c      Assign any value you want
#      d      Assign any value you want
#
# 2) Two hyphens
#   ... Anything really, whatever two-hyphen argument is given that is not
#       defined as flag, will be defined with the next argument after it.
#
# Example:
# ./parser_example.sh delete -a -c VA_1 --cats --dir /path/to/dir
parser() {
    # Define arguments with one hyphen that are boolean flags
    HYPHEN_FLAGS="a b"
    # Define arguments with two hyphens that are boolean flags
    DHYPHEN_FLAGS="cats dogs"

    # Iterate over all the arguments
    while [ $# -gt 0 ]; do
        # Handle the arguments with no hyphen
        if [[ $1 != "-"* ]]; then
            echo "Argument with no hyphen!"
            echo $1
            # Assign true to argument $1
            declare $1=true
            # Shift arguments by one to the left
            shift
        # Handle the arguments with one hyphen
        elif [[ $1 == "-"[A-Za-z0-9]* ]]; then
            # Handle the flags
            if [[ $HYPHEN_FLAGS == *"${1/-/}"* ]]; then
                echo "Argument with one hyphen flag!"
                echo $1
                # Remove the hyphen from $1
                local param="${1/-/}"
                # Assign true to $param
                declare $param=true
                # Shift by one
                shift
            # Handle the parameter-value cases
            else
                echo "Argument with one hyphen value!"
                echo $1 $2
                # Remove the hyphen from $1
                local param="${1/-/}"
                # Assign argument $2 to $param
                declare $param="$2"
                # Shift by two
                shift 2
            fi
        # Handle the arguments with two hyphens
        elif [[ $1 == "--"[A-Za-z0-9]* ]]; then
            # NOTE: For double hyphen I am using `declare -g $param`.
            #   This is the case because I am assuming that's going to be
            #   the final name of the variable
            echo "Argument with two hypens!"
            # Handle the flags
            if [[ $DHYPHEN_FLAGS == *"${1/--/}"* ]]; then
                echo $1 true
                # Remove the hyphens from $1
                local param="${1/--/}"
                # Assign argument $2 to $param
                declare -g $param=true
                # Shift by two
                shift
            # Handle the parameter-value cases
            else
                echo $1 $2
                # Remove the hyphens from $1
                local param="${1/--/}"
                # Assign argument $2 to $param
                declare -g $param="$2"
                # Shift by two
                shift 2
            fi
        fi

    done
    # Default value for arguments with no hypheb
    CREATE=${create:-'CREATE_DEFAULT'}
    DELETE=${delete:-'DELETE_DEFAULT'}
    # Default value for arguments with one hypen flag
    VAR1=${a:-false}
    VAR2=${b:-false}
    # Default value for arguments with value
    # NOTE1: This is just for illustration in one line. We can well create
    #   another function to handle this. Here I am handling the cases where
    #   we have a full named argument and a contraction of it.
    #   For example `--arg1` can be also set with `-c`.
    # NOTE2: What we are doing here is to check if $arg is defined. If not,
    #   check if $c was defined. If not, assign the default value "VD_"
    VAR3=$(if [[ $arg1 ]]; then echo $arg1; else echo ${c:-"VD_1"}; fi)
    VAR4=$(if [[ $arg2 ]]; then echo $arg2; else echo ${d:-"VD_2"}; fi)
}


# Pass all the arguments given to the script to the parser function
parser "$@"


echo $CREATE $DELETE $VAR1 $VAR2 $VAR3 $VAR4 $cats $dir

Một số tài liệu tham khảo

  • Các thủ tục chính đã được tìm thấy ở đây .
  • Thông tin thêm về việc chuyển tất cả các đối số cho một chức năng ở đây .
  • Thông tin thêm về các giá trị mặc định ở đây .
  • Thêm thông tin về declarelàm $ bash -c "help declare".
  • Thêm thông tin về shiftlàm $ bash -c "help shift".
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.