Melewati parameter ke fungsi Bash

981

Saya mencoba mencari cara melewatkan parameter dalam fungsi Bash, tetapi yang muncul selalu bagaimana cara melewatkan parameter dari baris perintah.

Saya ingin meneruskan parameter dalam skrip saya. Saya mencoba:

myBackupFunction("..", "...", "xx")

function myBackupFunction($directory, $options, $rootPassword) {
     ...
}

Tapi sintaksnya tidak benar, bagaimana cara melewatkan parameter ke fungsi saya?

stivlo
sumber
6
"... tapi yang muncul selalu bagaimana cara melewati parameter dari baris perintah" - Ya! Itu karena skrip Bash pada dasarnya adalah urutan dari baris perintah - menjalankan fungsi dalam skrip Bash persis seolah-olah itu adalah perintah pada baris perintah! :-) Telepon Anda akan menjadi fungsi myBackupFungsi ".." "..." "xx"; tidak ada tanda kurung, tidak ada koma.
Wil
4
Rekan untuk pertanyaan ini: mengembalikan nilai dari fungsi bash
MSalters

Jawaban:

1619

Ada dua cara khas untuk mendeklarasikan suatu fungsi. Saya lebih suka pendekatan kedua.

function function_name {
   command...
} 

atau

function_name () {
   command...
} 

Untuk memanggil fungsi dengan argumen:

function_name "$arg1" "$arg2"

Fungsi mengacu pada argumen yang diteruskan oleh posisi mereka (bukan dengan nama), yaitu $ 1, $ 2, dan sebagainya. $ 0 adalah nama skrip itu sendiri.

Contoh:

function_name () {
   echo "Parameter #1 is $1"
}

Juga, Anda perlu memanggil fungsi Anda setelah dideklarasikan.

#!/usr/bin/env sh

foo 1  # this will fail because foo has not been declared yet.

foo() {
    echo "Parameter #1 is $1"
}

foo 2 # this will work.

Keluaran:

./myScript.sh: line 2: foo: command not found
Parameter #1 is 2

Referensi: Lanjutan Bash-Scripting Guide .

dogbane
sumber
4
Anda lupa ruang, coba function name() {}. Mungkin dengan 'masuk' sebelum{}
lalo
21
Jawaban yang bagus. My 2 cents: dalam konstruksi shell yang berada dalam file yang bersumber (titik-titik) bila diperlukan, saya lebih suka menggunakan functionkata kunci dan yang (). Tujuan saya (dalam file, bukan baris perintah) adalah untuk meningkatkan kejelasan, bukan mengurangi jumlah karakter yang diketik, yaitu function myBackupFunction() compound-statement,.
Terry Gardner
22
@ CMCDragonkai, functionversi kata kunci adalah ekstensi; formulir lain berfungsi di semua shell yang sesuai dengan POSIX.
Charles Duffy
8
@TerryGardner, pertimbangkan bahwa upaya Anda untuk meningkatkan kejelasan mengurangi kompatibilitas.
Charles Duffy
6
@RonBurk, mungkin - tetapi bahkan jika kami hanya mempertimbangkan kejelasan, functionkata kunci memiliki jaminan di shell ksh-family lama yang memperkenalkannya bahwa bash modern tidak menghormati (dalam shell seperti itu, functionmembuat variabel lokal-secara-default; dalam bash , itu tidak). Dengan demikian, penggunaannya mengurangi kejelasan bagi siapa saja yang tahu, dan mungkin mengharapkan, perilaku ksh. Lihat wiki.bash-hackers.org/scripting/obsolete
Charles Duffy
70

