ส่งคืนค่าในฟังก์ชัน Bash


305

ฉันทำงานกับสคริปต์ทุบตีและฉันต้องการเรียกใช้ฟังก์ชันเพื่อพิมพ์ค่าส่งคืน:

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

เมื่อฉันเรียกใช้fun2งานจะไม่พิมพ์ "34" ทำไมเป็นกรณีนี้


8
returnในกรณีของคุณเป็นหลักเช่นเดียวกับซึ่งมีตั้งแต่exit code 0 - 255ใช้echoตามที่แนะนำโดย @septi $?รหัสออกสามารถจับกับ
devnull

1
ในกรณีนี้มันมีความยืดหยุ่นมากกว่าในการใช้ echo ใน fun1 มันเป็นความคิดของการเขียนโปรแกรมยูนิกซ์: echo ส่งผลลัพธ์ไปยังเอาต์พุตมาตรฐานซึ่งสามารถนำกลับมาใช้ใหม่โดยฟังก์ชั่นอื่น ๆ ด้วย res = $ (fun1) - หรือส่งโดยตรงไปยังฟังก์ชั่นอื่น ๆ :function a() { echo 34; } function b() { while read data; do echo $data ; done ;} a | b
Arne Babenhauserheide

วิธีที่เหมาะสมในการทำเช่นนี้คือการวางสิ่งของระดับบนสุดไว้ในฟังก์ชันและใช้โลคอลกับกฎการกำหนดขอบเขตแบบไดนามิกของ bash ฉันจะสร้างคำตอบเพื่อแสดงให้เห็นว่ามันไม่ใช่คุณสมบัติที่เป็นที่รู้จัก แต่ได้รับการสนับสนุนอย่างเต็มที่
โอลิเวอร์

ดูเพิ่มเติมที่: stackoverflow.com/a/8743103/12887
Jonathan Tran

คำตอบ:


373

แม้ว่า bash จะมีreturnคำสั่งสิ่งเดียวที่คุณสามารถระบุได้คือexitสถานะของฟังก์ชัน(ค่าระหว่าง0และ255, 0 หมายถึง "สำเร็จ") ดังนั้นreturnไม่ใช่สิ่งที่คุณต้องการ

คุณอาจต้องการที่จะแปลงreturnคำสั่งของคุณเป็นechoคำสั่ง - วิธีที่ฟังก์ชั่นเอาท์พุทของคุณจะถูกจับโดยใช้$()เครื่องหมายวงเล็บปีกกาซึ่งดูเหมือนจะเป็นสิ่งที่คุณต้องการ

นี่คือตัวอย่าง:

function fun1(){
  echo 34
}

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

อีกวิธีหนึ่งที่จะได้รับค่าตอบแทน (ถ้าคุณเพียงต้องการที่จะกลับมาเป็นจำนวนเต็ม 0-255) $?เป็น

function fun1(){
  return 34
}

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

นอกจากนี้โปรดทราบว่าคุณสามารถใช้ค่าส่งคืนเพื่อใช้ตรรกะบูลีนเช่นfun1 || fun2จะทำงานเฉพาะในfun2กรณีที่fun1ส่งกลับ0ค่า ค่าส่งคืนเริ่มต้นคือค่าออกจากคำสั่งสุดท้ายที่ดำเนินการภายในฟังก์ชั่น


2
คุณจำเป็นต้องมีการดำเนินการแล้วค่าส่งกลับถูกเก็บไว้ในfun1 $?ถึงแม้ว่าผมจะไม่แนะนำให้ทำอย่างนั้น ...
tamasgal

9
ทำไมไม่ใช้$??
Pithikos

147
ไม่ฉันต้องแช่งค่าตอบแทน ไปลงนรกด้วยเสียงสะท้อน
Tomáš Zato - Reinstate Monica

