Mengembalikan nilai dalam fungsi Bash

305

Saya bekerja dengan skrip bash dan saya ingin menjalankan fungsi untuk mencetak nilai kembali:

function fun1(){
  return 34
}
function fun2(){
  local res=$(fun1)
  echo $res
}

Ketika saya mengeksekusi fun2, itu tidak mencetak "34". Mengapa demikian?

mindia
sumber
8
returndalam kasus Anda pada dasarnya sama dengan exit codeyang berkisar dari 0 - 255. Gunakan echoseperti yang disarankan oleh @septi. Kode keluar dapat ditangkap dengan $?.
devnull
1
Dalam hal ini, jauh lebih fleksibel untuk menggunakan echo di fun1. Ini adalah ide unix-programming: gema mengirimkan hasilnya ke output standar yang kemudian dapat digunakan kembali oleh fungsi lain dengan res = $ (fun1) - atau langsung disalurkan ke fungsi lain:function a() { echo 34; } function b() { while read data; do echo $data ; done ;} a | b
Arne Babenhauserheide
Cara yang tepat untuk melakukan ini adalah dengan meletakkan hal-hal tingkat atas dalam suatu fungsi dan menggunakan lokal dengan aturan pelingkupan dinamis bash. Saya akan membuat jawaban untuk menunjukkan, ini bukan fitur yang terkenal tetapi didukung sepenuhnya.
Oliver
Lihat juga: stackoverflow.com/a/8743103/12887
Jonathan Tran

Jawaban:

374

Meskipun bash memiliki returnpernyataan, satu-satunya hal yang dapat Anda tentukan adalah status fungsi itu sendiri exit(nilai antara 0dan 255, 0 yang berarti "sukses"). Jadi returnbukan yang Anda inginkan.

Anda mungkin ingin mengubah returnpernyataan Anda menjadi echopernyataan - dengan cara itu output fungsi Anda dapat ditangkap menggunakan $()kurung kurawal, yang tampaknya persis seperti yang Anda inginkan.

Berikut ini sebuah contoh:

function fun1(){
  echo 34
}

function fun2(){
  local res=$(fun1)
  echo $res
}

Cara lain untuk mendapatkan nilai kembali (jika Anda hanya ingin mengembalikan integer 0-255) adalah $?.

function fun1(){
  return 34
}

function fun2(){
  fun1
  local res=$?
  echo $res
}

Juga, perhatikan bahwa Anda dapat menggunakan nilai kembali untuk menggunakan logika boolean seperti fun1 || fun2hanya akan berjalan fun2jika fun1mengembalikan 0nilai. Nilai pengembalian default adalah nilai keluar dari pernyataan terakhir yang dieksekusi dalam fungsi.

tamasgal
sumber
2
Anda perlu mengeksekusi fun1dan kemudian nilai balik disimpan $?. Meskipun saya tidak akan merekomendasikan untuk melakukan itu ...
Tamasgal
9
Kenapa tidak digunakan $??
Pithikos
147
Tidak, aku butuh nilai pengembaliannya . Persetan dengan gema.
Tomáš Zato - Reinstate Monica
7
@Blauhirn di lingkungan ini, dengan ||konstruk ini , kode keluar 0 dianggap sukses dan karenanya "benar". Bukan nol adalah kesalahan dan karenanya salah. Anggap fun1 || fun2sebagai singkatan untuk "jika fun1 mengembalikan kesuksesan atau fun2 mengembalikan kesuksesan" daripada operator pada nilai pengembalian aktual sendiri.
davidA
6
Yang menjengkelkan adalah bahwa fungsi yang seharusnya menyediakan data juga tidak dapat menggemakan hal-hal lain ke stdout, karena pemanggil menggunakan $ () akan menerima itu juga dan menjadi bingung atau harus mengurai output. Variabel global tidak hebat karena hanya masalah waktu sebelum Anda menggunakan global var yang sama di dua tempat yang bersarang dan data bisa hilang. Harus ada saluran terpisah untuk mencetak data vs mengirim kembali data.
Oliver
68

$(...)menangkap teks yang dikirim ke stdout oleh perintah yang ada di dalamnya. returntidak menghasilkan stdout. $?berisi kode hasil dari perintah terakhir.

fun1 (){
  return 34
}

fun2 (){
  fun1
  local res=$?
  echo $res
}
Ignacio Vazquez-Abrams
sumber
6
Ya returndigunakan untuk pengaturan $?yang merupakan exit status. Dalam contoh di atas, fun1's exit statusakan 34. Juga, perhatikan bahwa $(...)juga menangkap stderr selain stdout dari perintah yang ditentukan.
swoop81
59

Fungsi dalam Bash bukan fungsi seperti dalam bahasa lain; mereka sebenarnya perintah. Jadi fungsi digunakan seolah-olah mereka binari atau skrip yang diambil dari jalur Anda. Dari perspektif logika program Anda seharusnya tidak ada perbedaan.

