Trả về giá trị trong hàm Bash


305

Tôi đang làm việc với một tập lệnh bash và tôi muốn thực thi một chức năng để in một giá trị trả về:

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

Khi tôi thực thi fun2, nó không in "34". Tại sao điều này là trường hợp?


8
returntrong trường hợp của bạn về cơ bản là giống như exit codephạm vi từ 0 - 255. Sử dụng echotheo đề xuất của @septi. Mã thoát có thể được chụp với $?.
tà tà

1
Trong trường hợp này, nó linh hoạt hơn nhiều khi đã sử dụng echo trong fun1. Đó là ý tưởng của lập trình unix: echo gửi kết quả đến đầu ra tiêu chuẩn mà sau đó có thể được sử dụng lại bởi các chức năng khác với res = $ (fun1) - hoặc được chuyển trực tiếp đến các chức năng khác:function a() { echo 34; } function b() { while read data; do echo $data ; done ;} a | b
Arne Babenhauserheide

Cách thích hợp để làm điều này là đặt các công cụ cấp cao nhất vào một hàm và sử dụng cục bộ với quy tắc phạm vi động của bash. Tôi sẽ tạo một câu trả lời để chứng minh, nó không phải là một tính năng nổi tiếng mà là một tính năng được hỗ trợ đầy đủ.
Oliver


Câu trả lời:


374

Mặc dù bash có một returncâu lệnh, điều duy nhất bạn có thể chỉ định với nó là exittrạng thái riêng của hàm (một giá trị giữa 02550 có nghĩa là "thành công"). Vì vậy, returnkhông phải là những gì bạn muốn.

Bạn có thể muốn chuyển đổi returncâu lệnh của mình thành một echocâu lệnh - theo cách đó, đầu ra chức năng của bạn có thể được ghi lại bằng cách sử dụng $()dấu ngoặc nhọn, dường như chính xác là những gì bạn muốn.

Đây là một ví dụ:

function fun1(){
  echo 34
}

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

Một cách khác để nhận giá trị trả về (nếu bạn chỉ muốn trả về số nguyên 0-255) là $?.

function fun1(){
  return 34
}

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

Ngoài ra, lưu ý rằng bạn có thể sử dụng giá trị trả về để sử dụng logic boolean như fun1 || fun2sẽ chỉ chạy fun2nếu fun1trả về một 0giá trị. Giá trị trả về mặc định là giá trị thoát của câu lệnh cuối cùng được thực thi trong hàm.


2
Bạn cần phải thực thi fun1và sau đó giá trị trả về được lưu trữ trong $?. Mặc dù tôi không khuyên bạn nên làm điều đó
tamasgal

9
Tại sao không sử dụng $??
Pithikos

147
Không, tôi cần giá trị trả lại chết tiệt . Đến địa ngục với tiếng vang.
Tomáš Zato - Tái lập Monica

7
@Blauhirn trong môi trường này, với ||cấu trúc này , mã thoát 0 được coi là thành công và do đó "đúng". Khác không là lỗi và do đó sai. Hãy nghĩ về việc fun1 || fun2viết tắt cho "nếu fun1 trả về thành công hoặc fun2 trả lại thành công" chứ không phải là một toán tử trên chính các giá trị trả về thực tế.
davidA 30/03/2016

6
Điều khó chịu là một hàm cung cấp dữ liệu cũng không thể lặp lại các nội dung khác đến thiết bị xuất chuẩn, bởi vì người gọi sử dụng $ () cũng sẽ nhận được điều đó và bị lẫn lộn hoặc phải phân tích đầu ra. Biến toàn cục không phải là tuyệt vời vì đó chỉ là vấn đề thời gian trước khi bạn sử dụng cùng một var toàn cầu ở hai nơi xảy ra lồng nhau và dữ liệu có thể bị mất. Cần có các kênh riêng để in dữ liệu so với gửi dữ liệu trở lại.
Oliver

68

$(...)chụp văn bản được gửi đến thiết bị xuất chuẩn bằng lệnh có trong. returnkhông xuất ra thiết bị xuất chuẩn. $?chứa mã kết quả của lệnh cuối cùng.

fun1 (){
  return 34
}

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

6
returnđược sử dụng để thiết lập $?đó là exit status. Trong ví dụ trên, fun1's exit statussẽ 34. Ngoài ra, lưu ý rằng $(...)cũng bắt được stderr ngoài stdout từ lệnh được chỉ định.
swoop81

59

Các hàm trong Bash không phải là các hàm như trong ngôn ngữ khác; họ thực sự ra lệnh. Vì vậy, các hàm được sử dụng như thể chúng là nhị phân hoặc tập lệnh được tìm nạp từ đường dẫn của bạn. Từ quan điểm của logic chương trình của bạn nên thực sự không có sự khác biệt.

Các lệnh Shell được kết nối bằng các đường ống (còn gọi là luồng) và không phải là kiểu dữ liệu cơ bản hoặc do người dùng định nghĩa, như trong các ngôn ngữ lập trình "thực". Không có thứ nào giống như giá trị trả về cho một lệnh, có thể chủ yếu là vì không có cách nào thực sự để khai báo nó. Nó có thể xảy ra trên trang man, hoặc --helpđầu ra của lệnh, nhưng cả hai chỉ có thể đọc được bằng con người và do đó được ghi vào gió.

Khi một lệnh muốn nhận đầu vào, nó sẽ đọc nó từ luồng đầu vào hoặc danh sách đối số. Trong cả hai trường hợp, chuỗi văn bản phải được phân tích cú pháp.