Pengetahuan tentang bahasa pemrograman tingkat tinggi (C / C ++ / Java / PHP / Python / Perl ...) akan menyarankan kepada orang awam bahwa fungsi bash harus bekerja seperti yang mereka lakukan dalam bahasa lain. Sebagai gantinya , fungsi bash bekerja seperti perintah shell dan mengharapkan argumen untuk diteruskan kepada mereka dengan cara yang sama orang mungkin memberikan opsi ke perintah shell (misalnya ls -l). Akibatnya, argumen fungsi di bash diperlakukan sebagai parameter posisi ( $1, $2..$9, ${10}, ${11}, dan seterusnya). Ini tidak mengherankan mengingat cara getoptskerjanya. Jangan gunakan tanda kurung untuk memanggil fungsi dalam bash.


( Catatan : Kebetulan saya sedang mengerjakan Open Solaris saat ini.)

# bash style declaration for all you PHP/JavaScript junkies. :-)
# $1 is the directory to archive
# $2 is the name of the tar and zipped file when all is done.
function backupWebRoot ()
{
    tar -cvf - $1 | zip -n .jpg:.gif:.png $2 - 2>> $errorlog &&
        echo -e "\nTarball created!\n"
}


# sh style declaration for the purist in you. ;-)
# $1 is the directory to archive
# $2 is the name of the tar and zipped file when all is done.
backupWebRoot ()
{
    tar -cvf - $1 | zip -n .jpg:.gif:.png $2 - 2>> $errorlog &&
        echo -e "\nTarball created!\n"
}


# In the actual shell script
# $0               $1            $2

backupWebRoot ~/public/www/ webSite.tar.zip

Ingin menggunakan nama untuk variabel. Lakukan saja ini.

declare filename=$1 # declare gives you more options and limits variable scope

Ingin meneruskan array ke suatu fungsi?

callingSomeFunction "${someArray[@]}" # Expands to all array elements.

Di dalam fungsinya, tangani argumen seperti ini.

function callingSomeFunction ()
{
    for value in "$@" # You want to use "$@" here, not "$*" !!!!!
    do
        :
    done
}

Perlu meneruskan nilai dan array, tetapi masih menggunakan "$ @" di dalam fungsi?

function linearSearch ()
{
    declare myVar="$1"

    shift 1 # removes $1 from the parameter list

    for value in "$@" # Represents the remaining parameters.
    do
        if [[ $value == $myVar ]]
        then
            echo -e "Found it!\t... after a while."
            return 0
        fi
    done

    return 1
}

linearSearch $someStringValue "${someArray[@]}"
Anthony Rutledge
sumber
64

Jika Anda lebih suka parameter bernama, dimungkinkan (dengan beberapa trik) untuk benar-benar meneruskan parameter bernama ke fungsi (juga memungkinkan untuk melewatkan array dan referensi).

Metode yang saya kembangkan memungkinkan Anda untuk menentukan parameter bernama yang diteruskan ke fungsi seperti ini:

function example { args : string firstName , string lastName , integer age } {
  echo "My name is ${firstName} ${lastName} and I am ${age} years old."
}

Anda juga dapat membuat anotasi argumen sebagai @required atau @readonly, membuat ... sisanya argumen, membuat array dari argumen sekuensial (menggunakan misal string[4]) dan secara opsional daftar argumen dalam beberapa baris:

function example {
  args
    : @required string firstName
    : string lastName
    : integer age
    : string[] ...favoriteHobbies

  echo "My name is ${firstName} ${lastName} and I am ${age} years old."
  echo "My favorite hobbies include: ${favoriteHobbies[*]}"
}

Dengan kata lain, tidak hanya Anda dapat memanggil parameter Anda dengan namanya (yang merupakan inti yang lebih mudah dibaca), Anda sebenarnya dapat melewatkan array (dan referensi ke variabel - fitur ini hanya bekerja di bash 4.3)! Plus, variabel yang dipetakan semuanya berada dalam lingkup lokal, sama seperti $ 1 (dan lainnya).

Kode yang membuat pekerjaan ini cukup ringan dan berfungsi baik di bash 3 dan bash 4 (ini adalah satu-satunya versi yang telah saya uji dengan). Jika Anda tertarik dengan lebih banyak trik seperti ini yang membuat pengembangan dengan bash jauh lebih baik dan lebih mudah, Anda dapat melihat pada Bash Infinity Framework saya , kode di bawah ini tersedia sebagai salah satu fungsinya.

