วิธีรับอาร์กิวเมนต์ด้วยค่าสถานะใน Bash


283

ฉันรู้ว่าฉันสามารถรับพารามิเตอร์ตำแหน่งได้อย่างง่ายดายเช่นนี้ใน bash:

$0 หรือ $1

ฉันต้องการใช้ตัวเลือกการตั้งค่าสถานะเช่นนี้เพื่อระบุสิ่งที่แต่ละพารามิเตอร์ใช้:

mysql -u user -h host

เป็นวิธีที่ดีที่สุดในการรับ-u paramค่าและ-h paramค่าตามธงแทนโดยตำแหน่งคืออะไร?


2
มันอาจเป็นความคิดที่ดีที่จะถาม / ตรวจสอบที่unix.stackexchange.comเช่นกัน
MRR0GERS

8
google สำหรับ "bash getopts" - บทเรียนมากมาย
เกล็นแจ็

89
@ glenn-jackman: ฉันจะ google แน่นอนตอนนี้ฉันรู้ชื่อ สิ่งที่เกี่ยวกับ google คือ - ถามคำถาม - คุณควรรู้แล้ว 50% ของคำตอบ
Stann

คำตอบ:


292

นี่เป็นสำนวนที่ฉันมักจะใช้:

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

ประเด็นสำคัญคือ:

  • $# คือจำนวนอาร์กิวเมนต์
  • ในขณะที่วงดูที่ข้อโต้แย้งทั้งหมดที่ให้มาจับคู่กับค่าของพวกเขาภายในคำสั่งกรณี
  • กะใช้เวลาหนึ่งออกไป คุณสามารถเลื่อนหลายครั้งภายในคำสั่ง case เพื่อรับหลายค่า

3
อะไร--action*และ--output-dir*กรณีทำอย่างไร
Lucio

1
พวกเขาเพียงแค่บันทึกค่าที่พวกเขาได้รับในสภาพแวดล้อม
เฟล็กโซ

22
@Lucio ความคิดเห็นเก่าสุด แต่เพิ่มในกรณีที่คนอื่นเคยเข้าชมหน้านี้ The * (wildcard) สำหรับกรณีที่มีคนพิมพ์--action=[ACTION]และกรณีที่ใครบางคน--action [ACTION]
cooper

2
ทำไม*)คุณถึงไปพักที่นั่นคุณไม่ควรออกหรือเพิกเฉยต่อตัวเลือกที่ไม่ดี? ในคำอื่น ๆส่วนหนึ่งจะไม่ประมวลผล -bad -o dir-o dir
newguy

@newguy คำถามที่ดี ฉันคิดว่าฉันพยายามปล่อยให้พวกเขาผ่านไปยังสิ่งอื่น
เฟล็กโซ

428

ตัวอย่างนี้ใช้getoptsคำสั่งในตัวของ Bash และมาจาก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

หมายเหตุ: หากตัวอักษรตามด้วยเครื่องหมายโคลอน (เช่นf:) ตัวเลือกนั้นคาดว่าจะมีอาร์กิวเมนต์

ตัวอย่างการใช้งาน: ./script -v -a -b -f filename

การใช้ getopts มีข้อดีหลายประการมากกว่าคำตอบที่ยอมรับได้:

  • เงื่อนไข while สามารถอ่านได้มากขึ้นและแสดงตัวเลือกที่ยอมรับได้
  • รหัสทำความสะอาด ไม่นับจำนวนพารามิเตอร์และเลื่อน
  • คุณสามารถเข้าร่วมตัวเลือก (เช่น-a -b -c-abc)

อย่างไรก็ตามข้อเสียใหญ่คือมันไม่สนับสนุนตัวเลือกยาวเพียงตัวเดียวอักขระ


48
หนึ่งสงสัยว่าทำไมคำตอบนี้โดยใช้ bash builtin ไม่ใช่คำตอบที่ดีที่สุด
Will Barnwell

13
สำหรับลูกหลาน: เครื่องหมายโคลอนหลังใน 'abf: v' แสดงว่า -f รับอาร์กิวเมนต์เพิ่มเติม (ชื่อไฟล์ในกรณีนี้)
zahbaz

1
ฉันต้องเปลี่ยนบรรทัดข้อผิดพลาดเป็น:?) printf '\nUsage: %s: [-a] aflag [-b] bflag\n' $0; exit 2 ;;
Andy

7
คุณสามารถเพิ่มบันทึกเกี่ยวกับโคลอนได้หรือไม่ ในหลังจากนั้นตัวอักษรแต่ละตัวไม่มีเครื่องหมายโคลอนไม่ต้องหาเรื่องเครื่องหมายโคลอนหนึ่งหมายถึงการหาเรื่องและเครื่องหมายทวิภาคสองตัวหมายถึงตัวเลือก ARG?
limasxgoesto0

3
@ WillBarnwell หนึ่งควรทราบว่ามันถูกเพิ่ม 3 ปีหลังจากคำถามถูกถามในขณะที่คำตอบที่ถูกเพิ่มเข้ามาในวันเดียวกัน
rbennell

47

getopt เป็นเพื่อนของคุณ .. ตัวอย่างง่ายๆ:

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

ควรมีตัวอย่างต่าง ๆ ในไดเรกทอรี / usr / bin ของคุณ


3
ตัวอย่างที่ครอบคลุมมากขึ้นสามารถพบได้ในไดเรกทอรี/usr/share/doc/util-linux/examplesอย่างน้อยที่สุดในเครื่อง Ubuntu
Serge Stroobandt

10

