Передача параметров в функцию Bash

981

Я пытаюсь найти способ передачи параметров в функции Bash, но всегда возникает вопрос о том, как передать параметр из командной строки.

Я хотел бы передать параметры в моем скрипте. Я старался:

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

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

Но синтаксис не правильный, как передать параметр моей функции?

stivlo
источник
6
«... но всегда нужно передать параметр из командной строки» - да! Это потому, что скрипты Bash - это, в основном, последовательности командных строк - вызывайте функцию в скрипте Bash точно так же, как если бы это была команда в командной строке! :-) Ваш вызов будет myBackupFunction ".." "..." "xx"; без скобок, без запятых.
Уил
4
Аналог

Ответы:

1619

Есть два типичных способа объявления функции. Я предпочитаю второй подход.

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

Ссылка: Расширенное руководство по написанию сценариев .

кендырь
источник
4
Вы забыли пробелы, попробуйте function name() {}. Может быть, с «войти» раньше{}
lalo
21
Хороший ответ. Мои 2 цента: в скорлупе конструкций , которые находятся в файле , который добыт (пунктир) , когда это необходимо, я предпочитаю использовать functionключевое слово и() . Моя цель (в файле, а не в командной строке) - повысить ясность, а не уменьшить количество набираемых символов, а именно function myBackupFunction() compound-statement.
Терри Гарднер
22
@CMCDragonkai, functionверсия ключевого слова является расширением; другая форма работает во всех POSIX-совместимых оболочках.
Чарльз Даффи
8
@TerryGardner, учтите, что ваши попытки повысить ясность снижают совместимость.
Чарльз Даффи
6
@RonBurk, возможно - но даже если мы рассмотрим только ясность, functionв старых оболочках семейства ksh, которые ввели это ключевое слово, были гарантии того, что современный bash не соблюдает (в таких оболочках functionпеременные локальные по умолчанию; в bash , Это не). Таким образом, его использование снижает ясность для любого, кто знает и может ожидать поведения ksh. См. Wiki.bash-hackers.org/scripting/obsolete
Чарльз Даффи
70

Знание языков программирования высокого уровня (C / C ++ / Java / PHP / Python / Perl ...) подсказывает непрофессионалу, что функции bash должны работать так же, как и в других языках. Вместо этого функции bash работают подобно командам оболочки и ожидают, что аргументы будут переданы им таким же образом, как можно передать параметр команде оболочки (например 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, создавать аргументы ... rest, создавать массивы из последовательных аргументов (используя, например 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[*]}"
}

Другими словами, вы можете не только вызывать ваши параметры по их именам (что делает ядро ​​более читабельным), вы также можете передавать массивы (и ссылки на переменные - хотя эта функция работает только в bash 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;'
niieani
источник
Каковы @var, @reference, @paramsпеременные? Что я должен искать в Интернете, чтобы узнать больше об этом?
GypsyCosmonaut
3
Отличный ответ! Я только что исследовал Bash Infinity, и похоже, что это будет очень полезно. Спасибо!
Джонатан Халт
Спасибо @JonathanHult! Я на самом деле недавно обновил свой ответ выше, и теперь он является более новым, переписанным фрагментом кода, который в настоящее время находится в Bash Infinity 2.0. Причина, по которой я переписал это из-за ошибки в старой реализации (она есть в проблемах на GitHub). Еще не было времени интегрировать новую версию в Bash Infinity. Рад слышать, что это было полезно.
niieani
Привет @niieani, когда я пытаюсь создать функцию bash в форме, которую вы используете в своем ответе, она говорит мне, что мне нужно установить утилиты ucommon из apt. Так работает ваш bash-скрипт? Я делаю это правильно? Если я понимаю, что вы или кто-то еще в основном создали утилиту ucommon для расширения Bash, правильно?
Дэвид А. Френч
@ DavidA. Французский нет, этого не должно быть. Там нет никакого отношения между ucommonи моим кодом. Возможно, у вас установлен какой-то инструмент, вызывающий упомянутую вами проблему, не знаю, что это может быть.
niieani
27

Пропустите скобки и запятые:

 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. Для всех других вариантов использования ваш пример потерпит неудачу. Вместо этого вы должны использовать 'add "$ x" "$ y" "для общего кода.
Уил
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
Adiii
источник
5

Я подумал, что я расскажу о другом способе передачи именованных параметров в bash ... по ссылке. Это поддерживается начиная с 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];

Альтернативный синтаксис для bash 4.3 - использование nameref

Хотя nameref намного удобнее в том смысле, что оно легко разыменовывается, некоторые старые поддерживаемые дистрибутивы все еще поставляют более старую версию, поэтому я пока не буду рекомендовать ее.

Wil
источник
«Труба в». Я вижу что ты тут делал!
Джектоз