Cara mendapatkan argumen dengan bendera di Bash

284

Saya tahu bahwa saya dapat dengan mudah mendapatkan parameter yang diposisikan seperti ini di bash:

$0 atau $1

Saya ingin dapat menggunakan opsi flag seperti ini untuk menentukan untuk apa setiap parameter digunakan:

mysql -u user -h host

Apa cara terbaik untuk mendapatkan -u paramnilai dan -h paramnilai berdasarkan bendera, bukan oleh posisi?

Stann
sumber
2
Mungkin juga ide yang baik untuk bertanya / mengecek di unix.stackexchange.com juga
MRR0GERS
8
google untuk "bash getopts" - banyak tutorial.
glenn jackman
89
@ glenn-jackman: Saya pasti akan google sekarang karena saya tahu namanya. Hal tentang google adalah - untuk mengajukan pertanyaan - Anda harus sudah tahu 50% dari jawabannya.
Stann

Jawaban:

292

Ini adalah ungkapan yang biasa saya gunakan:

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

Poin utamanya adalah:

  • $# adalah jumlah argumen
  • sementara loop melihat semua argumen yang disediakan, cocok dengan nilainya di dalam pernyataan kasus
  • shift mengambil yang pertama. Anda dapat menggeser beberapa kali di dalam pernyataan kasus untuk mengambil beberapa nilai.
Flexo
sumber
3
Apa yang dilakukan --action*dan --output-dir*kasing kasus?
Lucio
1
Mereka hanya menyimpan nilai-nilai yang mereka dapatkan ke lingkungan.
Flexo
22
@Lucio Super komentar lama, tetapi menambahkannya kalau-kalau ada orang lain yang mengunjungi halaman ini. * (Wildcard) untuk kasus di mana seseorang mengetik --action=[ACTION]dan halnya di mana seseorang mengetik--action [ACTION]
cooper
2
Mengapa *)Anda istirahat di sana, tidak seharusnya Anda keluar atau mengabaikan opsi yang buruk? Dengan kata lain -bad -o diryang -o dirsebagian tidak pernah diproses.
newguy
@newguy pertanyaan bagus. Saya pikir saya mencoba untuk membiarkan mereka jatuh ke sesuatu yang lain
Flexo
428

Contoh ini menggunakan getoptsperintah bawaan Bash dan dari Panduan Gaya 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

Catatan: Jika sebuah karakter diikuti oleh titik dua (misalnya f:), opsi itu diharapkan memiliki argumen.

Contoh penggunaan: ./script -v -a -b -f filename

Menggunakan getopts memiliki beberapa keunggulan dibandingkan jawaban yang diterima:

  • kondisi sementara jauh lebih mudah dibaca dan menunjukkan apa opsi yang diterima
  • kode pembersih; tidak menghitung jumlah parameter dan pergeseran
  • Anda dapat bergabung dengan opsi (misalnya -a -b -c-abc)

Namun, kerugian besar adalah tidak mendukung opsi lama, hanya opsi satu karakter.

Dennis
sumber
48
Orang bertanya-tanya mengapa jawaban ini, menggunakan bash builtin, bukan yang teratas
Will Barnwell
13
Untuk keturunan: titik dua di dalam 'abf: v' menunjukkan bahwa -f mengambil argumen tambahan (nama file dalam kasus ini).
zahbaz
1
Saya harus mengubah garis kesalahan menjadi ini:?) printf '\nUsage: %s: [-a] aflag [-b] bflag\n' $0; exit 2 ;;
Andy
7
Bisakah Anda menambahkan catatan tentang titik dua? Dalam setelah setiap huruf, tidak ada titik dua berarti tidak ada arg, satu titik dua berarti satu arg, dan dua titik dua berarti titik opsional?
limasxgoesto0
3
@ WillBarnwell orang harus mencatat bahwa itu ditambahkan 3 tahun setelah pertanyaan diajukan, sedangkan jawaban teratas ditambahkan pada hari yang sama.
rbennell
47

getopt adalah temanmu .. contoh sederhana:

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

Seharusnya ada berbagai contoh di direktori / usr / bin Anda.

Shizzmo
sumber
3
Contoh yang lebih luas dapat ditemukan di direktori /usr/share/doc/util-linux/examples, paling tidak pada mesin Ubuntu.
Serge Stroobandt
10

Saya pikir ini akan menjadi contoh sederhana dari apa yang ingin Anda capai. Tidak perlu menggunakan alat eksternal. Alat Bash bawaan dapat melakukan pekerjaan untuk Anda.

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

Ini akan memungkinkan Anda untuk menggunakan flag sehingga tidak peduli urutan mana yang Anda lewati parameter Anda akan mendapatkan perilaku yang tepat.

