Передача параметрів функції Bash


980

Я намагаюся шукати, як передавати параметри у функції Bash, але все, що виходить - це завжди, як передавати параметр з командного рядка.

Я хотів би передати параметри всередині свого сценарію. Я намагався:

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

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

Але синтаксис невірний, як передати параметр моїй функції?


6
"... але завжди з'являється, як передавати параметр з командного рядка" - Так! Це тому, що сценарії Bash - це в основному послідовності командних рядків - викликайте функцію в сценарії Bash точно так, як ніби це була команда в командному рядку! :-) Ваш дзвінок буде myBackupFunction ".." "..." "xx"; немає дужок, ніяких коми.
Віл

4
Відповідник цьому питанню: повернення значення з функції bash
MSalters

Відповіді:


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

Довідка: Розширений посібник із написання сценарію .


4
Ви забули місця, спробуйте function name() {}. Можливо, з 'enter' раніше{}
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
Чарльз Даффі

68

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

Іншими словами, не тільки ви можете називати свої параметри за їхніми іменами (що становить більш читабельне ядро), ви можете фактично передавати масиви (і посилання на змінні - ця функція працює лише в 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;'

Які @var, @reference, @paramsзмінні? Що мені слід шукати в Інтернеті, щоб дізнатися більше про це?
GypsyCosmonaut

3
Чудова відповідь! Я щойно досліджував Bash Infinity, і, схоже, це буде дуже корисно. Дякую!
Джонатан Хульт

Дякую @JonathanHult! Я фактично оновив свою вищезазначену відповідь недавно, і тепер це новий, переписаний фрагмент коду до того, який зараз знаходиться в Bash Infinity 2.0. Причину я переписав це через помилку в старій реалізації (це у випусках на GitHub). Ще не встиг інтегрувати нову версію ще в Bash Infinity. Радий почути, що це було корисно.
niieani

Привіт @niieani, коли я намагаюся створити функцію bash у формі, яку ти використовуєш у своїй відповіді, це говорить мені, що мені потрібно встановити ucommon утиліти з apt. Так працює ваш скрипт bash? Чи правильно я це роблю? Якщо я розумію, ви або хтось інший в основному побудував програму ucommon util, щоб дозволити розширення Bash, правильно?
Девід А. Французький

@ DavidA.French ні, цього не повинно відбуватися. Немає зв'язку між 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

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 набагато зручніше тим, що він безперешкодно перенаправляє, деякі старі підтримувані дистрибутиви все ж постачають старішу версію, тому я поки не рекомендую його.


"Труба". Я бачу, що ви там робили!
Джектоз
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.