Come restituire un valore di stringa da una funzione Bash


462

Vorrei restituire una stringa da una funzione Bash.

Scriverò l'esempio in Java per mostrare cosa mi piacerebbe fare:

public String getSomeString() {
  return "tadaa";
}

String variable = getSomeString();

L'esempio seguente funziona in bash, ma esiste un modo migliore per farlo?

function getSomeString {
   echo "tadaa"
}

VARIABLE=$(getSomeString)

4
A parte questo, function funcName {è la sintassi pre-POSIX ereditata dai primi ksh (dove aveva differenze semantiche che bash non onora). funcName() {, con no function, dovrebbe essere usato invece; vedi wiki.bash-hackers.org/scripting/obsolete
Charles Duffy

Quel link dice di usare NAME () COMPOUND-CMD o la funzione NAME {CMDS; } Quindi function myFunction { blah; }va bene; non function myFunction() { blah }va bene, cioè l'uso della parentesi con la funzione parola chiave.
Sarà il

Risposte:


290

Non c'è modo migliore che io conosca. Bash conosce solo codici di stato (numeri interi) e stringhe scritte nello stdout.


15
+1 @ tomas-f: devi stare molto attento a ciò che hai in questa funzione "getSomeString ()" poiché avere un codice che alla fine farà eco significa che otterrai una stringa di ritorno errata.
Mani,

11
Questo è semplicemente sbagliato. È possibile restituire dati arbitrari all'interno di una variabile di ritorno. Che chiaramente è un modo migliore.
Evi1M4chine

36
@ Evi1M4chine, um ... no, non puoi. Puoi impostare una variabile globale e chiamarla "return", come ti vedo fare nei tuoi script. Ma questo è per convenzione, NON effettivamente legato a livello di codice all'esecuzione del codice. "chiaramente un modo migliore"? Uhm, no. La sostituzione dei comandi è molto più esplicita e modulare.
Wildcard il

6
"La sostituzione dei comandi è molto più esplicita e modulare" sarebbe rilevante se la domanda riguardasse i comandi; questa domanda è come restituire una stringa, da una funzione bash! Un modo integrato per fare ciò che l'OP ha chiesto è disponibile da Bash 4.3 (2014?) - vedi la mia risposta di seguito.
zenaan,

4
La domanda originale contiene il modo più semplice per farlo e funziona bene nella maggior parte dei casi. I valori di ritorno di Bash dovrebbero probabilmente essere chiamati "codici di ritorno" perché sono meno simili ai valori di ritorno standard negli script e più simili ai codici di uscita dei comandi della shell numerica (puoi fare cose come somefunction && echo 'success'). Se pensi a una funzione come un altro comando, ha senso; i comandi non "restituiscono" nulla all'uscita diverso da un codice di stato, ma nel frattempo possono generare elementi che è possibile acquisire.
Beejor,

193

Potresti avere la funzione di prendere una variabile come primo argomento e modificare la variabile con la stringa che vuoi restituire.

#!/bin/bash
set -x
function pass_back_a_string() {
    eval "$1='foo bar rab oof'"
}

return_var=''
pass_back_a_string return_var
echo $return_var

Stampa "foo bar rab oof".

modificare : aggiunta la citazione nella posizione appropriata per consentire agli spazi bianchi nella stringa di rispondere al commento di @Luca Borrione.

Modifica : come dimostrazione, vedere il seguente programma. Questa è una soluzione generica: ti consente persino di ricevere una stringa in una variabile locale.

#!/bin/bash
set -x
function pass_back_a_string() {
    eval "$1='foo bar rab oof'"
}

return_var=''
pass_back_a_string return_var
echo $return_var

function call_a_string_func() {
     local lvar=''
     pass_back_a_string lvar
     echo "lvar='$lvar' locally"
}

call_a_string_func
echo "lvar='$lvar' globally"

Questo stampa:

+ return_var=
+ pass_back_a_string return_var
+ eval 'return_var='\''foo bar rab oof'\'''
++ return_var='foo bar rab oof'
+ echo foo bar rab oof
foo bar rab oof
+ call_a_string_func
+ local lvar=
+ pass_back_a_string lvar
+ eval 'lvar='\''foo bar rab oof'\'''
++ lvar='foo bar rab oof'
+ echo 'lvar='\''foo bar rab oof'\'' locally'
lvar='foo bar rab oof' locally
+ echo 'lvar='\'''\'' globally'
lvar='' globally

Modifica : dimostrando che il valore della variabile originale è disponibile nella funzione, come erroneamente criticato da @Xichen Li in un commento.

#!/bin/bash
set -x
function pass_back_a_string() {
    eval "echo in pass_back_a_string, original $1 is \$$1"
    eval "$1='foo bar rab oof'"
}

return_var='original return_var'
pass_back_a_string return_var
echo $return_var

function call_a_string_func() {
     local lvar='original lvar'
     pass_back_a_string lvar
     echo "lvar='$lvar' locally"
}

call_a_string_func
echo "lvar='$lvar' globally"

Questo dà output:

+ return_var='original return_var'
+ pass_back_a_string return_var
+ eval 'echo in pass_back_a_string, original return_var is $return_var'
++ echo in pass_back_a_string, original return_var is original return_var
in pass_back_a_string, original return_var is original return_var
+ eval 'return_var='\''foo bar rab oof'\'''
++ return_var='foo bar rab oof'
+ echo foo bar rab oof
foo bar rab oof
+ call_a_string_func
+ local 'lvar=original lvar'
+ pass_back_a_string lvar
+ eval 'echo in pass_back_a_string, original lvar is $lvar'
++ echo in pass_back_a_string, original lvar is original lvar
in pass_back_a_string, original lvar is original lvar
+ eval 'lvar='\''foo bar rab oof'\'''
++ lvar='foo bar rab oof'
+ echo 'lvar='\''foo bar rab oof'\'' locally'
lvar='foo bar rab oof' locally
+ echo 'lvar='\'''\'' globally'
lvar='' globally

1
Questa risposta è fantastica! I parametri possono essere passati tramite riferimenti, simili all'idea in C ++.
Yun Huang,

4
Sarebbe bello ricevere una risposta da un esperto in merito a tale risposta. Non l'ho mai visto usato negli script, forse per una buona ragione. Comunque: questo è +1 Avrebbe dovuto essere votato per la risposta corretta
John

Non è la stessa fgmrisposta scritta in modo semplificato? Questo non funzionerà se la stringa foocontiene spazi bianchi, mentre fgmquella sarà ... come sta mostrando.
Luca Borrione

4
@XichenLi: grazie per aver lasciato un commento con il tuo downvote; per favore vedi la mia modifica. È possibile ottenere il valore iniziale della variabile nella funzione con \$$1. Se stai cercando qualcosa di diverso, per favore fatemelo sapere.
bstpierre,

1
@timiscoding Che può essere risolto con a printf '%q' "$var". % q è una stringa di formato per la shell escape. Quindi passalo crudo.
bb010g,

99

Tutte le risposte sopra ignorano ciò che è stato affermato nella pagina man di bash.

  • Tutte le variabili dichiarate all'interno di una funzione saranno condivise con l'ambiente chiamante.
  • Tutte le variabili dichiarate locali non saranno condivise.

Codice di esempio

#!/bin/bash

f()
{
    echo function starts
    local WillNotExists="It still does!"
    DoesNotExists="It still does!"
    echo function ends
}

echo $DoesNotExists #Should print empty line
echo $WillNotExists #Should print empty line
f                   #Call the function
echo $DoesNotExists #Should print It still does!
echo $WillNotExists #Should print empty line

E uscita

$ sh -x ./x.sh
+ echo

+ echo

+ f
+ echo function starts 
function starts
+ local 'WillNotExists=It still does!'
+ DoesNotExists='It still does!'
+ echo function ends 
function ends
+ echo It still 'does!' 
It still does!
+ echo

Anche sotto pdksh e ksh questo script fa lo stesso!


10
Questa risposta ha i suoi meriti. Sono venuto qui pensando di voler restituire una stringa da una funzione. Questa risposta mi ha fatto capire che quella era solo la mia conversazione su C #. Sospetto che altri possano avere la stessa esperienza.
LOAS,

4
@ElmarZander Ti sbagli, questo è del tutto rilevante. Questo è un modo semplice per ottenere nell'ambito globale un valore di ambito funzionale, e alcuni lo considererebbero migliore / più semplice dell'approccio eval per ridefinire una variabile globale come indicato da bstpierre.
KomodoDave,

local non è portabile con script non bash, motivo per cui alcune persone lo evitano.
don luminoso

Domanda: che dire delle variabili nei loop?
anu,

1
Su un mac ($ bash --version GNU bash, versione 3.2.57 (1) -release (x86_64-apple-darwin14) Copyright (C) 2007 Free Software Foundation, Inc.), è corretto che una variabile globale corrispondente sia inizializzato, ma quando provo ad effetti collaterali la stessa variabile in un'altra funzione f2, quell'effetto collaterale non è persistente. Quindi, sembra molto incoerente e quindi non va bene per il mio utilizzo.
AnneTheAgile,

45

Bash, dalla versione 4.3, febbraio 2014 (?), Ha esplicito supporto per variabili di riferimento o riferimenti a nomi (namerefs), oltre "eval", con lo stesso effetto benefico e effetto indiretto, e che può essere più chiaro nei tuoi script e anche più difficile per "dimenticare di 'eval' e correggere questo errore":

declare [-aAfFgilnrtux] [-p] [name[=value] ...]
typeset [-aAfFgilnrtux] [-p] [name[=value] ...]
  Declare variables and/or give them attributes
  ...
  -n Give each name the nameref attribute, making it a name reference
     to another variable.  That other variable is defined by the value
     of name.  All references and assignments to name, except for
     changing the -n attribute itself, are performed on the variable
     referenced by name's value.  The -n attribute cannot be applied to
     array variables.
...
When used in a function, declare and typeset make each name local,
as with the local command, unless the -g option is supplied...

e anche:

PARAMETRI

A una variabile può essere assegnato l'attributo nameref usando l'opzione -n ​​ai comandi builtin declare o local (vedere le descrizioni di declare e local di seguito) per creare un nameref o un riferimento a un'altra variabile. Ciò consente di manipolare le variabili indirettamente. Ogni volta che si fa riferimento o si assegna alla variabile nameref, l'operazione viene effettivamente eseguita sulla variabile specificata dal valore della variabile nameref. Un nameref è comunemente usato all'interno delle funzioni della shell per fare riferimento a una variabile il cui nome viene passato come argomento alla funzione. Ad esempio, se un nome di variabile viene passato a una funzione shell come primo argomento, in esecuzione

      declare -n ref=$1

all'interno della funzione crea un riferimento alla variabile nameref il cui valore è il nome della variabile passato come primo argomento. I riferimenti e le assegnazioni a ref vengono considerati come riferimenti e assegnazioni alla variabile il cui nome è stato passato come $ 1. Se la variabile di controllo in un ciclo for ha l'attributo nameref, l'elenco delle parole può essere un elenco di variabili shell e un riferimento al nome verrà ⋅ stabilito per ogni parola nell'elenco, a sua volta, quando il ciclo viene eseguito. Non è possibile assegnare l'attributo -n alle variabili di matrice. Tuttavia, le variabili nameref possono fare riferimento a variabili di array e variabili di array sottoscritte. Namerefs può essere set disinserito usando l'opzione -n ​​per l'integrato unset. Altrimenti, se unset viene eseguito con il nome di una variabile nameref come argomento,

Ad esempio ( EDIT 2 : (grazie Ron) ha spaziato (prefissato) il nome della variabile interna alla funzione, per ridurre al minimo gli scontri delle variabili esterne, che dovrebbero finalmente rispondere correttamente, il problema sollevato nei commenti di Karsten):

# $1 : string; your variable to contain the return value
function return_a_string () {
    declare -n ret=$1
    local MYLIB_return_a_string_message="The date is "
    MYLIB_return_a_string_message+=$(date)
    ret=$MYLIB_return_a_string_message
}

e testando questo esempio:

$ return_a_string result; echo $result
The date is 20160817

Si noti che l'integrato bash "declare", quando usato in una funzione, rende la variabile dichiarata "locale" per impostazione predefinita, e "-n" può anche essere usato con "local".

Preferisco distinguere le variabili "importanti da dichiarare" dalle variabili "noiose locali", quindi usare "dichiarare" e "locale" in questo modo funge da documentazione.

EDIT 1 - (Risposta al commento qui sotto di Karsten) - Non posso più aggiungere commenti qui sotto, ma il commento di Karsten mi ha fatto pensare, quindi ho fatto il seguente test che FUNZIONA FINE, AFAICT - Karsten se leggi questo, ti preghiamo di fornire un set esatto dei passaggi di test dalla riga di comando, mostrando il problema che si presume esiste, poiché questi passaggi seguenti funzionano bene:

$ return_a_string ret; echo $ret
The date is 20170104

(L'ho eseguito proprio ora, dopo aver incollato la funzione sopra in un termine bash - come puoi vedere, il risultato funziona bene.)


4
La mia speranza è che questo scorra verso l'alto. eval dovrebbe essere l'ultima risorsa. Degno di menzione è che le variabili nameref sono disponibili solo a partire dal bash 4.3 (secondo il changelog ) (rilasciato nel febbraio 2014 [?]). Questo è importante se la portabilità è una preoccupazione. Si prega di citare il manuale di bash sul fatto che declarecrea variabili locali all'interno delle funzioni (che le informazioni non sono fornite da help declare): "... Quando usate in una funzione, dichiarare e comporre rendere ogni nome locale, come con il comando locale, a meno che il - Viene fornita l'opzione g ... "
init_js

2
Questo ha lo stesso problema di aliasing della soluzione eval. Quando si chiama una funzione e si passa il nome della variabile di output, è necessario evitare di passare il nome di una variabile utilizzata localmente all'interno della funzione chiamata. Questo è un grosso problema in termini di incapsulamento, in quanto non è possibile semplicemente aggiungere o rinominare nuove variabili locali in una funzione senza che una delle funzioni che i chiamanti potrebbero voler utilizzare quel nome per il parametro di output.
Karsten,

1
@Karsten ha concordato. in entrambi i casi (eval e namerefs), potresti dover scegliere un nome diverso. Un vantaggio con l'approccio nameref rispetto a eval è che non si deve affrontare l'evasione di stringhe. Certo, puoi sempre fare qualcosa del genere K=$1; V=$2; eval "$A='$V'";, ma un errore (ad esempio un parametro vuoto o omesso), e sarebbe più pericoloso. @zenaan il problema sollevato da @Karsten si applica se si sceglie "message" come nome della variabile di ritorno, anziché "ret".
init_js il

3
Presumibilmente una funzione deve essere progettata dall'inizio per accettare un argomento nameref, quindi l'autore della funzione dovrebbe essere consapevole della possibilità di una collisione del nome e può usare una convenzione tipica per evitarlo. Ad esempio, all'interno della funzione X, denominare le variabili locali con la convenzione "X_LOCAL_name".
Ron Burk,

34

Come bstpierre sopra, uso e raccomando l'uso di denominare esplicitamente le variabili di output:

function some_func() # OUTVAR ARG1
{
   local _outvar=$1
   local _result # Use some naming convention to avoid OUTVARs to clash
   ... some processing ....
   eval $_outvar=\$_result # Instead of just =$_result
}

Nota l'uso di quotare $. Ciò eviterà di interpretare il contenuto $resultcome caratteri speciali della shell. Ho scoperto che questo è un ordine di grandezza più veloce delresult=$(some_func "arg1") linguaggio di catturare un eco. La differenza di velocità sembra ancora più evidente usando bash su MSYS in cui la cattura stdout dalle chiamate di funzione è quasi catastrofica.

Va bene inviare una variabile locale poiché i locali sono dinamicamente definiti in bash:

function another_func() # ARG
{
   local result
   some_func result "$1"
   echo result is $result
}

4
Questo mi aiuta perché mi piace usare più istruzioni echo per scopi di debug / registrazione. L'idioma di catturare l'eco fallisce dal momento che li cattura tutti. Grazie!
AnneTheAgile,

Questa è la (seconda migliore) soluzione corretta! Pulito, veloce, elegante, sensibile.
Evi1M4chine

+2 per mantenerlo reale. Stavo per dire. Come possono tante persone ignorare combinando un echointerno di una funzione, combinato con la sostituzione dei comandi!
Anthony Rutledge,

23

È inoltre possibile acquisire l'output della funzione:

#!/bin/bash
function getSomeString() {
     echo "tadaa!"
}

return_var=$(getSomeString)
echo $return_var
# Alternative syntax:
return_var=`getSomeString`
echo $return_var

Sembra strano, ma è meglio dell'uso delle variabili globali IMHO. Passare i parametri funziona come al solito, basta metterli tra parentesi graffe o backtick.


11
a parte la nota di sintassi alternativa, non è esattamente la stessa cosa che l'op ha già scritto nella sua stessa domanda?
Luca Borrione,

molto chiaro, grazie!
bcag2

12

La soluzione più semplice e solida è usare la sostituzione dei comandi, come hanno scritto altre persone:

assign()
{
    local x
    x="Test"
    echo "$x"
}

x=$(assign) # This assigns string "Test" to x

L'aspetto negativo è la prestazione in quanto richiede un processo separato.

L'altra tecnica suggerita in questo argomento, vale a dire passare il nome di una variabile da assegnare come argomento, ha effetti collaterali e non lo consiglierei nella sua forma base. Il problema è che probabilmente avrai bisogno di alcune variabili nella funzione per calcolare il valore restituito, e può accadere che il nome della variabile destinata a memorizzare il valore restituito interferisca con una di esse:

assign()
{
    local x
    x="Test"
    eval "$1=\$x"
}

assign y # This assigns string "Test" to y, as expected

assign x # This will NOT assign anything to x in this scope
         # because the name "x" is declared as local inside the function

Ovviamente potresti non dichiarare le variabili interne della funzione come locali, ma dovresti sempre farlo come altrimenti potresti, d'altra parte, sovrascrivere accidentalmente una variabile non correlata dall'ambito padre se ce n'è una con lo stesso nome .

Una possibile soluzione alternativa è una dichiarazione esplicita della variabile passata come globale:

assign()
{
    local x
    eval declare -g $1
    x="Test"
    eval "$1=\$x"
}

Se il nome "x" viene passato come argomento, la seconda riga del corpo della funzione sovrascriverà la precedente dichiarazione locale. Ma i nomi stessi potrebbero comunque interferire, quindi se si intende utilizzare il valore precedentemente memorizzato nella variabile passata prima di scrivere lì il valore restituito, tenere presente che è necessario copiarlo in un'altra variabile locale all'inizio; altrimenti il ​​risultato sarà imprevedibile! Inoltre, funzionerà solo con la versione più recente di BASH, ovvero 4.2. Un codice più portatile potrebbe utilizzare espliciti costrutti condizionali con lo stesso effetto:

assign()
{
    if [[ $1 != x ]]; then
      local x
    fi
    x="Test"
    eval "$1=\$x"
}

Forse la soluzione più elegante è solo riservare un nome globale per i valori di ritorno della funzione e usarlo in modo coerente in ogni funzione che scrivi.


3
Questo ^^^. L'involontario aliasing che rompe l'incapsulamento è il grande problema sia con la evale declare -nsoluzioni. La soluzione alternativa di avere un singolo nome di variabile dedicato come resultper tutti i parametri di output sembra l'unica soluzione che non richiede una funzione per conoscere tutti i chiamanti per evitare conflitti.
Karsten,

12

Come accennato in precedenza, il modo "corretto" per restituire una stringa da una funzione è con la sostituzione del comando. Nel caso in cui la funzione debba anche eseguire l'output sulla console (come menzionato sopra @Mani), creare un file temporaneo all'inizio della funzione e reindirizzare alla console. Chiudi il file temporaneo prima di restituire la stringa.

#!/bin/bash
# file:  func_return_test.sh
returnString() {
    exec 3>&1 >/dev/tty
    local s=$1
    s=${s:="some default string"}
    echo "writing directly to console"
    exec 3>&-     
    echo "$s"
}

my_string=$(returnString "$*")
echo "my_string:  [$my_string]"

l'esecuzione di script senza parametri produce ...

# ./func_return_test.sh
writing directly to console
my_string:  [some default string]

spero che questo aiuti le persone

-Andy


6
Questo ha i suoi usi, ma nel complesso dovresti evitare di effettuare un reindirizzamento esplicito alla console; l'output potrebbe già essere reindirizzato oppure lo script potrebbe essere in esecuzione in un contesto in cui non esiste tty. Puoi aggirare questo duplicando 3>&1in testa allo script, quindi manipolando &1 &3e un altro segnaposto &4all'interno della funzione. Brutto dappertutto, comunque.
jmb,

8

È possibile utilizzare una variabile globale:

declare globalvar='some string'

string ()
{
  eval  "$1='some other string'"
} # ----------  end of function string  ----------

string globalvar

echo "'${globalvar}'"

Questo da

'some other string'

6

Per illustrare il mio commento sulla risposta di Andy, con un'ulteriore manipolazione del descrittore di file per evitare l'uso di /dev/tty:

#!/bin/bash

exec 3>&1

returnString() {
    exec 4>&1 >&3
    local s=$1
    s=${s:="some default string"}
    echo "writing to stdout"
    echo "writing to stderr" >&2
    exec >&4-
    echo "$s"
}

my_string=$(returnString "$*")
echo "my_string:  [$my_string]"

Comunque brutto.


3

Il modo in cui lo hai è l'unico modo per farlo senza rompere l'ambito. Bash non ha un concetto di tipi restituiti, solo codici di uscita e descrittori di file (stdin / out / err, ecc.)


3

Rivolgendosi Vicky Ronnen 's testa, considerando il seguente codice:

function use_global
{
    eval "$1='changed using a global var'"
}

function capture_output
{
    echo "always changed"
}

function test_inside_a_func
{
    local _myvar='local starting value'
    echo "3. $_myvar"

    use_global '_myvar'
    echo "4. $_myvar"

    _myvar=$( capture_output )
    echo "5. $_myvar"
}

function only_difference
{
    local _myvar='local starting value'
    echo "7. $_myvar"

    local use_global '_myvar'
    echo "8. $_myvar"

    local _myvar=$( capture_output )
    echo "9. $_myvar"
}

declare myvar='global starting value'
echo "0. $myvar"

use_global 'myvar'
echo "1. $myvar"

myvar=$( capture_output )
echo "2. $myvar"

test_inside_a_func
echo "6. $_myvar" # this was local inside the above function

only_difference



darà

0. global starting value
1. changed using a global var
2. always changed
3. local starting value
4. changed using a global var
5. always changed
6. 
7. local starting value
8. local starting value
9. always changed

Forse lo scenario normale è utilizzare la sintassi utilizzata nella test_inside_a_funcfunzione, quindi è possibile utilizzare entrambi i metodi nella maggior parte dei casi, sebbene l'acquisizione dell'output sia il metodo più sicuro che funziona sempre in qualsiasi situazione, imitando il valore restituito da una funzione che è possibile trovare in altre lingue, come Vicky Ronnencorrettamente sottolineato.


2

Le opzioni sono state tutte elencate, credo. La scelta di uno può arrivare a una questione del miglior stile per la tua particolare applicazione, e in tal senso, voglio offrire uno stile particolare che ho trovato utile. In bash, le variabili e le funzioni non si trovano nello stesso spazio dei nomi. Quindi, trattare la variabile con lo stesso nome come valore della funzione è una convenzione che trovo minimizza gli scontri tra nomi e migliora la leggibilità, se la applico rigorosamente. Un esempio dalla vita reale:

UnGetChar=
function GetChar() {
    # assume failure
    GetChar=
    # if someone previously "ungot" a char
    if ! [ -z "$UnGetChar" ]; then
        GetChar="$UnGetChar"
        UnGetChar=
        return 0               # success
    # else, if not at EOF
    elif IFS= read -N1 GetChar ; then
        return 0           # success
    else
        return 1           # EOF
    fi
}

function UnGetChar(){
    UnGetChar="$1"
}

E un esempio dell'uso di tali funzioni:

function GetToken() {
    # assume failure
    GetToken=
    # if at end of file
    if ! GetChar; then
        return 1              # EOF
    # if start of comment
    elif [[ "$GetChar" == "#" ]]; then
        while [[ "$GetChar" != $'\n' ]]; do
            GetToken+="$GetChar"
            GetChar
        done
        UnGetChar "$GetChar"
    # if start of quoted string
    elif [ "$GetChar" == '"' ]; then
# ... et cetera

Come puoi vedere, lo stato di restituzione è lì per te da usare quando ne hai bisogno, o ignorare se non lo fai. Anche la variabile "restituita" può essere utilizzata o ignorata, ma ovviamente solo dopo che la funzione è stata invocata.

Certo, questa è solo una convenzione. Sei libero di non riuscire a impostare il valore associato prima di tornare (quindi la mia convenzione di annullarlo sempre all'inizio della funzione) o di calpestare il suo valore chiamando di nuovo la funzione (possibilmente indirettamente). Tuttavia, è una convenzione che trovo molto utile se mi trovo a fare un uso pesante delle funzioni bash.

Contrariamente al sentimento che questo è un segno che uno dovrebbe ad esempio "passare al perl", la mia filosofia è che le convenzioni sono sempre importanti per gestire la complessità di qualsiasi lingua.


2

È possibile echouna stringa, ma catturarla eseguendo il piping ( |) della funzione su qualcos'altro.

Puoi farlo anche exprse ShellCheck segnala questo utilizzo come obsoleto.


Il problema è che la cosa a destra della pipe è una subshell. Quindi myfunc | read OUTPUT ; echo $OUTPUTnon cede nulla. myfunc | ( read OUTPUT; echo $OUTPUT )ottiene il valore atteso e chiarisce ciò che sta accadendo sul lato destro. Ma ovviamente OUTPUT non è quindi disponibile dove serve ...
Ed Randall

2

Risolvono il problema di qualsiasi schema di "variabile di output denominata" in cui il chiamante può passare il nome della variabile (sia utilizzando evalche declare -n) è aliasing involontario, ovvero scontri di nome: dal punto di vista dell'incapsulamento, è terribile non poter aggiungere o rinominare una variabile locale in una funzione senza prima controllare TUTTI i chiamanti della funzione per assicurarsi che non vogliano passare lo stesso nome del parametro di output. (O nella direzione opposta, non voglio leggere l'origine della funzione che sto chiamando solo per assicurarmi che il parametro di output che intendo utilizzare non sia un locale in quella funzione.)

L'unico modo per aggirare questo è usare una singola variabile di output dedicata come REPLY(come suggerito da Evi1M4chine ) o una convenzione come quella suggerita da Ron Burk .

Tuttavia, è possibile che le funzioni utilizzino internamente una variabile di output fissa , quindi aggiungere un po 'di zucchero sopra per nascondere questo fatto al chiamante , come ho fatto con la callfunzione nell'esempio seguente. Considera questa una prova del concetto, ma i punti chiave sono

  • La funzione assegna sempre il valore restituito a REPLY e può anche restituire un codice di uscita come al solito
  • Dal punto di vista del chiamante, il valore restituito può essere assegnato a qualsiasi variabile (locale o globale) incluso REPLY (vedere l' wrapperesempio). Il codice di uscita della funzione viene passato, quindi usandoli ad es. In ifowhile o costrutti simili funziona come previsto.
  • Sintatticamente la chiamata di funzione è ancora una singola istruzione semplice.

Il motivo per cui funziona è perché la callfunzione stessa non ha gente del posto e non usa variabili diverse da quella REPLY, evitando qualsiasi potenziale di scontro tra nomi. Nel punto in cui viene assegnato il nome della variabile di output definito dal chiamante, siamo effettivamente nell'ambito del chiamante (tecnicamente nell'ambito identico della callfunzione), piuttosto che nell'ambito della funzione chiamata.

#!/bin/bash
function call() { # var=func [args ...]
  REPLY=; "${1#*=}" "${@:2}"; eval "${1%%=*}=\$REPLY; return $?"
}

function greet() {
  case "$1" in
    us) REPLY="hello";;
    nz) REPLY="kia ora";;
    *) return 123;;
  esac
}

function wrapper() {
  call REPLY=greet "$@"
}

function main() {
  local a b c d
  call a=greet us
  echo "a='$a' ($?)"
  call b=greet nz
  echo "b='$b' ($?)"
  call c=greet de
  echo "c='$c' ($?)"
  call d=wrapper us
  echo "d='$d' ($?)"
}
main

Produzione:

a='hello' (0)
b='kia ora' (0)
c='' (123)
d='hello' (0)

1

modello bash per restituire oggetti valore scalare e array :

definizione

url_parse() { # parse 'url' into: 'url_host', 'url_port', ...
   local "$@" # inject caller 'url' argument in local scope
   local url_host="..." url_path="..." # calculate 'url_*' components
   declare -p ${!url_*} # return only 'url_*' object fields to the caller
}

invocazione

main() { # invoke url parser and inject 'url_*' results in local scope
   eval "$(url_parse url=http://host/path)" # parse 'url'
   echo "host=$url_host path=$url_path" # use 'url_*' components
}

0

Nei miei programmi, per convenzione, questo è lo scopo della $REPLYvariabile preesistente , che readutilizza per quello scopo preciso.

function getSomeString {
  REPLY="tadaa"
}

getSomeString
echo $REPLY

Questo echoes

tadaa

Ma per evitare conflitti, qualsiasi altra variabile globale lo farà.

declare result

function getSomeString {
  result="tadaa"
}

getSomeString
echo $result

Se ciò non bastasse, raccomando la soluzione di Markarian451 .


-2
agt@agtsoft:~/temp$ cat ./fc 
#!/bin/sh

fcall='function fcall { local res p=$1; shift; fname $*; eval "$p=$res"; }; fcall'

function f1 {
    res=$[($1+$2)*2];
}

function f2 {
    local a;
    eval ${fcall//fname/f1} a 2 3;
    echo f2:$a;
}

a=3;
f2;
echo after:a=$a, res=$res

agt@agtsoft:~/temp$ ./fc
f2:10
after:a=3, res=
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.