Truyền tham số cho hàm Bash


980

Tôi đang cố gắng tìm kiếm cách truyền tham số trong hàm Bash, nhưng điều xuất hiện luôn là làm thế nào để truyền tham số từ dòng lệnh.

Tôi muốn truyền tham số trong tập lệnh của tôi. Tôi đã thử:

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

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

Nhưng cú pháp không đúng, làm thế nào để truyền tham số cho hàm của tôi?


6
"... nhưng những gì xuất hiện luôn là làm thế nào để truyền tham số từ dòng lệnh" - Vâng! Đó là bởi vì các tập lệnh Bash về cơ bản là các chuỗi dòng lệnh - gọi một hàm trong tập lệnh Bash chính xác như thể nó là một lệnh trên dòng lệnh! :-) Cuộc gọi của bạn sẽ là myBackupFunction ".." "..." "xx"; không dấu ngoặc đơn, không dấu phẩy.
Wil

4
Đối tác của câu hỏi này: giá trị trả về từ hàm bash
MSalters

Câu trả lời:


1618

Có hai cách điển hình để khai báo một hàm. Tôi thích cách tiếp cận thứ hai.

function function_name {
   command...
} 

hoặc là

function_name () {
   command...
} 

Để gọi một hàm với các đối số:

function_name "$arg1" "$arg2"

Hàm tham chiếu đến các đối số được truyền theo vị trí của chúng (không phải theo tên), đó là $ 1, $ 2, v.v. $ 0 là tên của chính tập lệnh.

Thí dụ:

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

Ngoài ra, bạn cần gọi hàm của bạn sau khi nó được khai báo.

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

Đầu ra:

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

Tham khảo: Hướng dẫn Bash-Scripting nâng cao .


4
Bạn đã quên không gian, hãy thử function name() {}. Có thể với một 'nhập' trước{}
lalo

21
Câu trả lời tốt. My 2 cents: trong cấu trúc vỏ mà cư trú trong một tập tin mà có nguồn gốc (chấm) khi cần thiết, tôi thích sử dụng các functiontừ khóa các (). Mục tiêu của tôi (trong một tệp, không phải dòng lệnh) là để tăng độ rõ ràng, không làm giảm số lượng ký tự được gõ, viz function myBackupFunction() compound-statement,.
Terry Gardner

22
@CMCDragonkai, functionphiên bản từ khóa là một phần mở rộng; dạng khác hoạt động trong tất cả các shell tương thích POSIX.
Charles Duffy

8
@TerryGardner, hãy xem xét rằng những nỗ lực của bạn để tăng sự rõ ràng đang làm giảm khả năng tương thích.
Charles Duffy

6
@RonBurk, có lẽ - nhưng ngay cả khi chúng tôi chỉ xem xét sự rõ ràng, functiontừ khóa đã đảm bảo trong các vỏ gia đình ksh cũ đã giới thiệu rằng bash hiện đại không tôn trọng (trong các shell như vậy, functionđã tạo các biến cục bộ theo mặc định; trong bash , nó không). Như vậy, việc sử dụng nó làm giảm sự rõ ràng cho bất cứ ai biết và có thể mong đợi, hành vi ksh. Xem wiki.bash-hackers.org/scripting/obsolete
Charles Duffy

70

Kiến thức về các ngôn ngữ lập trình cấp cao (C / C ++ / Java / PHP / Python / Perl ...) sẽ gợi ý cho cư sĩ rằng các hàm bash sẽ hoạt động giống như các ngôn ngữ khác. Thay vào đó , các hàm bash hoạt động giống như các lệnh shell và mong muốn các đối số được truyền cho chúng theo cùng một cách mà người ta có thể chuyển một tùy chọn cho lệnh shell (ví dụ ls -l). Trong thực tế, các đối số hàm trong bash được coi là tham số vị trí ( $1, $2..$9, ${10}, ${11}v.v.). Điều này không có gì ngạc nhiên khi xem xét cách làm getoptsviệc. Không sử dụng dấu ngoặc đơn để gọi một hàm trong bash.


