การส่งพารามิเตอร์ไปยังฟังก์ชัน Bash


980

ฉันพยายามค้นหาวิธีการส่งผ่านพารามิเตอร์ในฟังก์ชัน Bash แต่สิ่งที่เกิดขึ้นคือวิธีการส่งผ่านพารามิเตอร์จากบรรทัดคำสั่งเสมอ

ฉันต้องการส่งพารามิเตอร์ภายในสคริปต์ของฉัน ฉันเหนื่อย:

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

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

แต่ไวยากรณ์ไม่ถูกต้องจะส่งพารามิเตอร์ไปยังฟังก์ชันของฉันได้อย่างไร


6
"... แต่สิ่งที่เกิดขึ้นคือวิธีส่งพารามิเตอร์จากบรรทัดคำสั่ง" - ใช่! นั่นเป็นเพราะสคริปต์ Bash นั้นเป็นลำดับของบรรทัดคำสั่ง - เรียกใช้ฟังก์ชันในสคริปต์ Bash เหมือนกับว่าเป็นคำสั่งบนบรรทัดคำสั่ง! :-) สายของคุณจะเป็น myBackupFunction ".. " "... " "xx"; ไม่มีวงเล็บไม่มีคอมม่า
Wil

คำตอบ:


1618

มีวิธีทั่วไปสองวิธีในการประกาศฟังก์ชัน ฉันชอบวิธีที่สอง

function function_name {
   command...
} 

หรือ

function_name () {
   command...
} 

ในการเรียกใช้ฟังก์ชันที่มีอาร์กิวเมนต์:

function_name "$arg1" "$arg2"

ฟังก์ชันอ้างถึงอาร์กิวเมนต์ที่ส่งผ่านตามตำแหน่ง (ไม่ใช่ตามชื่อ) นั่นคือ $ 1, $ 2 และอื่น ๆ $ 0เป็นชื่อของสคริปต์เอง

ตัวอย่าง:

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

นอกจากนี้คุณต้องเรียกใช้ฟังก์ชั่นของคุณหลังจากที่มีการประกาศ

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

เอาท์พุท:

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

อ้างอิง: Advanced ทุบตี Scripting คู่มือ


4
คุณลืมช่องว่างลองfunction name() {}แล้ว อาจด้วย 'ป้อน' ก่อน{}
lalo

21
คำตอบที่ดี. 2 เซ็นต์ของฉัน: ในการสร้างเปลือกหอยที่อยู่ในแฟ้มที่มีที่มา (ประ) เมื่อมีความจำเป็นฉันชอบที่จะใช้functionคำหลักและ ()เป้าหมายของฉัน (ในไฟล์ไม่บรรทัดคำสั่ง) function myBackupFunction() compound-statementคือการเพิ่มความคมชัดไม่ลดจำนวนตัวอักษรที่พิมพ์ลงกล่าวคือ
Terry Gardner

22
@CMCDragonkai functionเวอร์ชันคำหลักเป็นส่วนขยาย รูปแบบอื่นทำงานในเชลล์ที่สอดคล้องกับ POSIX ทั้งหมด
Charles Duffy

8
@TerryGardner พิจารณาว่าความพยายามของคุณในการเพิ่มความคมชัดคือการลดความเข้ากันได้
Charles Duffy

6
@RonBurk บางที - แต่แม้ว่าเราจะพิจารณาความชัดเจนเท่านั้นfunctionคำหลักที่มีการค้ำประกันในเก่าเปลือกหอย ksh ครอบครัวที่นำว่าทุบตีที่ทันสมัยไม่ให้เกียรติ (ในเปลือกหอยดังกล่าวfunctionทำให้ตัวแปรท้องถิ่นโดยค่าเริ่มต้นในการทุบตี , มันไม่ใช่). เช่นการใช้งานจะลดความชัดเจนให้กับทุกคนที่รู้และอาจคาดหวังพฤติกรรม ksh ดูwiki.bash-hackers.org/scripting/obsolete
Charles Duffy

68

