Cara mengembalikan nilai string dari fungsi Bash

462

Saya ingin mengembalikan string dari fungsi Bash.

Saya akan menulis contoh di java untuk menunjukkan apa yang ingin saya lakukan:

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

String variable = getSomeString();

Contoh di bawah berfungsi dalam bash, tetapi apakah ada cara yang lebih baik untuk melakukan ini?

function getSomeString {
   echo "tadaa"
}

VARIABLE=$(getSomeString)
Tomas F
sumber
4
Selain itu, function funcName {sintaksis pra-POSIX warisan diwarisi dari ksh awal (di mana ada perbedaan semantik yang bash tidak menghormati). funcName() {, tanpa function, harus digunakan sebagai gantinya; lihat wiki.bash-hackers.org/scripting/obsolete
Charles Duffy
Tautan itu mengatakan untuk menggunakan NAME () COMPOUND-CMD atau fungsi NAME {CMDS; } Jadi function myFunction { blah; }baik-baik saja; itu function myFunction() { blah }tidak baik, yaitu penggunaan tanda kurung dengan fungsi kata kunci.
Will

Jawaban:

290

Tidak ada cara yang lebih baik yang saya tahu. Bash hanya tahu kode status (bilangan bulat) dan string yang ditulis ke stdout.

Philipp
sumber
15
+1 @ tomas-f: Anda harus benar-benar berhati-hati pada apa yang Anda miliki dalam fungsi ini "getSomeString ()" karena memiliki kode apa pun yang pada akhirnya akan bergema berarti Anda mendapatkan string pengembalian yang salah.
Mani
11
Ini benar-benar salah. Anda dapat mengembalikan data acak di dalam variabel pengembalian. Yang jelas merupakan cara yang lebih baik.
Evi1M4chine
36
@ Evi1M4chine, um ... tidak, Anda tidak bisa. Anda dapat mengatur variabel global dan menyebutnya "kembali", seperti yang saya lihat Anda lakukan dalam skrip Anda. Tapi kemudian itu adalah dengan konvensi, TIDAK benar-benar terikat secara terprogram untuk eksekusi kode Anda. "Jelas cara yang lebih baik"? Um, tidak. Substitusi perintah jauh lebih eksplisit dan modular.
Wildcard
6
"Substitusi perintah jauh lebih eksplisit dan modular" akan relevan jika pertanyaannya adalah tentang perintah; pertanyaan ini adalah bagaimana mengembalikan string, dari fungsi bash! Cara bawaan untuk melakukan apa yang diminta OP sudah tersedia sejak Bash 4.3 (2014?) - lihat jawaban saya di bawah ini.
zenaan
4
Pertanyaan aslinya berisi cara paling sederhana untuk melakukannya, dan berfungsi dengan baik dalam kebanyakan kasus. Nilai pengembalian Bash mungkin harus disebut "kode pengembalian" karena mereka kurang seperti nilai pengembalian standar dalam skrip, dan lebih seperti kode keluar perintah numeric shell (Anda dapat melakukan hal-hal seperti somefunction && echo 'success'). Jika Anda berpikir tentang fungsi seperti perintah lain, itu masuk akal; perintah tidak "mengembalikan" apa pun yang keluar selain dari kode status, tetapi perintah tersebut dapat menampilkan hal-hal sementara yang dapat Anda tangkap.
Beejor
193

Anda bisa meminta fungsi mengambil variabel sebagai argumen pertama dan memodifikasi variabel dengan string yang ingin Anda kembalikan.

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

Mencetak "foo bar rab oof".

Edit : menambahkan kutipan di tempat yang sesuai untuk memungkinkan spasi putih dalam string untuk mengatasi komentar @Luca Borrione.

Sunting : Sebagai demonstrasi, lihat program berikut. Ini adalah solusi tujuan umum: bahkan memungkinkan Anda untuk menerima string ke variabel lokal.

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

Ini mencetak:

+ 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

Mengedit : menunjukkan bahwa nilai variabel asli yang tersedia di fungsi, seperti yang tidak benar dikritik oleh @Xichen Li dalam komentar.

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

Ini menghasilkan:

+ 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
bstpierre
sumber
1
Jawaban ini luar biasa! Parameter dapat dilewatkan oleh referensi, mirip dengan ide di C ++.
Yun Huang
4
Akan menyenangkan menerima tanggapan dari pakar tentang jawaban itu. Saya belum pernah melihat yang digunakan dalam skrip, mungkin karena alasan yang bagus. Pokoknya: itu +1. Itu seharusnya dipilih untuk jawaban yang benar
John
Bukankah ini sama dengan fgmjawaban yang ditulis dengan cara yang disederhanakan? Ini tidak akan berfungsi jika string fooberisi spasi putih, sedangkan yang fgmsatu akan .. seperti yang ditampilkan.
Luca Borrione
4
@XichenLi: terima kasih telah meninggalkan komentar dengan downvote Anda; silakan lihat edit saya. Anda bisa mendapatkan nilai awal variabel dengan fungsi \$$1. Jika Anda mencari sesuatu yang berbeda, beri tahu saya.
bstpierre
1
@timiscoding Itu bisa diperbaiki dengan a printf '%q' "$var". % q adalah string format untuk pelarian shell. Kemudian hanya lulus mentah.
bb010g
99

Semua jawaban di atas mengabaikan apa yang telah dinyatakan di halaman manual bash.

  • Semua variabel yang dideklarasikan di dalam suatu fungsi akan dibagikan dengan lingkungan panggilan.
  • Semua variabel yang dinyatakan lokal tidak akan dibagikan.

Kode contoh

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

Dan output

$ 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

Juga di bawah pdksh dan ksh script ini melakukan hal yang sama!

Vicky Ronnen
sumber
10
Jawaban ini memang memiliki kelebihan. Saya datang ke sini berpikir bahwa saya ingin mengembalikan string dari suatu fungsi. Jawaban ini membuat saya sadar bahwa itu hanya kebiasaan saya yang berbicara. Saya menduga orang lain mungkin memiliki pengalaman yang sama.
LOAS
4
@ Elmarander Anda salah, ini sepenuhnya relevan. Ini adalah cara sederhana untuk masuk ke lingkup global sebagai nilai fungsi-lingkup, dan beberapa akan menganggap ini lebih baik / sederhana daripada pendekatan eval untuk mendefinisikan ulang variabel global seperti yang diuraikan oleh bstpierre.
KomodoDave
local tidak portabel untuk skrip non-bash yang merupakan salah satu alasan beberapa orang menghindarinya.
don bright
Pertanyaan: Bagaimana dengan variabel dalam loop?
anu
1
Pada mac ($ bash --versi GNU bash, versi 3.2.57 (1) -release (x86_64-apple-darwin14) Hak Cipta (C) 2007 Free Software Foundation, Inc.), sudah benar bahwa variabel global yang cocok adalah diinisialisasi, tetapi ketika saya mencoba efek samping variabel yang sama di fungsi lain f2, efek samping itu tidak bertahan. Jadi, sepertinya sangat tidak konsisten dan karenanya tidak baik untuk penggunaan saya.
AnneTheAgile
45

Bash, sejak versi 4.3, Februari 2014 (?), Memiliki dukungan eksplisit untuk variabel referensi atau referensi nama (namerefs), di luar "eval", dengan kinerja manfaat dan efek tipuan yang sama, dan yang mungkin lebih jelas dalam skrip Anda dan juga lebih sulit untuk "lupa untuk 'eval' dan harus memperbaiki kesalahan ini":

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...

dan juga:

PARAMETER

Sebuah variabel dapat diberi atribut nameref menggunakan opsi -n ke perintah mendeklarasikan atau perintah builtin lokal (lihat deskripsi mendeklarasikan dan lokal di bawah) untuk membuat nameref, atau referensi ke variabel lain. Ini memungkinkan variabel untuk dimanipulasi secara tidak langsung. Setiap kali variabel nameref direferensikan atau ditugaskan, operasi sebenarnya dilakukan pada variabel yang ditentukan oleh nilai variabel nameref. Nameref biasanya digunakan dalam fungsi shell untuk merujuk ke variabel yang namanya dilewatkan sebagai argumen ke⋅ fungsi. Misalnya, jika nama variabel dilewatkan ke fungsi shell sebagai argumen pertamanya, jalankan

      declare -n ref=$1

di dalam fungsi menciptakan ref variabel nameref yang nilainya adalah nama variabel yang diteruskan sebagai argumen pertama. Referensi dan penugasan ke ref diperlakukan sebagai referensi dan penugasan ke variabel yang namanya disahkan sebagai $ 1. Jika variabel kontrol dalam for for memiliki atribut nameref, daftar kata-kata bisa menjadi daftar variabel shell, dan referensi nama akan⋅dibuat untuk setiap kata dalam daftar, pada gilirannya, ketika loop dijalankan. Variabel array tidak dapat diberi atribut -n. Namun, variabel nameref dapat merujuk variabel array dan variabel array yang disubkripsikan. Namerefs dapat⋅ tidak disetel menggunakan opsi -n ke unset builtin. Jika tidak, jika tidak disetel dijalankan dengan nama variabel nameref sebagai argumen,

Misalnya ( EDIT 2 : (terima kasih Ron) namespaced (diawali) nama variabel fungsi-internal, untuk meminimalkan benturan variabel eksternal, yang akhirnya harus menjawab dengan benar, masalah yang diangkat dalam komentar oleh 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
}

dan menguji contoh ini:

$ return_a_string result; echo $result
The date is 20160817

Perhatikan bahwa bash "menyatakan" builtin, ketika digunakan dalam suatu fungsi, menjadikan variabel yang dideklarasikan "lokal" secara default, dan "-n" juga dapat digunakan dengan "lokal".

Saya lebih suka membedakan variabel "mendeklarasikan penting" dari variabel "membosankan lokal", jadi menggunakan "menyatakan" dan "lokal" dengan cara ini bertindak sebagai dokumentasi.

EDIT 1 - (Tanggapan terhadap komentar di bawah oleh Karsten) - Saya tidak dapat menambahkan komentar di bawah ini lagi, tetapi komentar Karsten membuat saya berpikir, jadi saya melakukan tes berikut yang BEKERJA HALUS, AFAICT - Karsten jika Anda membaca ini, berikan set yang tepat langkah uji dari baris perintah, menunjukkan masalah yang Anda asumsikan ada, karena langkah-langkah berikut ini berfungsi dengan baik:

$ return_a_string ret; echo $ret
The date is 20170104

(Saya menjalankan ini sekarang, setelah menempelkan fungsi di atas ke dalam istilah bash - seperti yang Anda lihat, hasilnya bekerja dengan baik.)

zenaan
sumber
4
Ini adalah harapan saya bahwa ini meresap ke atas. eval harus menjadi pilihan terakhir. Layak disebutkan adalah bahwa variabel nameref hanya tersedia sejak bash 4.3 (menurut changelog ) (dirilis pada Februari 2014 [?]). Ini penting jika portabilitas menjadi perhatian. Tolong kutip bash manual pada fakta yang declaremembuat variabel lokal di dalam fungsi (info itu tidak diberikan oleh help declare): "... Ketika digunakan dalam suatu fungsi, deklarasikan dan ketik setel setiap nama lokal, seperti dengan perintah lokal, kecuali - opsi g disediakan ... "
init_js
2
Ini memiliki masalah aliasing yang sama dengan solusi eval. Saat Anda memanggil suatu fungsi dan memasukkan nama variabel keluaran, Anda harus menghindari pemberian nama variabel yang digunakan secara lokal di dalam fungsi yang Anda panggil. Itu masalah besar dalam hal enkapsulasi, karena Anda tidak bisa hanya menambah atau mengganti nama variabel lokal baru dalam suatu fungsi tanpa jika ada fungsi penelepon yang mungkin ingin menggunakan nama itu untuk parameter output.
Karsten
1
@Karsten setuju. dalam kedua kasus (eval dan namerefs), Anda mungkin harus memilih nama yang berbeda. Satu keuntungan dengan pendekatan nameref atas eval adalah bahwa seseorang tidak harus berurusan dengan string yang lolos. Tentu saja, Anda selalu dapat melakukan sesuatu seperti K=$1; V=$2; eval "$A='$V'";, tetapi satu kesalahan (misalnya parameter kosong atau dihilangkan), dan itu akan lebih berbahaya. @zenaan masalah yang diangkat oleh @Karsten berlaku jika Anda memilih "message" sebagai nama variabel kembali, alih-alih "ret".
init_js
3
Suatu fungsi mungkin harus dirancang dari awal untuk menerima argumen nameref, sehingga penulis fungsi harus menyadari kemungkinan tabrakan nama dan dapat menggunakan beberapa konvensi umum untuk menghindarinya. Misalnya, di dalam fungsi X, beri nama variabel lokal dengan konvensi "X_LOCAL_name".
Ron Burk
34

Seperti bstpierre di atas, saya menggunakan dan merekomendasikan penggunaan variabel output penamaan eksplisit:

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
}

Perhatikan penggunaan mengutip $. Ini akan menghindari menafsirkan konten $resultsebagai karakter khusus shell. Saya telah menemukan bahwa ini adalah urutan besarnya lebih cepat daripadaresult=$(some_func "arg1") ungkapan menangkap gema. Perbedaan kecepatan tampaknya bahkan lebih terkenal menggunakan bash pada MSYS di mana stdout menangkap dari panggilan fungsi hampir menjadi bencana besar.

Tidak apa-apa mengirim variabel lokal karena penduduk lokal secara dinamis dicakup dalam bash:

function another_func() # ARG
{
   local result
   some_func result "$1"
   echo result is $result
}
Markarian451
sumber
4
Ini membantu saya karena saya suka menggunakan beberapa pernyataan gema untuk keperluan debugging / logging. Ungkapan menangkap gema gagal karena menangkap mereka semua. Terima kasih!
AnneTheAgile
Ini adalah solusi terbaik (terbaik kedua)! Bersih, cepat, elegan, masuk akal.
Evi1M4chine
+2 untuk membuatnya tetap nyata. Saya akan katakan. Bagaimana bisa begitu banyak orang mengabaikan menggabungkan bagian echodalam suatu fungsi, dikombinasikan dengan penggantian perintah!
Anthony Rutledge
23

Anda juga dapat menangkap output fungsi:

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

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

Terlihat aneh, tetapi lebih baik daripada menggunakan variabel global IMHO. Melewati parameter berfungsi seperti biasa, cukup taruh di dalam kawat gigi atau backtick.

Chiborg
sumber
11
selain dari catatan sintaksis alternatif, bukankah ini hal yang persis sama dengan yang sudah ditulis op dalam pertanyaannya sendiri?
Luca Borrione
sangat jelas terima kasih!
bcag2
12

Solusi yang paling mudah dan kuat adalah dengan menggunakan substitusi perintah, seperti yang ditulis orang lain:

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

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

Kelemahannya adalah kinerja karena ini membutuhkan proses yang terpisah.

Teknik lain yang disarankan dalam topik ini, yaitu meneruskan nama variabel untuk ditetapkan sebagai argumen, memiliki efek samping, dan saya tidak akan merekomendasikannya dalam bentuk dasarnya. Masalahnya adalah bahwa Anda mungkin perlu beberapa variabel dalam fungsi untuk menghitung nilai kembali, dan mungkin saja nama variabel yang dimaksudkan untuk menyimpan nilai pengembalian akan mengganggu salah satu dari mereka:

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

Anda mungkin, tentu saja, tidak mendeklarasikan variabel internal dari fungsi sebagai lokal, tetapi Anda benar-benar harus selalu melakukannya karena jika tidak, Anda mungkin, secara tidak sengaja menimpa variabel yang tidak terkait dari ruang lingkup induk jika ada satu dengan nama yang sama .

Salah satu solusi yang mungkin adalah deklarasi eksplisit dari variabel yang dikirimkan sebagai global:

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

Jika nama "x" dilewatkan sebagai argumen, baris kedua dari badan fungsi akan menimpa deklarasi lokal sebelumnya. Tetapi nama-nama itu sendiri mungkin masih mengganggu, jadi jika Anda berniat untuk menggunakan nilai yang sebelumnya disimpan dalam variabel yang dikirimkan sebelum menulis nilai kembali di sana, perlu diketahui bahwa Anda harus menyalinnya ke variabel lokal lain di awal; jika tidak hasilnya akan tidak dapat diprediksi! Selain itu, ini hanya akan berfungsi dalam versi BASH terbaru, yaitu 4.2. Lebih banyak kode portabel mungkin menggunakan konstruksi kondisional eksplisit dengan efek yang sama:

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

Mungkin solusi yang paling elegan adalah hanya mencadangkan satu nama global untuk nilai-nilai pengembalian fungsi dan menggunakannya secara konsisten di setiap fungsi yang Anda tulis.

Tomasz Żuk
sumber
3
Ini ^^^. Aliasing yang tidak disengaja yang memecah enkapsulasi adalah masalah besar baik dengan solusi evalmaupun declare -n. Solusi untuk memiliki nama variabel tunggal khusus seperti resultuntuk semua parameter output tampaknya satu-satunya solusi yang tidak memerlukan fungsi untuk mengetahui semua penelepon itu untuk menghindari konflik.
Karsten
12

Seperti disebutkan sebelumnya, cara "benar" untuk mengembalikan string dari suatu fungsi adalah dengan substitusi perintah. Jika fungsi tersebut juga perlu di-output ke konsol (seperti @Mani menyebutkan di atas), buat fd sementara di awal fungsi dan arahkan ke konsol. Tutup fd sementara sebelum mengembalikan string Anda.

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

mengeksekusi skrip tanpa params menghasilkan ...

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

Semoga ini bisa membantu orang

-Andy

Andy
sumber
6
Itu memiliki kegunaannya, tetapi secara keseluruhan Anda harus menghindari membuat pengalihan eksplisit ke konsol; hasilnya mungkin sudah dialihkan, atau skrip mungkin berjalan dalam konteks di mana tidak ada tty. Anda bisa menyiasatinya dengan menduplikasi 3>&1di bagian atas skrip, lalu memanipulasi &1 &3dan placeholder lain &4dalam fungsi. Jelek semua, meskipun.
jmb
8

Anda bisa menggunakan variabel global:

declare globalvar='some string'

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

string globalvar

echo "'${globalvar}'"

Ini memberi

'some other string'
Fritz G. Mehner
sumber
6

Untuk mengilustrasikan komentar saya pada jawaban Andy, dengan manipulasi deskriptor file tambahan untuk menghindari penggunaan /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]"