7
@Blauhirn ในสภาพแวดล้อมนี้ด้วยการ||สร้างนี้รหัสออกจาก 0 ถือว่าเป็นความสำเร็จและดังนั้นจึงเป็น "ความจริง" ไม่ใช่ศูนย์คือข้อผิดพลาดและดังนั้นจึงเป็นเท็จ ลองนึกภาพว่าfun1 || fun2"ถ้า fun1 คืนความสำเร็จหรือ fun2 คืนความสำเร็จ" แทนที่จะใช้โอเปอเรเตอร์ในค่าส่งคืนจริง
davidA

6
สิ่งที่น่ารำคาญก็คือฟังก์ชั่นที่ควรให้ข้อมูลไม่สามารถสะท้อนสิ่งอื่น ๆ เพื่อ stdout ได้เพราะผู้โทรที่ใช้ $ () จะได้รับเช่นกันและสับสนหรือต้องแยกวิเคราะห์เอาต์พุต ตัวแปรโกลบอลนั้นไม่ดีนักเพราะมันใช้เวลาสักครู่ก่อนที่คุณจะใช้ var ส่วนกลางเดียวกันในสองแห่งที่ซ้อนกันและข้อมูลอาจสูญหายได้ ควรมีแชนเนลที่แยกต่างหากสำหรับการพิมพ์ข้อมูลและการส่งข้อมูลกลับ
โอลิเวอร์

68

$(...)จับข้อความที่ส่งไปยัง stdout โดยคำสั่งที่อยู่ภายใน returnไม่ส่งออกไปยัง stdout $?มีรหัสผลลัพธ์ของคำสั่งสุดท้าย

fun1 (){
  return 34
}

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

6
ใช่returnถูกนำมาใช้สำหรับการตั้งค่าซึ่งเป็น$? exit statusในตัวอย่างข้างต้นfun1's จะเป็นexit status 34นอกจากนี้โปรดทราบว่า$(...)จะจับ stderr นอกเหนือจาก stdout จากคำสั่งที่ระบุ
swoop81

59

ฟังก์ชั่นใน Bash ไม่ได้ทำงานเหมือนในภาษาอื่น พวกเขากำลังสั่ง ดังนั้นฟังก์ชั่นจะถูกใช้ราวกับว่าพวกเขาเป็นไบนารีหรือสคริปต์ที่ดึงมาจากเส้นทางของคุณ จากมุมมองของตรรกะโปรแกรมของคุณไม่ควรมีความแตกต่าง

คำสั่งเชลล์เชื่อมต่อกันด้วยไพพ์ (กระแสข้อมูล) และไม่ใช่ชนิดข้อมูลพื้นฐานหรือที่ผู้ใช้กำหนดเช่นเดียวกับในภาษาการเขียนโปรแกรม "ของจริง" ไม่มีสิ่งเช่นค่าตอบแทนสำหรับคำสั่งอาจเป็นเพราะส่วนใหญ่ไม่มีวิธีการประกาศ มันอาจเกิดขึ้นใน man-page หรือ--helpเอาต์พุตของคำสั่ง แต่ทั้งคู่เป็นเพียงมนุษย์ที่สามารถอ่านได้และด้วยเหตุนี้จะถูกเขียนลงในลม

เมื่อคำสั่งต้องการรับอินพุตคำสั่งจะอ่านจากอินพุตสตรีมหรือรายการอาร์กิวเมนต์ ในทั้งสองกรณีจะต้องแยกสตริงข้อความ

เมื่อคำสั่งต้องการส่งคืนบางสิ่งมันจะechoไปยังเอาต์พุตสตรีม วิธีที่ได้รับการฝึกฝนอีกวิธีหนึ่งคือการจัดเก็บค่าส่งคืนในตัวแปรโกลบอลเฉพาะ การเขียนไปยังกระแสข้อมูลขาออกนั้นมีความชัดเจนและมีความยืดหยุ่นมากกว่าเพราะมันสามารถรับข้อมูลแบบไบนารี่ได้เช่นกัน ตัวอย่างเช่นคุณสามารถส่งคืน BLOB ได้อย่างง่ายดาย:

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