ความรู้เกี่ยวกับการเขียนโปรแกรมภาษาระดับสูง (C / C ++ / Java / PHP / Python / Perl ... ) จะแนะนำให้คนธรรมดาฟังก์ชั่นทุบตีควรทำงานเหมือนที่พวกเขาทำในภาษาอื่น ๆ เหล่านั้น แต่ฟังก์ชั่นทุบตีทำงานเช่นคำสั่งเชลล์และคาดว่าข้อโต้แย้งจะถูกส่งผ่านพวกเขาในลักษณะเดียวกับที่หนึ่งอาจส่งผ่านตัวเลือกไปยังคำสั่งเชลล์ (เช่นls -l) ในทางปฏิบัติฟังก์ชันอาร์กิวเมนต์ใน bash จะถือว่าเป็นพารามิเตอร์ตำแหน่ง ( $1, $2..$9, ${10}, ${11}และอื่น ๆ ) นี่ไม่น่าแปลกใจเลยที่พิจารณาว่าgetoptsทำงานอย่างไร อย่าใช้วงเล็บในการเรียกใช้ฟังก์ชันใน bash


( หมายเหตุ : ฉันกำลังทำงานกับ Open Solaris ในขณะนี้)

# 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

ต้องการใช้ชื่อสำหรับตัวแปร เพิ่งทำสิ่งนี้

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

ต้องการส่งผ่านอาร์เรย์ไปยังฟังก์ชั่นหรือไม่?

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

ภายในฟังก์ชั่นจัดการข้อโต้แย้งเช่นนี้

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

ต้องการส่งค่าและอาร์เรย์ แต่ยังคงใช้ "$ @" ภายในฟังก์ชันหรือไม่

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[@]}"

64

หากคุณต้องการพารามิเตอร์ที่มีชื่ออาจเป็นไปได้ (มีลูกเล่นเล็กน้อย) เพื่อส่งผ่านพารามิเตอร์ที่กำหนดชื่อไปยังฟังก์ชั่น (เช่นทำให้สามารถส่งผ่านอาร์เรย์และการอ้างอิงได้)

วิธีที่ฉันพัฒนาช่วยให้คุณสามารถกำหนดพารามิเตอร์ที่มีชื่อส่งผ่านไปยังฟังก์ชันดังนี้:

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

นอกจากนี้คุณยังสามารถใส่คำอธิบายประกอบอาร์กิวเมนต์เป็น @required หรือ @readonly, สร้าง ... อาร์กิวเมนต์ที่เหลือ, สร้างอาร์เรย์จากอาร์กิวเมนต์ลำดับ (โดยใช้เช่นstring[4]) และเลือกรายการอาร์กิวเมนต์ในหลายบรรทัด:

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[*]}"
}

กล่าวอีกนัยหนึ่งไม่เพียง แต่คุณสามารถเรียกพารามิเตอร์ของคุณตามชื่อของพวกเขา (ซึ่งประกอบไปด้วยแกนกลางที่อ่านได้มากขึ้น) คุณสามารถส่งผ่านอาร์เรย์ได้ (และการอ้างอิงถึงตัวแปร - คุณลักษณะนี้ใช้ได้เฉพาะในทุบตี 4.3)! นอกจากนี้ตัวแปรที่แมปนั้นอยู่ในขอบเขตของท้องถิ่นเช่นเดียวกับ $ 1 (และอื่น ๆ )

รหัสที่ทำให้งานนี้ค่อนข้างเบาและใช้ได้ทั้งใน bash 3 และ bash 4 (นี่เป็นรุ่นเดียวที่ฉันทดสอบด้วย) หากคุณสนใจเทคนิคเพิ่มเติมเช่นนี้ที่พัฒนาด้วย bash ที่ดีกว่าและง่ายกว่าคุณสามารถดูBash Infinity Frameworkของฉันได้รหัสด้านล่างนี้เป็นหนึ่งในฟังก์ชันการทำงานของมัน

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

อะไรคือ@var, @reference, @paramsตัวแปร? ฉันควรค้นหาข้อมูลทางอินเทอร์เน็ตเพื่อเรียนรู้เพิ่มเติมเกี่ยวกับเรื่องนี้อย่างไร
GypsyCosmonaut

3
คำตอบที่ดี! ฉันเพิ่งค้นคว้า Bash Infinity และดูเหมือนว่ามันจะเป็นประโยชน์จริงๆ ขอบคุณ!
Jonathan Hult