Tapi tetap saja jahat.

jmb
sumber
3

Cara Anda memilikinya adalah satu-satunya cara untuk melakukan ini tanpa melanggar ruang lingkup. Bash tidak memiliki konsep tipe pengembalian, cukup kode keluar dan deskriptor file (stdin / out / err, dll)

Daenyth
sumber
3

Mengatasi kepala Vicky Ronnen , mempertimbangkan kode berikut:

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



akan memberi

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

Mungkin skenario normal adalah dengan menggunakan sintaks yang digunakan dalam test_inside_a_funcfungsi, sehingga Anda dapat menggunakan kedua metode di sebagian besar kasus, meskipun menangkap output adalah metode yang lebih aman selalu bekerja dalam situasi apa pun, meniru nilai kembali dari fungsi yang Anda bisa temukan dalam bahasa lain, sebagaimana Vicky Ronnenditunjukkan dengan benar.

Luca Borrione
sumber
2

Semua opsi sudah disebutkan, saya kira. Memilih satu dapat datang ke masalah gaya terbaik untuk aplikasi khusus Anda, dan dalam nada itu, saya ingin menawarkan satu gaya tertentu yang saya temukan berguna. Dalam bash, variabel dan fungsi tidak berada dalam ruang nama yang sama. Jadi, memperlakukan variabel dengan nama yang sama dengan nilai fungsi adalah konvensi yang saya temukan meminimalkan bentrokan nama dan meningkatkan keterbacaan, jika saya menerapkannya dengan ketat. Contoh dari kehidupan nyata:

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"
}