ตามที่คนอื่นเขียนไว้ในเธรดนี้ผู้เรียกสามารถใช้การทดแทนคำสั่ง$()เพื่อดักจับเอาต์พุต

ฟังก์ชันจะ "คืน" รหัสทางออกของgpg(GnuPG) คิดว่ารหัสการออกเป็นโบนัสที่ภาษาอื่นไม่มีหรือขึ้นอยู่กับอารมณ์ของคุณในฐานะ "Schmutzeffekt" ของฟังก์ชันเชลล์ สถานะนี้ตามแบบแผน 0 เมื่อสำเร็จหรือเลขจำนวนเต็มในช่วง 1-255 สำหรับอย่างอื่น ในการทำให้ชัดเจน: return(เช่นexit) สามารถรับค่าได้ตั้งแต่ 0-255 เท่านั้นและค่าอื่น ๆ ที่ไม่ใช่ 0 นั้นไม่จำเป็นต้องเป็นข้อผิดพลาดตามที่มักถูกยืนยัน

เมื่อคุณไม่ระบุค่าที่ชัดเจนพร้อมreturnสถานะจะถูกนำมาจากคำสั่งสุดท้ายในคำสั่ง Bash / function / command และอื่น ๆ ดังนั้นจึงมีสถานะอยู่เสมอและreturnเป็นวิธีที่ง่ายในการจัดเตรียม


4
+1 สำหรับการอธิบายฟังก์ชั่นเทียบกับคำสั่งและวิธีการนี้ส่งผลกระทบต่อความคิดในการส่งข้อมูลกลับไปยังผู้โทร
Oliver

4
+1 สำหรับการอธิบายว่าการเขียนโปรแกรมเชลล์เกี่ยวกับการเชื่อมต่อคำสั่งผ่านไพพ์ ภาษาโปรแกรมอื่น ๆ ประกอบฟังก์ชั่นผ่านประเภทผลตอบแทน Bash รวบรวมคำสั่งผ่านสตรีมของข้อความ
jrahhali

29

returnคำสั่งกำหนดรหัสทางออกของฟังก์ชั่นที่มากเช่นเดียวกับexitที่จะทำสำหรับสคริปต์ทั้งหมด

โค้ดทางออกสำหรับคำสั่งสุดท้ายพร้อมใช้งานเสมอใน$?ตัวแปร

function fun1(){
  return 34
}

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

  res=$(fun1)
  echo $?  #<-- Outputs 34
}

20

ปัญหากับคำตอบอื่น ๆ ก็คือพวกเขาอาจใช้ทั่วโลกซึ่งสามารถเขียนทับเมื่อฟังก์ชั่นหลายอย่างอยู่ในสายโซ่หรือechoซึ่งหมายความว่าฟังก์ชั่นของคุณไม่สามารถส่งออกข้อมูลการวินิจฉัย (คุณจะลืมฟังก์ชั่นของคุณ มูลค่าจะมีข้อมูลมากกว่าที่ผู้โทรของคุณคาดว่าจะนำไปสู่ข้อผิดพลาดที่แปลก) หรือevalหนักเกินไปและแฮ็ค

วิธีที่เหมาะสมในการทำเช่นนี้คือการวางสิ่งของระดับบนสุดไว้ในฟังก์ชันและใช้localกฎการกำหนดขอบเขตแบบไดนามิกของ bash ตัวอย่าง:

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

ผลลัพธ์นี้

nothing
hi
bye

การกำหนดขอบเขตแบบไดนามิกหมายถึงการret_valชี้ไปยังวัตถุอื่นที่แตกต่างกันไปตามผู้โทร! สิ่งนี้แตกต่างจากการกำหนดขอบเขตคำศัพท์ซึ่งเป็นสิ่งที่ภาษาการเขียนโปรแกรมส่วนใหญ่ใช้ นี่เป็นคุณลักษณะที่มีการบันทึกไว้ง่าย ๆ ที่จะพลาดและไม่ได้อธิบายได้ดีนี่คือเอกสารสำหรับมัน (การเน้นคือของฉัน):

