Passando i parametri a una funzione Bash


981

Sto cercando di cercare come passare i parametri in una funzione Bash, ma quello che viene fuori è sempre come passare i parametri dalla riga di comando.

Vorrei passare i parametri all'interno della mia sceneggiatura. Provai:

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

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

Ma la sintassi non è corretta, come passare un parametro alla mia funzione?


6
"... ma ciò che emerge è sempre come passare i parametri dalla riga di comando" - Sì! Questo perché gli script Bash sono fondamentalmente sequenze di righe di comando - invocano una funzione in uno script Bash esattamente come se fosse un comando sulla riga di comando! :-) La tua chiamata sarebbe myBackupFunction ".." "..." "xx"; nessuna parentesi, nessuna virgola.
Wil,

4
La controparte di questa domanda: restituisce valore da una funzione bash
MSalters

Risposte:


1619

Esistono due modi tipici di dichiarare una funzione. Preferisco il secondo approccio.

function function_name {
   command...
} 

o

function_name () {
   command...
} 

Per chiamare una funzione con argomenti:

function_name "$arg1" "$arg2"

La funzione si riferisce agli argomenti passati per la loro posizione (non per nome), ovvero $ 1, $ 2 e così via. $ 0 è il nome dello script stesso.

Esempio:

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

Inoltre, è necessario chiamare la funzione dopo che è stata dichiarata.

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

Produzione:

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

Riferimento: Guida avanzata agli script di Bash .


4
Hai dimenticato gli spazi, prova function name() {}. Forse con un 'invio' prima{}
lalo

21
Buona risposta. I miei 2 centesimi: nei costrutti shell che risiedono in un file che viene fornito (punteggiato) quando necessario, preferisco usare la functionparola chiave e il (). Il mio obiettivo (in un file, non nella riga di comando) è aumentare la chiarezza, non ridurre il numero di caratteri digitati, vale a dire function myBackupFunction() compound-statement.
Terry Gardner,

22
@CMCDragonkai, la functionversione della parola chiave è un'estensione; l'altra forma funziona in tutte le shell conformi a POSIX.
Charles Duffy,

8
@TerryGardner, considera che i tuoi tentativi di aumentare la chiarezza stanno riducendo la compatibilità.
Charles Duffy,

6
@RonBurk, forse - ma anche se consideriamo solo chiarezza, la functionparola chiave aveva garanzie nelle vecchie shell della famiglia ksh che lo introdussero che il bash moderno non onora (in tali shell, ha functionreso le variabili locali per impostazione predefinita; in bash , non è così). Come tale, il suo uso riduce la chiarezza a chiunque conosca e potrebbe aspettarsi il comportamento di ksh. Vedi wiki.bash-hackers.org/scripting/obsolete
Charles Duffy,

70

La conoscenza di linguaggi di programmazione di alto livello (C / C ++ / Java / PHP / Python / Perl ...) suggerirebbe ai non addetti ai lavori che le funzioni bash dovrebbero funzionare come fanno in quegli altri linguaggi. Invece , le funzioni bash funzionano come i comandi di shell e si aspettano che gli argomenti vengano passati a loro nello stesso modo in cui si potrebbe passare un'opzione a un comando di shell (ad es ls -l.). In effetti, gli argomenti di funzione in bash sono trattati come parametri posizionali ( $1, $2..$9, ${10}, ${11}e così via). Questa non è una sorpresa considerando come getoptsfunziona. Non usare le parentesi per chiamare una funzione in bash.