( Lưu ý : Hiện tại tôi đang làm việc trên 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

Muốn sử dụng tên cho các biến. Chỉ cần làm điều này.

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

Bạn muốn truyền một mảng cho một hàm?

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

Bên trong hàm, xử lý các đối số như thế này.

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

Cần truyền một giá trị và một mảng, nhưng vẫn sử dụng "$ @" bên trong hàm?

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

Nếu bạn thích các tham số được đặt tên, có thể (với một vài thủ thuật) thực sự chuyển các tham số được đặt tên cho các hàm (cũng có thể truyền các mảng và tham chiếu).

Phương thức tôi đã phát triển cho phép bạn xác định các tham số được đặt tên được truyền cho một hàm như thế này:

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

Bạn cũng có thể chú thích các đối số là @required hoặc @readonly, tạo ... phần còn lại đối số, tạo mảng từ các đối số tuần tự (sử dụng ví dụ string[4]) và liệt kê tùy chọn các đối số trong nhiều dòng:

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

Nói cách khác, không chỉ bạn có thể gọi các tham số của mình bằng tên của chúng (mà tạo ra lõi dễ đọc hơn), bạn thực sự có thể truyền các mảng (và tham chiếu đến các biến - mặc dù tính năng này chỉ hoạt động trong bash 4.3)! Thêm vào đó, các biến được ánh xạ đều nằm trong phạm vi cục bộ, chỉ là $ 1 (và các biến khác).

Mã làm cho công việc này khá nhẹ và hoạt động cả trong bash 3 và bash 4 (đây là những phiên bản duy nhất tôi đã thử nghiệm với nó). Nếu bạn quan tâm đến nhiều thủ thuật như thế này giúp phát triển bash đẹp hơn và dễ dàng hơn, bạn có thể xem Bash Infinity Framework của tôi , mã dưới đây có sẵn như là một trong những chức năng của nó.

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

Là gì @var, @reference, @paramsbiến? Tôi nên tìm kiếm gì trên internet để tìm hiểu thêm về điều này?
GypsyCosmonaut

3
Câu trả lời chính xác! Tôi mới nghiên cứu Bash Infinity và có vẻ như nó sẽ thực sự hữu ích. Cảm ơn!
Jonathan Hult

Cảm ơn @JonathanHult! Tôi thực sự đã cập nhật câu trả lời trên của tôi gần đây và bây giờ nó là một đoạn mã mới hơn, được viết lại thành mã hiện tại trong Bash Infinity 2.0. Lý do tôi viết lại là do lỗi trong triển khai cũ (đó là vấn đề trên GitHub). Haven đã không có thời gian để tích hợp phiên bản mới trở lại vào Bash Infinity. Vui mừng khi biết nó rất hữu ích.
niieani

Xin chào @niieani khi tôi cố gắng tạo một hàm bash theo dạng bạn sử dụng trong câu trả lời của bạn, nó cho tôi biết tôi cần cài đặt các tiện ích ucommon từ apt. Đây có phải là cách tập lệnh bash của bạn hoạt động? Tôi đang làm điều này một cách chính xác? Nếu tôi hiểu bạn hoặc người khác về cơ bản đã xây dựng chương trình sử dụng ucommon để cho phép mở rộng Bash, đúng không?
David A. Pháp

@ DavidA. Không, điều này không nên xảy ra. Không có mối quan hệ giữa ucommonvà mã của tôi. Có thể bạn đã cài đặt một số công cụ gây ra sự cố mà bạn đã đề cập, không biết nó có thể là gì.
niieani

27

Bỏ lỡ các dấu phẩy và dấu phẩy:

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

và chức năng sẽ trông như thế này:

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

8

Tôi hy vọng ví dụ này có thể giúp bạn. Nó lấy hai số từ người dùng, đưa chúng vào hàm được gọi add(ở dòng cuối cùng của mã) và addsẽ tổng hợp chúng và in chúng.

#!/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
Truyền theo tên theo cách đó chỉ hoạt động đối với các số nguyên được truyền vào toán tử số (()) và nó chỉ hoạt động vì toán tử số giải quyết đệ quy các chuỗi thành các giá trị. Nếu bạn muốn kiểm tra ý tôi là gì, hãy thử nhập '5' cho x và sau đó 'x' cho y và bạn sẽ thấy nó thêm (x + y) = (5 + x) = (5 + 5) = 10. Đối với tất cả các trường hợp sử dụng khác, ví dụ của bạn sẽ thất bại. Thay vào đó, bạn nên sử dụng 'thêm "$ x" "$ y"' cho mã chung.
Wil

6

Một ví dụ đơn giản sẽ xóa cả hai trong khi thực thi tập lệnh hoặc bên trong tập lệnh trong khi gọi một hàm.

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

Tôi nghĩ rằng tôi sẽ đề cập đến một cách khác để chuyển các tham số được đặt tên sang bash ... chuyển qua tham chiếu. Điều này được hỗ trợ kể từ 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];

Một cú pháp thay thế cho bash 4.3 đang sử dụng một nameref

Mặc dù nameref thuận tiện hơn rất nhiều ở chỗ nó liền mạch, nhưng một số bản phân phối được hỗ trợ cũ hơn vẫn gửi phiên bản cũ hơn nên tôi sẽ không đề xuất nó.


Đường ống tại thành phố. Tôi thấy những gì bạn đã làm ở đó!
Jacktose
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.