ตัวแปรท้องถิ่นฟังก์ชั่นอาจมีการประกาศด้วย builtin ท้องถิ่น ตัวแปรเหล่านี้จะปรากฏเฉพาะกับฟังก์ชั่นและคำสั่งมันจะเรียก

สำหรับผู้ที่มีพื้นหลัง C / C ++ / Python / Java / C # / javascript นี่อาจเป็นอุปสรรค์ที่ยิ่งใหญ่ที่สุด: ฟังก์ชั่นใน bash ไม่ใช่ฟังก์ชั่นพวกเขาเป็นคำสั่งและประพฤติเช่น: พวกเขาสามารถส่งออกไปstdout/ stderrพวกเขาสามารถ / out พวกเขาสามารถส่งคืนรหัสออกได้ โดยทั่วไปจะไม่มีความแตกต่างระหว่างการกำหนดคำสั่งในสคริปต์และการสร้างโปรแกรมที่สามารถเรียกได้จากบรรทัดคำสั่ง

ดังนั้นแทนที่จะเขียนสคริปต์ของคุณเช่นนี้:

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

เขียนแบบนี้:

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

ที่main()ประกาศret_valเป็นlocalและฟังก์ชั่นอื่น ๆ ret_valทั้งหมดกลับค่าผ่านทาง

ดูเพิ่มเติมดังต่อไปนี้ Unix และ Linux คำถาม: ขอบเขตของตัวแปรท้องถิ่นในฟังก์ชั่นเชลล์

อีกวิธีการแก้ปัญหาอาจดียิ่งขึ้นขึ้นอยู่กับสถานการณ์เป็นหนึ่งโพสต์โดย ya.tecklocal -nซึ่งการใช้งาน


17

อีกวิธีในการบรรลุเป้าหมายนี้คือการอ้างอิงชื่อ (ต้องใช้ Bash 4.3+)

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

example RESULT
echo $RESULT

3
ใครก็ตามที่สงสัยว่า-n <name>=<reference>ทำอะไร: ทำให้ตัวแปรที่สร้างขึ้นใหม่เป็นการอ้างอิงไปยังอีกอันหนึ่งที่ชี้<reference>ไป การมอบหมายเพิ่มเติมให้<name>ดำเนินการกับตัวแปรที่อ้างอิง
Valerio

7

ฉันต้องการทำสิ่งต่อไปนี้หากทำงานในสคริปต์ที่กำหนดฟังก์ชัน:

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"

ฉันชอบสิ่งนี้เพราะฉันสามารถรวมคำสั่ง echo ในฟังก์ชั่นของฉันถ้าฉันต้องการ

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

5

ในฐานะที่เป็นส่วนเสริมของโพสต์ที่ยอดเยี่ยมของผู้อื่นนี่คือบทความที่สรุปเทคนิคเหล่านี้:

  • ตั้งค่าตัวแปรส่วนกลาง
  • ตั้งค่าตัวแปรส่วนกลางที่มีชื่อคุณผ่านไปยังฟังก์ชั่น
  • ตั้งรหัสส่งคืน (และรับด้วย $?)
  • 'echo' ข้อมูลบางอย่าง (และรับด้วย MYVAR = $ (myfunction))

การคืนค่าจากฟังก์ชัน Bash


นี่คือคำตอบที่ดีที่สุดเนื่องจากบทความกล่าวถึงตัวเลือกทั้งหมดอย่างถี่ถ้วน
mzimmermann

-2

Git BashบนWindowsโดยใช้อาร์เรย์สำหรับค่าส่งคืนหลายค่า

รหัสทุบตี:

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

ผลลัพธ์ที่คาดหวัง:

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:
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.