( Nota : al momento mi capita di lavorare su 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

Vuoi usare i nomi per le variabili. Fallo e basta.

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

Vuoi passare un array a una funzione?

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

All'interno della funzione, gestisci gli argomenti in questo modo.

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

Devi passare un valore e un array, ma usi ancora "$ @" all'interno della funzione?

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

Se si preferiscono i parametri con nome, è possibile (con alcuni trucchi) passare effettivamente i parametri con nome alle funzioni (consente anche di passare array e riferimenti).

Il metodo che ho sviluppato consente di definire parametri denominati passati a una funzione come questa:

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

Puoi anche annotare gli argomenti come @required o @readonly, creare ... resto argomenti, creare matrici da argomenti sequenziali (usando ad esempio string[4]) ed eventualmente elencare gli argomenti su più righe:

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

In altre parole, non solo puoi chiamare i tuoi parametri con i loro nomi (che compensano un nucleo più leggibile), ma puoi anche passare array (e riferimenti a variabili - questa funzionalità funziona solo in bash 4.3 però)! Inoltre, le variabili mappate sono tutte nell'ambito locale, proprio come $ 1 (e altre).

Il codice che rende questo lavoro abbastanza leggero e funziona sia in bash 3 che bash 4 (queste sono le uniche versioni con cui l'ho provato). Se sei interessato a più trucchi come questo che rendono lo sviluppo con bash molto più bello e semplice, puoi dare un'occhiata al mio Bash Infinity Framework , il codice qui sotto è disponibile come una delle sue funzionalità.

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

Quali sono i @var, @reference, @paramsvariabili? Cosa devo cercare su Internet per saperne di più su questo?
GypsyCosmonaut il

3
Bella risposta! Ho appena cercato Bash Infinity e sembra che sarà davvero utile. Grazie!
Jonathan Hult,

Grazie @JonathanHult! Di recente ho aggiornato la mia risposta di cui sopra, ed è ora un frammento di codice più recente e riscritto a quello attualmente in Bash Infinity 2.0. Il motivo per cui l'ho riscritto è a causa di un bug nella vecchia implementazione (è nei problemi su GitHub). Non ho ancora avuto il tempo di integrare la nuova versione in Bash Infinity. Sono contento di sentire che è stato utile.
niieani,

Ciao @niieani quando provo a creare una funzione bash nella forma che usi nella tua risposta mi dice che devo installare ucommon utils da apt. È così che funziona il tuo script bash? Lo sto facendo correttamente? Se capisco che tu o qualcun altro fondamentalmente ha creato il programma util ucommon per consentire un'estensione di Bash, giusto?
David A. French,

@ DavidA.French no, questo non dovrebbe succedere. Non esiste alcuna relazione tra ucommone il mio codice. È possibile che tu abbia installato uno strumento che causa il problema che hai citato, non ho idea di cosa potrebbe essere.
niieani,

27

Perdere le parentesi e le virgole:

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

e la funzione dovrebbe apparire così:

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

8

Spero che questo esempio ti possa aiutare. Prende due numeri dall'utente, li inserisce nella funzione chiamata add(nell'ultima riga del codice) e addli riassume e li stampa.

#!/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
Il passaggio per nome in quel modo funziona solo per numeri interi passati nell'operatore numerico (()) e funziona solo perché l'operatore numerico risolve ricorsivamente le stringhe in valori. Se vuoi provare cosa intendo, prova a inserire '5' per xe quindi 'x' per y e vedrai che aggiunge (x + y) = (5 + x) = (5 + 5) = 10. Per tutti gli altri casi d'uso il tuo esempio fallirà. Invece dovresti usare 'aggiungi "$ x" "$ y"' per il codice generico.
Wil,

6

Un semplice esempio che verrà cancellato sia durante l'esecuzione dello script che all'interno dello script durante la chiamata di una funzione.

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

Ho pensato di inserire un altro modo per passare parametri nominati a bash ... passando per riferimento. Questo è supportato da 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];

Una sintassi alternativa per bash 4.3 sta usando un nameref

Sebbene nameref sia molto più conveniente in quanto dereferenze senza soluzione di continuità, alcune distribuzioni supportate più vecchie spediscono ancora una versione precedente, quindi non lo consiglio ancora.


"Pipe in". Vedo quello che hai fatto lì!
Jacktose,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.