Contoh:

 DOSOMETHING -last "Adios" -first "Hola"

Output:

 First argument : Hola
 Last argument : Adios

Anda dapat menambahkan fungsi ini ke profil Anda atau memasukkannya ke dalam skrip.

Terima kasih!

Sunting: Simpan ini sebagai file aa dan kemudian jalankan sebagai 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";
Matias Barrios
sumber
Saya menggunakan kode di atas & ketika menjalankannya tidak mencetak apa pun. ./hello.sh DOSOMETHING -last "Adios" -first "Hola"
dinu0101
@ dinu0101 Ini adalah fungsi. Bukan skrip. Anda harus menggunakannya sebagai DOSIS-terakhir "Adios" -Pertama "Hola"
Matias Barrios
Terima kasih @Matias. Dimengerti cara menjalankan skrip di dalamnya.
dinu0101
1
Terima kasih banyak @Matias
dinu0101
2
Menggunakan return 1;dengan contoh keluaran terakhir can only 'return' from a function or sourced scriptpada macOS. Beralih ke exit 1;pekerjaan seperti yang diharapkan.
Mattias
5

Alternatif lain adalah dengan menggunakan sesuatu seperti contoh di bawah ini yang memungkinkan Anda untuk menggunakan tag --i panjang atau pendek -i dan juga memungkinkan kompilasi -i = "example.jpg" atau metode-metode terpisah lewatii example.jpg untuk menyampaikan argumen .

# 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";
Robert McMahan
sumber
3

Saya suka jawaban Robert McMahan yang terbaik di sini karena tampaknya yang termudah untuk dibuat menjadi file sharable menyertakan untuk skrip Anda untuk digunakan. Tetapi tampaknya memiliki cacat dengan garis if [[ -n ${variables[$argument_label]} ]]melemparkan pesan, "variabel: array array yang buruk". Saya tidak punya perwakilan untuk berkomentar, dan saya ragu ini adalah 'perbaikan' yang tepat, tetapi membungkusnya ifdengan if [[ -n $argument_label ]] ; thenmembersihkannya.

Inilah kode yang akhirnya saya dapatkan, jika Anda tahu cara yang lebih baik, silakan tambahkan komentar pada jawaban Robert.

Sertakan File "flags-declares.sh"

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

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

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

"Script.sh" Anda

. 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";
Michael
sumber
3

Jika Anda terbiasa dengan Python argparse, dan tidak keberatan memanggil python ke parse bash argumen, ada sepotong kode yang saya temukan sangat membantu dan super mudah digunakan disebut argparse-bash https://github.com/nhoffman/ argparse-bash

Contoh ambil dari script example.sh mereka:

#!/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
Linh
sumber
2

Saya mengusulkan TLDR sederhana:; contoh untuk yang belum diinisiasi.

Buat skrip bash bernama helloworld.sh

#!/bin/bash

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

echo "Hello $Name!"

Anda kemudian dapat melewatkan parameter opsional -nsaat menjalankan skrip.

Jalankan script seperti itu:

$ bash helloworld.sh -n 'World'

Keluaran

$ Hello World!

Catatan

Jika Anda ingin menggunakan beberapa parameter:

  1. meluas while getops "n:" arg: dodengan lebih banyak paramaters seperti while getops "n:o:p:" arg: do
  2. memperpanjang sakelar kasus dengan penugasan variabel tambahan. Seperti o) Option=$OPTARGdanp) Parameter=$OPTARG
pijemcolu
sumber
1
#!/bin/bash

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

Simpan sebagai sample.sh dan coba jalankan

sh sample.sh -n John

di terminal Anda.

Nishant Ingle
sumber
1

Saya mengalami masalah menggunakan getop dengan banyak flag, jadi saya menulis kode ini. Ia menggunakan variabel modal untuk mendeteksi flag, dan menggunakan flag tersebut untuk menetapkan argumen ke variabel.

Perhatikan bahwa, jika sebuah bendera tidak boleh memiliki argumen, sesuatu selain pengaturan CURRENTFLAG dapat dilakukan.

    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
Jessica Richards
sumber
0

Jadi ini dia solusinya. Saya ingin dapat menangani boolean flag tanpa tanda hubung, dengan satu tanda hubung, dan dengan dua tanda hubung serta penugasan parameter / nilai dengan satu dan dua tanda hubung.

# 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

Beberapa referensi

  • Prosedur utama ditemukan di sini .
  • Lebih lanjut tentang meneruskan semua argumen ke fungsi di sini .
  • Info selengkapnya tentang nilai default di sini .
  • Info lebih lanjut tentang declaredo $ bash -c "help declare".
  • Info lebih lanjut tentang shiftdo $ bash -c "help shift".
H. Sánchez
sumber