Dan, contoh penggunaan fungsi-fungsi tersebut:

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

Seperti yang Anda lihat, status pengembalian ada untuk Anda gunakan saat Anda membutuhkannya, atau abaikan jika tidak. Variabel "dikembalikan" juga dapat digunakan atau diabaikan, tetapi tentu saja hanya setelah fungsi dipanggil.

Tentu saja, ini hanya sebuah konvensi. Anda bebas untuk gagal mengatur nilai terkait sebelum kembali (karena itu konvensi saya untuk selalu membatalkannya di awal fungsi) atau menginjak nilainya dengan memanggil fungsi lagi (mungkin secara tidak langsung). Tetap saja, ini adalah konvensi yang menurut saya sangat berguna jika saya menggunakan banyak fungsi bash.

Bertolak belakang dengan sentimen bahwa ini adalah tanda yang semestinya misalnya "pindah ke perl", filosofi saya adalah bahwa konvensi selalu penting untuk mengelola kompleksitas bahasa apa pun.

Ron Burk
sumber
2

Anda dapat echosebuah string, tetapi menangkapnya dengan memipet ( |) fungsi ke sesuatu yang lain.

Anda dapat melakukannya dengan expr, meskipun ShellCheck melaporkan penggunaan ini sudah usang.

apennebaker
sumber
Masalahnya adalah bahwa hal di sebelah kanan pipa adalah subkulit. Jadi myfunc | read OUTPUT ; echo $OUTPUTtidak menghasilkan apa-apa. myfunc | ( read OUTPUT; echo $OUTPUT )tidak mendapatkan nilai yang diharapkan dan mengklarifikasi apa yang terjadi di sisi kanan. Tapi tentu saja OUTPUT tidak tersedia di tempat yang Anda butuhkan ...
Ed Randall
2