ฉันคิดว่านี่จะเป็นตัวอย่างที่ง่ายกว่าของสิ่งที่คุณต้องการบรรลุ ไม่จำเป็นต้องใช้เครื่องมือภายนอก เครื่องมือทุบตีในตัวสามารถทำงานให้คุณได้

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

คุณสามารถเพิ่มฟังก์ชั่นนี้ในโปรไฟล์ของคุณหรือใส่ไว้ในสคริปต์

ขอบคุณ!

แก้ไข: บันทึกเป็นไฟล์ aa จากนั้นเรียกใช้งานเป็น 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";

ฉันใช้โค้ดด้านบน & เมื่อเรียกใช้มันไม่ได้พิมพ์อะไรเลย ./hello.sh DOSOMETHING -last "Adios" -first "Hola"
dinu0101

@ dinu0101 นี่คือฟังก์ชั่น ไม่ใช่สคริปต์ คุณควรใช้มันเป็น DOSOMETHING - สุดท้าย "Adios" - ก่อน "Hola"
Matias Barrios

ขอบคุณ @Matias เข้าใจ วิธีรันสคริปต์ภายใน
dinu0101

1
ขอบคุณ @Matias มาก ๆ
dinu0101

2
ใช้return 1;กับผลลัพธ์ตัวอย่างสุดท้ายcan only 'return' from a function or sourced scriptบน macOS เปลี่ยนไปใช้exit 1;งานได้ตามที่คาดหวัง
Mattias

5

อีกทางเลือกหนึ่งคือการใช้บางอย่างเช่นตัวอย่างด้านล่างซึ่งจะช่วยให้คุณใช้แท็กยาว - ภาพหรือแท็ก-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'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

ฉันชอบคำตอบของ Robert McMahan ที่ดีที่สุดที่นี่เพราะดูเหมือนจะเป็นวิธีที่ง่ายที่สุดในการรวมไฟล์ไว้ด้วยกันเพื่อให้สคริปต์ของคุณใช้ได้ แต่ดูเหมือนว่าจะมีข้อบกพร่องที่มีบรรทัดที่if [[ -n ${variables[$argument_label]} ]]ขว้างข้อความ "ตัวแปร: ตัวห้อยอาร์เรย์ที่ไม่ดี" ฉันไม่มีตัวแทนที่จะแสดงความคิดเห็นและฉันสงสัยว่านี่คือ 'การแก้ไข' ที่เหมาะสม แต่เป็นการรวมifไว้ในนั้นif [[ -n $argument_label ]] ; thenทำความสะอาด

นี่คือรหัสที่ฉันได้รับถ้าคุณรู้วิธีที่ดีกว่าโปรดเพิ่มความคิดเห็นในคำตอบของ Robert

รวมไฟล์ "flags-declares.sh"

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

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

รวมไฟล์ "flag-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;

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

3

หากคุณคุ้นเคยกับ Python argparse และไม่รังเกียจที่จะเรียก python เพื่อวิเคราะห์ข้อโต้แย้งของ bash มีชิ้นส่วนของรหัสที่ฉันพบว่ามีประโยชน์จริง ๆ และใช้งานง่ายสุดที่เรียกว่า argparse-bash https://github.com/nhoffman/ argparse-ทุบตี

ตัวอย่างใช้จากสคริปต์ example.sh:

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

ฉันเสนอ TLDR แบบง่าย: ตัวอย่างสำหรับผู้เริ่มต้น

สร้างสคริปต์ทุบตีที่เรียกว่า 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!

หมายเหตุ

หากคุณต้องการใช้หลายพารามิเตอร์:

  1. ขยายwhile getops "n:" arg: doด้วยพารามิเตอร์เพิ่มเติมเช่น while getops "n:o:p:" arg: do
  2. ขยายสวิตช์เคสด้วยการกำหนดตัวแปรพิเศษ เช่นo) Option=$OPTARGและp) Parameter=$OPTARG

1
#!/bin/bash

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

บันทึกเป็น sample.sh แล้วลองเรียกใช้

sh sample.sh -n John

ในสถานีของคุณ


1

ฉันมีปัญหาในการใช้ getopts ที่มีหลายแฟล็กดังนั้นฉันจึงเขียนรหัสนี้ มันใช้ตัวแปรโมดัลในการตรวจจับธงและใช้ธงเหล่านั้นเพื่อกำหนดข้อโต้แย้งให้กับตัวแปร

โปรดทราบว่าหากธงไม่ควรมีข้อโต้แย้งสิ่งอื่นนอกเหนือจากการตั้งค่า 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

ดังนั้นนี่คือทางออกของฉัน ฉันต้องการที่จะสามารถจัดการกับธงบูลีนโดยไม่ต้องใช้เครื่องหมายขีดคั่นพร้อมด้วยเครื่องหมายยัติภังค์หนึ่งและสองยัติภังค์เช่นเดียวกับการกำหนดพารามิเตอร์ / ค่าที่มีหนึ่งและสองยัติภังค์

# 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

อ้างอิงบางอย่าง

  • ขั้นตอนหลักถูกพบที่นี่
  • เพิ่มเติมเกี่ยวกับการส่งข้อโต้แย้งทั้งหมดเพื่อการทำงานที่นี่
  • ข้อมูลเพิ่มเติมเกี่ยวกับค่าเริ่มต้นที่นี่
  • ข้อมูลเพิ่มเติมเกี่ยวกับสิ่งที่ต้องทำdeclare$ bash -c "help declare"
  • ข้อมูลเพิ่มเติมเกี่ยวกับสิ่งที่ต้องทำshift$ bash -c "help shift"
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.