Perintah Shell dihubungkan oleh pipa (alias aliran), dan bukan tipe data dasar atau yang ditentukan pengguna, seperti dalam bahasa pemrograman "nyata". Tidak ada yang namanya nilai pengembalian untuk suatu perintah, mungkin sebagian besar karena tidak ada cara nyata untuk mendeklarasikannya. Itu bisa terjadi pada halaman manual, atau --helpoutput dari perintah, tetapi keduanya hanya dapat dibaca oleh manusia dan karenanya ditulis ke angin.

Ketika sebuah perintah ingin mendapatkan input, ia membacanya dari aliran inputnya, atau dari daftar argumen. Dalam kedua kasus, string teks harus diuraikan.

Ketika sebuah perintah ingin mengembalikan sesuatu, echoia harus menjalankannya ke aliran outputnya. Cara lain yang sering dilakukan adalah menyimpan nilai pengembalian dalam variabel global khusus. Menulis ke aliran keluaran lebih jelas dan lebih fleksibel, karena dapat juga mengambil data biner. Misalnya, Anda dapat mengembalikan BLOB dengan mudah:

encrypt() {
    gpg -c -o- $1 # encrypt data in filename to stdout (asks for a passphrase)
}

encrypt public.dat > private.dat # write function result to file

Seperti orang lain telah menulis di utas ini, pemanggil juga dapat menggunakan substitusi perintah $()untuk menangkap output.

Secara paralel, fungsi akan "mengembalikan" kode keluar dari gpg(GnuPG). Pikirkan kode keluar sebagai bonus yang tidak dimiliki bahasa lain, atau, tergantung pada temperamen Anda, sebagai "Schmutzeffekt" fungsi shell. Status ini, berdasarkan konvensi, 0 berhasil atau bilangan bulat dalam kisaran 1-255 untuk hal lain. Untuk memperjelas ini: return(seperti exit) hanya dapat mengambil nilai dari 0-255, dan nilai selain 0 tidak selalu merupakan kesalahan, seperti yang sering dinyatakan.

Ketika Anda tidak memberikan nilai eksplisit dengan returnstatus diambil dari perintah terakhir dalam pernyataan / fungsi / perintah Bash dan sebagainya. Jadi selalu ada status, dan returnhanya cara mudah untuk menyediakannya.

Andreas Spindler
sumber
4
+1 untuk menjelaskan fungsi vs perintah dan bagaimana ini berdampak pada gagasan mengirim data kembali ke pemanggil
Oliver
4
+1 untuk menjelaskan bahwa pemrograman shell adalah tentang menghubungkan perintah melalui pipa. Bahasa pemrograman lain menyusun fungsi melalui tipe kembali. Bash menyusun perintah melalui aliran teks.
jrahhali
29

The returnpernyataan menetapkan kode keluar dari fungsi, sama seperti exitakan melakukan untuk seluruh naskah.

Kode keluar untuk perintah terakhir selalu tersedia dalam $?variabel.

function fun1(){
  return 34
}

function fun2(){
  local res=$(fun1)
  echo $? # <-- Always echos 0 since the 'local' command passes.

  res=$(fun1)
  echo $?  #<-- Outputs 34
}
Austin Phillips
sumber
21

Masalah dengan jawaban lain adalah mereka menggunakan global, yang dapat ditimpa ketika beberapa fungsi dalam rantai panggilan, atau echoyang berarti fungsi Anda tidak dapat menampilkan informasi diagnostik (Anda akan lupa fungsi Anda melakukan ini dan "hasil", yaitu mengembalikan nilai, akan mengandung lebih banyak info dari yang diharapkan penelepon Anda, yang mengarah ke bug aneh), atau evalyang terlalu berat dan peretasan.

Cara yang tepat untuk melakukan ini adalah dengan meletakkan hal-hal tingkat atas dalam suatu fungsi dan menggunakan localaturan pelingkupan dinamis dengan bash. Contoh:

func1() 
{
    ret_val=hi
}

func2()
{
    ret_val=bye
}

func3()
{
    local ret_val=nothing
    echo $ret_val
    func1
    echo $ret_val
    func2
    echo $ret_val
}

func3

Ini output

nothing
hi
bye

Pelingkupan dinamis berarti ret_valmenunjuk ke objek yang berbeda tergantung pada pemanggil! Ini berbeda dari pelingkupan leksikal, yang digunakan oleh sebagian besar bahasa pemrograman. Ini sebenarnya adalah fitur yang terdokumentasi , mudah dilewatkan, dan tidak dijelaskan dengan baik, berikut adalah dokumentasi untuk itu (penekanan ada pada saya):

Variabel lokal ke fungsi dapat dideklarasikan dengan builtin lokal. Variabel-variabel ini hanya dapat dilihat oleh fungsi dan perintah yang diminta .

Untuk seseorang dengan latar belakang javascript C / C ++ / Python / Java / C # /, ini mungkin merupakan rintangan terbesar: fungsi dalam bash bukan fungsi, mereka adalah perintah, dan berperilaku seperti itu: mereka dapat menampilkan ke stdout/ stderr, mereka dapat mengirim pipa ke / out, mereka dapat mengembalikan kode keluar. Pada dasarnya tidak ada perbedaan antara mendefinisikan perintah dalam skrip dan membuat executable yang dapat dipanggil dari baris perintah.

