Przekazywanie parametrów do funkcji Bash

980

Próbuję wyszukać, jak przekazać parametry w funkcji Bash, ale zawsze pojawia się sposób przekazania parametru z wiersza polecenia.

Chciałbym przekazać parametry w moim skrypcie. Próbowałem:

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

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

Ale składnia jest nieprawidłowa, jak przekazać parametr do mojej funkcji?

stivlo
źródło
6
„... ale pojawia się zawsze to, jak przekazać parametr z wiersza poleceń” - Tak! Wynika to z faktu, że skrypty Bash są w zasadzie sekwencjami wierszy poleceń - wywołują funkcję w skrypcie Bash dokładnie tak, jakby była poleceniem w wierszu poleceń! :-) Twoje wywołanie to myBackupFunction ".." "... ..." "xx"; bez nawiasów, bez przecinków.
Wil,
4
Odpowiednik tego pytania: zwraca wartość z funkcji bash
MSalters

Odpowiedzi:

1618

Istnieją dwa typowe sposoby deklarowania funkcji. Wolę drugie podejście.

function function_name {
   command...
} 

lub

function_name () {
   command...
} 

Aby wywołać funkcję z argumentami:

function_name "$arg1" "$arg2"

Funkcja odnosi się do przekazanych argumentów według ich pozycji (a nie nazwy), czyli 1 $, 2 $ i tak dalej. $ 0 to nazwa samego skryptu.

Przykład:

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

Ponadto musisz wywołać swoją funkcję po jej zadeklarowaniu.

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

Wynik:

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

Odsyłacz: Przewodnik zaawansowanego skryptu bash .

dogbane
źródło
4
Zapomniałeś przestrzeni, spróbuj function name() {}. Może z „enter” przed{}
lalo
21
Dobra odpowiedź. Moje 2 centy: skorupek konstruktów, które znajdują się w pliku, który jest pozyskiwany (przerywana), gdy są potrzebne, wolę używać functionsłowa kluczowego i() . Moim celem (w pliku, a nie w wierszu poleceń) jest zwiększenie przejrzystości, a nie zmniejszenie liczby wpisywanych znaków, a mianowicie function myBackupFunction() compound-statement.
Terry Gardner
22
@CMCDragonkai, functionwersja słowa kluczowego jest rozszerzeniem; druga forma działa we wszystkich powłokach zgodnych z POSIX.
Charles Duffy
8
@TerryGardner, pomyśl, że twoje próby zwiększenia przejrzystości zmniejszają kompatybilność.
Charles Duffy
6
@RonBurk, być może - ale nawet jeśli weźmiemy pod uwagę tylko klarowność, functionsłowo kluczowe miało gwarancje w starych powłokach rodziny ksh, które wprowadziły go, że współczesne bash nie honoruje (w takich powłokach functiondomyślnie zmienne były lokalne; w bash , to nie). Jako takie, jego użycie zmniejsza jasność dla każdego, kto zna i może się spodziewać zachowania ksh. Zobacz wiki.bash-hackers.org/scripting/obsolete
Charles Duffy
68

Znajomość języków programowania wysokiego poziomu (C / C ++ / Java / PHP / Python / Perl ...) zasugeruje laikowi, że funkcje bash powinny działać tak, jak w innych językach. Zamiast tego funkcje bash działają jak polecenia powłoki i oczekują, że zostaną im przekazane argumenty w taki sam sposób, w jaki można przekazać opcję do polecenia powłoki (np ls -l.). W efekcie argumenty funkcji w bash są traktowane jako parametry pozycyjne ( $1, $2..$9, ${10}, ${11}i tak dalej). Nie jest to zaskoczeniem, biorąc pod uwagę sposób getoptsdziałania. Nie używaj nawiasów do wywoływania funkcji w bash.