Mereka masalah utama dari setiap skema 'bernama variabel keluaran' di mana penelepon dapat meneruskan nama variabel (apakah menggunakan evalatau declare -n) tidak sengaja alias, yaitu bentrokan nama: Dari sudut pandang enkapsulasi, sangat mengerikan untuk tidak dapat menambah atau mengganti nama variabel lokal dalam suatu fungsi tanpa memeriksa SEMUA penelepon fungsi terlebih dahulu untuk memastikan mereka tidak ingin memberikan nama yang sama dengan parameter output. (Atau ke arah lain, saya tidak ingin harus membaca sumber fungsi yang saya panggil hanya untuk memastikan parameter output yang ingin saya gunakan bukan lokal dalam fungsi itu.)

Satu-satunya cara mengatasinya adalah dengan menggunakan variabel output khusus seperti REPLY(seperti yang disarankan oleh Evi1M4chine ) atau konvensi seperti yang disarankan oleh Ron Burk .

Namun, dimungkinkan untuk memiliki fungsi menggunakan variabel keluaran tetap secara internal , dan kemudian menambahkan gula di atasnya untuk menyembunyikan fakta ini dari pemanggil , seperti yang telah saya lakukan dengan callfungsi pada contoh berikut. Anggap ini bukti konsep, tetapi poin kuncinya adalah

  • Fungsi selalu memberikan nilai kembali ke REPLY , dan juga dapat mengembalikan kode keluar seperti biasa
  • Dari perspektif penelepon, nilai kembali dapat ditetapkan untuk variabel apa pun (lokal atau global) termasuk REPLY(lihat wrappercontoh). Kode keluar dari fungsi dilewati, jadi menggunakannya dalam misalnya a ifatauwhile atau konstruksi serupa berfungsi seperti yang diharapkan.
  • Secara fungsi, pemanggilan fungsi masih berupa satu pernyataan sederhana.

Alasan ini berfungsi adalah karena callfungsi itu sendiri tidak memiliki penduduk setempat dan tidak menggunakan variabel selain REPLY, menghindari potensi bentrokan nama. Pada titik di mana nama variabel output yang ditentukan pemanggil ditetapkan, kita secara efektif berada dalam ruang lingkup pemanggil (secara teknis dalam lingkup callfungsi yang identik ), daripada dalam lingkup fungsi yang dipanggil.

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

Keluaran:

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

bash pattern untuk mengembalikan objek nilai skalar dan array :

definisi

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
}

doa

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
}
Andrei Pozolotin
sumber
0

Dalam program saya, berdasarkan konvensi, ini adalah untuk apa $REPLYvariabel yang sudah ada sebelumnya , yang readdigunakan untuk tujuan yang tepat.

function getSomeString {
  REPLY="tadaa"
}

getSomeString
echo $REPLY

Ini echoes

tadaa

Tetapi untuk menghindari konflik, variabel global lainnya akan dilakukan.

declare result

function getSomeString {
  result="tadaa"
}

getSomeString
echo $result

Jika itu tidak cukup, saya sarankan solusi Markarian451 .

Evi1M4chine
sumber
-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=
agtsoft
sumber