ขอบคุณ @JonathanHult! ฉันเพิ่งปรับปรุงคำตอบข้างต้นจริง ๆ แล้วเมื่อเร็ว ๆ นี้และตอนนี้ก็เป็นรหัสใหม่ที่ถูกเขียนใหม่และถูกเขียนใหม่ใน Bash Infinity 2.0 เหตุผลที่ฉันเขียนใหม่มันเป็นเพราะข้อผิดพลาดในการใช้งานเก่า (มันเป็นปัญหาใน GitHub) ยังไม่มีเวลาที่จะรวมเวอร์ชันใหม่ แต่กลับเข้าสู่ Bash Infinity ดีใจที่ได้ยินว่าเป็นประโยชน์
niieani

สวัสดี @niieani เมื่อฉันพยายามที่จะสร้างฟังก์ชั่นทุบตีในแบบฟอร์มที่คุณใช้ในคำตอบของคุณมันบอกฉันว่าฉันต้องติดตั้ง ut ut ut ut ut uton จากฉลาด นี่เป็นวิธีที่สคริปต์ทุบตีของคุณทำงานหรือไม่ ฉันทำสิ่งนี้ถูกต้องหรือไม่ หากฉันเข้าใจว่าคุณหรือคนอื่นสร้างโปรแกรม ucommon util เพื่อให้สามารถใช้ Bash ได้ถูกต้องใช่ไหม
David A. French

@ DavidA.French ไม่สิ่งนี้ไม่ควรเกิดขึ้น ไม่มีความสัมพันธ์ระหว่างucommonและรหัสของฉัน อาจเป็นไปได้ว่าคุณมีเครื่องมือบางอย่างติดตั้งอยู่ซึ่งทำให้เกิดปัญหาที่คุณพูดถึง
niieani

27

พลาด parens และเครื่องหมายจุลภาค:

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

และฟังก์ชั่นควรมีลักษณะเช่นนี้:

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

8

ฉันหวังว่าตัวอย่างนี้สามารถช่วยคุณได้ ใช้ตัวเลขสองตัวจากผู้ใช้ป้อนเข้าสู่ฟังก์ชั่นที่เรียกว่าadd(ในบรรทัดสุดท้ายของรหัส) และaddจะสรุปและพิมพ์ออกมา

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

6
การส่งตามชื่อในลักษณะนั้นใช้ได้กับจำนวนเต็มที่ส่งผ่านไปยังตัวดำเนินการที่เป็นตัวเลข (()) และใช้งานได้เพียงเพราะตัวดำเนินการที่เป็นตัวเลขจะแก้ไขสตริงเป็นค่าแบบเรียกซ้ำ หากคุณต้องการทดสอบสิ่งที่ฉันหมายถึงลองป้อน '5' สำหรับ x แล้ว 'x' สำหรับ y แล้วคุณจะเห็นว่ามันเพิ่ม (x + y) = (5 + x) = (5 + 5) = 10 สำหรับกรณีการใช้งานอื่นทั้งหมดตัวอย่างของคุณจะล้มเหลว แต่คุณควรใช้ 'เพิ่ม "$ x" "$ y" "สำหรับรหัสทั่วไป
Wil

6

ตัวอย่างง่ายๆที่จะล้างทั้งในระหว่างการเรียกใช้สคริปต์หรือสคริปต์ภายในในขณะที่เรียกฟังก์ชั่น

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

5

คิดว่าฉันจะพูดถึงวิธีอื่นในการส่งพารามิเตอร์ที่มีชื่อเพื่อทุบตี ... ส่งต่อโดยการอ้างอิง สิ่งนี้ได้รับการสนับสนุนตั้งแต่ทุบตี 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];

ไวยากรณ์ทางเลือกสำหรับ bash 4.3 กำลังใช้nameref

ถึงแม้ว่า nameref จะสะดวกกว่ามากในเรื่องของการปรับความไม่ลงรอยกัน distros ที่รองรับที่เก่ากว่าบางตัวยังคงจัดส่งเวอร์ชั่นที่เก่ากว่าดังนั้นฉันจะยังไม่แนะนำเลย


“ ท่อใน” ฉันรู้ว่าคุณทำอะไรที่นั่น!
Jacktose
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.