( Uwaga : w tej chwili pracuję nad 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

Chcesz używać nazw dla zmiennych. Po prostu to zrób.

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

Chcesz przekazać tablicę do funkcji?

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

Wewnątrz funkcji obsłuż takie argumenty.

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

Chcesz przekazać wartość i tablicę, ale nadal używać „$ @” wewnątrz funkcji?

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
źródło
64

Jeśli wolisz parametry nazwane, możliwe jest (z kilkoma sztuczkami) przekazanie nazwanych parametrów do funkcji (umożliwia także przekazywanie tablic i referencji).

Opracowana przeze mnie metoda pozwala zdefiniować nazwane parametry przekazywane do funkcji takiej jak ta:

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

Możesz także dodawać adnotacje do argumentów jako @required lub @readonly, tworzyć ... pozostałe argumenty, tworzyć tablice z argumentów sekwencyjnych (używając np. string[4]) I opcjonalnie wyświetlać argumenty w wielu wierszach:

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

Innymi słowy, nie tylko możesz wywoływać swoje parametry według ich nazw (co stanowi bardziej czytelny rdzeń), możesz faktycznie przekazywać tablice (i odniesienia do zmiennych - ta funkcja działa jednak tylko w bash 4.3)! Ponadto wszystkie zmapowane zmienne są w zasięgu lokalnym, podobnie jak 1 USD (i inne).

Kod, który sprawia, że ​​ta praca jest dość lekka i działa zarówno w bash 3, jak i bash 4 (to jedyne wersje, z którymi go testowałem). Jeśli interesuje Cię więcej takich sztuczek, które sprawiają, że programowanie w bash jest znacznie przyjemniejsze i łatwiejsze, możesz rzucić okiem na mój Bash Infinity Framework , poniższy kod jest dostępny jako jedna z jego funkcjonalności.

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
źródło
Jakie są @var, @reference, @paramszmienne? Co powinienem sprawdzić w Internecie, aby dowiedzieć się więcej na ten temat?
GypsyCosmonaut
3
Świetna odpowiedź! Właśnie zbadałem Bash Infinity i wygląda na to, że będzie to naprawdę pomocne. Dzięki!
Jonathan Hult
Dzięki @JonathanHult! Ostatnio zaktualizowałem powyższą odpowiedź i teraz jest to nowszy, przepisany fragment kodu do tego, który jest obecnie w Bash Infinity 2.0. Powodem, dla którego go przepisałem, jest błąd w starej implementacji (dotyczy problemów na GitHub). Nie miałem czasu na integrację nowej wersji z powrotem w Bash Infinity. Cieszę się, że to było pomocne.
niieani
Cześć @niieani, kiedy próbuję utworzyć funkcję bash w formie, której używasz w odpowiedzi, mówi mi, że muszę zainstalować nietypowe narzędzia z apt. Czy tak działa twój skrypt bash? Czy robię to poprawnie? Jeśli rozumiem, że ty lub ktoś inny w zasadzie zbudowałeś niezwykły program narzędziowy, aby umożliwić rozszerzenie Bash, prawda?
David A. French
@ DavidA.French nie, to nie powinno się zdarzyć. Nie ma związku między ucommonmoim kodem a nim. Możliwe, że masz zainstalowane jakieś narzędzie, które powoduje wspomniany problem, nie mam pojęcia, co to może być.
niieani
27

Pomiń pareny i przecinki:

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

a funkcja powinna wyglądać następująco:

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

źródło
8

Mam nadzieję, że ten przykład może ci pomóc. Pobiera od użytkownika dwie liczby, podaje je do wywołanej funkcji add(w ostatnim wierszu kodu), addpodsumowuje je i drukuje.

#!/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.
źródło
6
Przekazywanie przez nazwę w ten sposób działa tylko dla liczb całkowitych przekazanych do operatora numerycznego (()) i działa tylko dlatego, że operator liczbowy rekurencyjnie rozpoznaje ciągi znaków na wartości. Jeśli chcesz przetestować, co mam na myśli, spróbuj wpisać „5” dla x, a następnie „x” dla y, a zobaczysz, że dodaje (x + y) = (5 + x) = (5 + 5) = 10. Dla wszystkich innych przypadków użycia twój przykład się nie powiedzie. Zamiast tego należy użyć „dodaj” $ x ”„ $ y ”dla kodu ogólnego.
Wil,
6

Prosty przykład, który wyczyści zarówno podczas wykonywania skryptu, jak i wewnątrz skryptu podczas wywoływania funkcji.

#!/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
Adii
źródło
5

Pomyślałem, że wprowadzę się, wspominając o innym sposobie przekazywania nazwanych parametrów do bash ... przechodząc przez referencję. Jest to obsługiwane od wersji 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];

Alternatywną składnią dla bash 4.3 jest użycie nazwy-nazwy

Chociaż nameref jest o wiele wygodniejszy, ponieważ bezproblemowo usuwa dereferencje, niektóre starsze obsługiwane dystrybucje wciąż wysyłają starszą wersję, więc jeszcze go nie polecę.

Wil
źródło
„Wpuść”. Widzę, co tu zrobiłeś!
Jacktose