Jadi alih-alih menulis skrip Anda seperti ini:

top-level code 
bunch of functions
more top-level code

tulis seperti ini:

# define your main, containing all top-level code
main() 
bunch of functions
# call main
main  

di mana main()menyatakan ret_valsebagai localdan semua fungsi lainnya mengembalikan nilai melalui ret_val.

Lihat juga pertanyaan Unix & Linux berikut: Cakupan Variabel Lokal di Fungsi Shell .

Solusi lain, mungkin lebih baik tergantung pada situasinya, adalah solusi yang diposting oleh ya.teck yang menggunakan local -n.

Oliver
sumber
17

Cara lain untuk mencapai ini adalah referensi nama (membutuhkan Bash 4.3+).

function example {
  local -n VAR=$1
  VAR=foo
}

example RESULT
echo $RESULT
ya.teck
sumber
3
orang bertanya-tanya apa yang -n <name>=<reference>dilakukannya: membuat baru dibuat variabel referensi ke satu sama lain ditunjukkan oleh <reference>. Penugasan lebih lanjut <name>dilakukan pada variabel yang dirujuk.
Valerio
7

Saya suka melakukan hal berikut jika menjalankan skrip di mana fungsi didefinisikan:

POINTER= # used for function return values

my_function() {
    # do stuff
    POINTER="my_function_return"
}

my_other_function() {
    # do stuff
    POINTER="my_other_function_return"
}

my_function
RESULT="$POINTER"

my_other_function
RESULT="$POINTER"

Saya suka ini, karena saya kemudian dapat memasukkan pernyataan gema dalam fungsi saya jika saya mau

my_function() {
    echo "-> my_function()"
    # do stuff
    POINTER="my_function_return"
    echo "<- my_function. $POINTER"
}
dokter
sumber
5

Sebagai tambahan pada postingan bagus orang lain, inilah artikel yang merangkum teknik ini:

  • mengatur variabel global
  • atur variabel global, yang namanya Anda lewati ke fungsi
  • setel kode pengembalian (dan ambil dengan $?)
  • 'echo' beberapa data (dan ambil dengan MYVAR = $ (fungsi saya))

Mengembalikan Nilai dari Fungsi Bash

Tom Hundt
sumber
Ini adalah jawaban terbaik, karena artikel ini membahas semua opsi dengan bersih.
mzimmermann
-2

Git Bash di Windows menggunakan array untuk beberapa nilai kembali

KODE BASH:

#!/bin/bash

##A 6-element array used for returning
##values from functions:
declare -a RET_ARR
RET_ARR[0]="A"
RET_ARR[1]="B"
RET_ARR[2]="C"
RET_ARR[3]="D"
RET_ARR[4]="E"
RET_ARR[5]="F"


function FN_MULTIPLE_RETURN_VALUES(){

   ##give the positional arguments/inputs
   ##$1 and $2 some sensible names:
   local out_dex_1="$1" ##output index
   local out_dex_2="$2" ##output index

   ##Echo for debugging:
   echo "running: FN_MULTIPLE_RETURN_VALUES"

   ##Here: Calculate output values:
   local op_var_1="Hello"
   local op_var_2="World"

   ##set the return values:
   RET_ARR[ $out_dex_1 ]=$op_var_1
   RET_ARR[ $out_dex_2 ]=$op_var_2
}


echo "FN_MULTIPLE_RETURN_VALUES EXAMPLES:"
echo "-------------------------------------------"
fn="FN_MULTIPLE_RETURN_VALUES"
out_dex_a=0
out_dex_b=1
eval $fn $out_dex_a $out_dex_b  ##<--Call function
a=${RET_ARR[0]} && echo "RET_ARR[0]: $a "
b=${RET_ARR[1]} && echo "RET_ARR[1]: $b "
echo
##----------------------------------------------##
c="2"
d="3"
FN_MULTIPLE_RETURN_VALUES $c $d ##<--Call function
c_res=${RET_ARR[2]} && echo "RET_ARR[2]: $c_res "
d_res=${RET_ARR[3]} && echo "RET_ARR[3]: $d_res "
echo
##----------------------------------------------##
FN_MULTIPLE_RETURN_VALUES 4 5  ##<---Call function
e=${RET_ARR[4]} && echo "RET_ARR[4]: $e "
f=${RET_ARR[5]} && echo "RET_ARR[5]: $f "
echo
##----------------------------------------------##


read -p "Press Enter To Exit:"

OUTPUT YANG DIHARAPKAN:

FN_MULTIPLE_RETURN_VALUES EXAMPLES:
-------------------------------------------
running: FN_MULTIPLE_RETURN_VALUES
RET_ARR[0]: Hello
RET_ARR[1]: World

running: FN_MULTIPLE_RETURN_VALUES
RET_ARR[2]: Hello
RET_ARR[3]: World

running: FN_MULTIPLE_RETURN_VALUES
RET_ARR[4]: Hello
RET_ARR[5]: World

Press Enter To Exit:
JMI MADISON
sumber