shopt -s expand_aliases

function assignTrap {
  local evalString
  local -i paramIndex=${__paramIndex-0}
  local initialCommand="${1-}"

  if [[ "$initialCommand" != ":" ]]
  then
    echo "trap - DEBUG; eval \"${__previousTrap}\"; unset __previousTrap; unset __paramIndex;"
    return
  fi

  while [[ "${1-}" == "," || "${1-}" == "${initialCommand}" ]] || [[ "${#@}" -gt 0 && "$paramIndex" -eq 0 ]]
  do
    shift # first colon ":" or next parameter's comma ","
    paramIndex+=1
    local -a decorators=()
    while [[ "${1-}" == "@"* ]]
    do
      decorators+=( "$1" )
      shift
    done

    local declaration=
    local wrapLeft='"'
    local wrapRight='"'
    local nextType="$1"
    local length=1

    case ${nextType} in
      string | boolean) declaration="local " ;;
      integer) declaration="local -i" ;;
      reference) declaration="local -n" ;;
      arrayDeclaration) declaration="local -a"; wrapLeft= ; wrapRight= ;;
      assocDeclaration) declaration="local -A"; wrapLeft= ; wrapRight= ;;
      "string["*"]") declaration="local -a"; length="${nextType//[a-z\[\]]}" ;;
      "integer["*"]") declaration="local -ai"; length="${nextType//[a-z\[\]]}" ;;
    esac

    if [[ "${declaration}" != "" ]]
    then
      shift
      local nextName="$1"

      for decorator in "${decorators[@]}"
      do
        case ${decorator} in
          @readonly) declaration+="r" ;;
          @required) evalString+="[[ ! -z \$${paramIndex} ]] || echo \"Parameter '$nextName' ($nextType) is marked as required by '${FUNCNAME[1]}' function.\"; " >&2 ;;
          @global) declaration+="g" ;;
        esac
      done

      local paramRange="$paramIndex"

      if [[ -z "$length" ]]
      then
        # ...rest
        paramRange="{@:$paramIndex}"
        # trim leading ...
        nextName="${nextName//\./}"
        if [[ "${#@}" -gt 1 ]]
        then
          echo "Unexpected arguments after a rest array ($nextName) in '${FUNCNAME[1]}' function." >&2
        fi
      elif [[ "$length" -gt 1 ]]
      then
        paramRange="{@:$paramIndex:$length}"
        paramIndex+=$((length - 1))
      fi

      evalString+="${declaration} ${nextName}=${wrapLeft}\$${paramRange}${wrapRight}; "

      # continue to the next param:
      shift
    fi
  done
  echo "${evalString} local -i __paramIndex=${paramIndex};"
}

alias args='local __previousTrap=$(trap -p DEBUG); trap "eval \"\$(assignTrap \$BASH_COMMAND)\";" DEBUG;'
niieani
sumber
Apa @var, @reference, @paramsvariabel? Apa yang harus saya cari di internet untuk mempelajari lebih lanjut tentang ini?
GypsyCosmonaut
3
Jawaban bagus! Saya baru saja meneliti Bash Infinity dan sepertinya itu akan sangat membantu. Terima kasih!
Jonathan Hult
Terima kasih @JonathanHult! Saya sebenarnya telah memperbarui jawaban saya di atas baru-baru ini, dan sekarang ini adalah kode yang lebih baru, ditulis ulang dengan kode yang saat ini ada di Bash Infinity 2.0. Alasan saya menulis ulang itu adalah karena bug dalam implementasi lama (itu dalam masalah di GitHub). Belum sempat mengintegrasikan versi baru namun kembali ke Bash Infinity. Senang mendengarnya sudah membantu.
niieani
Hai @niieani ketika saya mencoba membuat fungsi bash dalam bentuk yang Anda gunakan dalam jawaban Anda, ini memberitahu saya bahwa saya perlu menginstal utuc ucommon dari apt. Apakah ini cara kerja bash script Anda? Apakah saya melakukan ini dengan benar? Jika saya mengerti Anda atau orang lain pada dasarnya membangun program util ucommon untuk memungkinkan perpanjangan Bash, benar?
David A. Prancis
@ DavidA.French tidak, ini seharusnya tidak terjadi. Tidak ada hubungan antara ucommondan kode saya. Mungkin Anda memiliki beberapa alat yang diinstal yang menyebabkan masalah yang Anda sebutkan, tidak tahu apa itu.
niieani
27