Khi một lệnh muốn trả về một cái gì đó, nó phải echocho dòng đầu ra của nó. Một cách khác thường được thực hành là lưu trữ giá trị trả về trong các biến toàn cục, chuyên dụng. Ghi vào luồng đầu ra rõ ràng và linh hoạt hơn, bởi vì nó cũng có thể lấy dữ liệu nhị phân. Ví dụ: bạn có thể trả lại BLOB dễ dàng:

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

Như những người khác đã viết trong luồng này, người gọi cũng có thể sử dụng thay thế lệnh $()để nắm bắt đầu ra.

Song song, hàm sẽ "trả về" mã thoát của gpg(GnuPG). Hãy nghĩ về mã thoát như một phần thưởng mà các ngôn ngữ khác không có, hoặc, tùy thuộc vào tính khí của bạn, như là một "Schmutzeffekt" của các hàm shell. Trạng thái này, theo quy ước, 0 khi thành công hoặc một số nguyên trong phạm vi 1-255 cho một thứ khác. Để làm rõ điều này: return(như exit) chỉ có thể lấy một giá trị từ 0-255 và các giá trị khác 0 không nhất thiết là lỗi, như thường được khẳng định.

Khi bạn không cung cấp một giá trị rõ ràng với returntrạng thái được lấy từ lệnh cuối cùng trong câu lệnh / hàm / lệnh Bash và vv. Vì vậy, luôn luôn có một trạng thái, và returnchỉ là một cách dễ dàng để cung cấp nó.


4
+1 để giải thích các chức năng so với các lệnh và cách điều này tác động đến khái niệm gửi dữ liệu lại cho người gọi
Oliver

4
+1 để giải thích rằng lập trình shell là về kết nối các lệnh thông qua các đường ống. Các ngôn ngữ lập trình khác soạn các hàm thông qua các kiểu trả về. Bash soạn các lệnh thông qua các dòng văn bản.
jrahhali

29

Câu returnlệnh đặt mã thoát của hàm, giống như exitsẽ làm cho toàn bộ tập lệnh.

Mã thoát cho lệnh cuối cùng luôn có sẵn trong $?biến.

function fun1(){
  return 34
}

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

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

21

Vấn đề với các câu trả lời khác là chúng sử dụng toàn cục, có thể bị ghi đè khi một số chức năng nằm trong chuỗi cuộc gọi hoặc echocó nghĩa là chức năng của bạn không thể xuất thông tin chẩn đoán (bạn sẽ quên chức năng này và "kết quả", tức là trả về giá trị, sẽ chứa nhiều thông tin hơn người gọi của bạn mong đợi, dẫn đến lỗi lạ) hoặc evalquá nặng và hack.

Cách thích hợp để làm điều này là đặt các công cụ cấp cao nhất vào một hàm và sử dụng localquy tắc phạm vi động của bash. Thí dụ:

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

Đầu ra này

nothing
hi
bye

Phạm vi động có nghĩa là ret_valtrỏ đến một đối tượng khác tùy thuộc vào người gọi! Điều này khác với phạm vi từ vựng, đó là điều mà hầu hết các ngôn ngữ lập trình sử dụng. Đây thực sự là một tính năng tài liệu , chỉ dễ bỏ lỡ và không được giải thích rõ ràng, đây là tài liệu cho nó (nhấn mạnh là của tôi):

Các biến cục bộ của hàm có thể được khai báo với hàm dựng sẵn cục bộ. Các biến này chỉ hiển thị cho hàm và các lệnh mà nó gọi .

Đối với ai đó có nền tảng C / C ++ / Python / Java / C # / javascript, đây có lẽ là trở ngại lớn nhất: các hàm trong bash không phải là các hàm, chúng là các lệnh và hoạt động như vậy: chúng có thể xuất ra stdout/ stderr, chúng có thể dẫn vào / ra, họ có thể trả lại mã thoát. Về cơ bản, không có sự khác biệt giữa việc xác định lệnh trong tập lệnh và tạo tệp thực thi có thể được gọi từ dòng lệnh.

Vì vậy, thay vì viết kịch bản của bạn như thế này:

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

viết nó như thế này:

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

trong đó main()khai báo ret_valas localvà tất cả các hàm khác trả về giá trị thông qua ret_val.

Xem thêm câu hỏi Unix & Linux sau: Phạm vi của các biến cục bộ trong các hàm Shell .

Một giải pháp khác, có lẽ thậm chí tốt hơn tùy thuộc vào tình huống, là giải pháp được đăng bởi ya.teck sử dụng local -n.


17

Một cách khác để đạt được điều này là tham chiếu tên (yêu cầu Bash 4.3+).

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

example RESULT
echo $RESULT

3
bất cứ ai tự hỏi điều gì -n <name>=<reference>: làm cho biến mới được tạo tham chiếu đến một biến khác được chỉ bởi <reference>. Bài tập tiếp <name>theo được thực hiện trên biến được tham chiếu.
Valerio

7

Tôi muốn làm như sau nếu chạy trong một kịch bản có chức năng được xác định:

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"

Tôi thích điều này, vì sau đó tôi có thể bao gồm các câu lệnh echo trong các chức năng của mình nếu tôi muốn

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

5

Là một tiện ích bổ sung cho các bài đăng xuất sắc của người khác, đây là một bài viết tóm tắt các kỹ thuật này:

  • đặt một biến toàn cục
  • đặt biến toàn cục, tên bạn đã chuyển cho hàm
  • đặt mã trả về (và chọn mã bằng $?)
  • 'echo' một số dữ liệu (và chọn nó với MYVAR = $ (chức năng của tôi))

Trả về các giá trị từ các hàm Bash


Đây là câu trả lời tốt nhất, vì bài viết thảo luận rõ ràng về tất cả các lựa chọn.
mzimmermann

-2

Git Bash trên Windows sử dụng mảng cho nhiều giá trị trả về

MÃ 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:"

ĐẦU RA MỞ RỘNG:

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:
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.