Kehilangan paren dan koma:

 myBackupFunction ".." "..." "xx"

dan fungsinya akan terlihat seperti ini:

function myBackupFunction() {
   # here $1 is the first parameter, $2 the second etc.
}

sumber
8

Saya harap contoh ini dapat membantu Anda. Dibutuhkan dua angka dari pengguna, mengumpankannya ke fungsi yang disebut add(pada baris terakhir dari kode), dan addakan menjumlahkannya dan mencetaknya.

#!/bin/bash

read -p "Enter the first  value: " x
read -p "Enter the second value: " y

add(){
    arg1=$1 #arg1 gets to be the first  assigned argument (note there are no spaces)
    arg2=$2 #arg2 gets to be the second assigned argument (note there are no spaces)

    echo $(($arg1 + $arg2))
}

add x y #feeding the arguments
Milad P.
sumber
6
Melewati nama dengan cara itu hanya berfungsi untuk bilangan bulat yang diteruskan ke operator numerik (()), dan itu hanya berfungsi karena operator numerik secara rekursif menyelesaikan string ke nilai. Jika Anda ingin menguji apa yang saya maksud, coba masukkan '5' untuk x dan kemudian 'x' untuk y dan Anda akan melihat bahwa itu menambahkan (x + y) = (5 + x) = (5 + 5) = 10. Untuk semua kasus penggunaan lainnya, contoh Anda akan gagal. Sebagai gantinya Anda harus menggunakan 'add "$ x" "$ y"' untuk kode generik.
Wil
6

Contoh sederhana yang akan dihapus saat menjalankan skrip atau skrip di dalam saat memanggil fungsi.

#!/bin/bash
echo "parameterized function example"
function print_param_value(){
    value1="${1}" # $1 represent first argument
    value2="${2}" # $2 represent second argument
    echo "param 1 is  ${value1}" #as string
    echo "param 2 is ${value2}"
    sum=$(($value1+$value2)) #process them as number
    echo "The sum of two value is ${sum}"
}
print_param_value "6" "4" #space sparted value
#you can also pass paramter durign executing script
print_param_value "$1" "$2" #parameter $1 and $2 during executing

#suppose our script name is param_example
# call like this 
# ./param_example 5 5
# now the param will be $1=5 and $2=5
Adiii
sumber
5

Pikir saya akan pipa dengan menyebutkan cara lain untuk melewati parameter bernama ke bash ... lewat referensi. Ini didukung pada bash 4.0

#!/bin/bash
function myBackupFunction(){ # directory options destination filename
local directory="$1" options="$2" destination="$3" filename="$4";
  echo "tar cz ${!options} ${!directory} | ssh root@backupserver \"cat > /mnt/${!destination}/${!filename}.tgz\"";
}

declare -A backup=([directory]=".." [options]="..." [destination]="backups" [filename]="backup" );

myBackupFunction backup[directory] backup[options] backup[destination] backup[filename];

Sintaks alternatif untuk bash 4.3 menggunakan a nameref

Meskipun nameref jauh lebih nyaman karena dereferences mulus, beberapa distro yang didukung lebih tua masih mengirimkan versi yang lebih tua sehingga saya tidak akan merekomendasikannya.

Wil
sumber
"Pipa masuk". Saya melihat apa yang Anda lakukan di sana!
Jacktose