Cách trả về giá trị chuỗi từ hàm Bash


461

Tôi muốn trả về một chuỗi từ hàm Bash.

Tôi sẽ viết ví dụ trong java để hiển thị những gì tôi muốn làm:

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

String variable = getSomeString();

Ví dụ dưới đây hoạt động trong bash, nhưng có cách nào tốt hơn để làm điều này không?

function getSomeString {
   echo "tadaa"
}

VARIABLE=$(getSomeString)

4
Bên cạnh đó, function funcName {là cú pháp kế thừa tiền POSIX được kế thừa từ ksh đầu tiên (nơi nó có sự khác biệt về ngữ nghĩa mà bash không tôn vinh). funcName() {, với không function, nên được sử dụng thay thế; xem wiki.bash-hackers.org/scripting/obsolete
Charles Duffy

Liên kết đó nói rằng sử dụng NAME () COMPOUND-CMD hoặc chức năng NAME {CMDS; } Vậy function myFunction { blah; }là tốt rồi; điều function myFunction() { blah }đó không ổn, tức là việc sử dụng dấu ngoặc đơn với chức năng từ khóa.
Sẽ

Câu trả lời:


290

Không có cách nào tốt hơn tôi biết. Bash chỉ biết mã trạng thái (số nguyên) và chuỗi được ghi vào thiết bị xuất chuẩn.


15
+1 @ tomas-f: bạn phải thực sự cẩn thận với những gì bạn có trong hàm này "getSomeString ()" vì có bất kỳ mã nào cuối cùng sẽ lặp lại sẽ có nghĩa là bạn nhận được chuỗi trả về không chính xác.
Mani

11
Đây chỉ là một lỗi bình thường. Bạn có thể trả về dữ liệu tùy ý bên trong một biến trả về. Mà rõ ràng là một cách tốt hơn.
Evi1M4chine

36
@ Evi1M4chine, ừm ... không, bạn không thể. Bạn có thể đặt một biến toàn cục và gọi nó là "return", như tôi thấy bạn làm trong các tập lệnh của mình. Nhưng đó là theo quy ước, KHÔNG thực sự gắn kết lập trình với việc thực thi mã của bạn. "Rõ ràng là một cách tốt hơn"? À, không. Thay thế lệnh là rõ ràng hơn và mô-đun.
tự đại diện

6
"Thay thế lệnh rõ ràng hơn và mô-đun" sẽ có liên quan nếu câu hỏi là về các lệnh; Câu hỏi này là làm thế nào để trả về một chuỗi, từ hàm bash! Một cách xây dựng để thực hiện những gì OP đã yêu cầu có sẵn kể từ Bash 4.3 (2014?) - xem câu trả lời của tôi dưới đây.
zenaan

4
Câu hỏi ban đầu chứa cách đơn giản nhất để làm điều đó, và hoạt động tốt trong hầu hết các trường hợp. Các giá trị trả về của Bash có lẽ nên được gọi là "mã trả về" vì chúng giống như các giá trị trả về tiêu chuẩn trong tập lệnh và giống như mã thoát lệnh của vỏ số (bạn có thể thực hiện các công cụ như somefunction && echo 'success'). Nếu bạn nghĩ về một chức năng giống như một lệnh khác, nó có ý nghĩa; các lệnh không "trả lại" bất cứ thứ gì khi thoát ngoài mã trạng thái, nhưng chúng có thể xuất ra những thứ trong thời gian đó mà bạn có thể nắm bắt.
Beejor

193

Bạn có thể có hàm lấy một biến làm đối số đầu tiên và sửa đổi biến với chuỗi bạn muốn trả về.

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

In "foo bar rab oof".

Chỉnh sửa : đã thêm trích dẫn ở vị trí thích hợp để cho phép khoảng trắng trong chuỗi giải quyết nhận xét của @Luca Borrione.

Chỉnh sửa : Như một minh chứng, xem chương trình sau đây. Đây là một giải pháp cho mục đích chung: thậm chí nó còn cho phép bạn nhận một chuỗi thành một biến cục bộ.

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

Bản in này:

+ 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

Chỉnh sửa : chứng minh rằng giá trị của biến gốc có sẵn trong các chức năng, như đã sai chỉ trích bởi @Xichen Li trong nhận xét.

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

Điều này cho đầu ra:

+ 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
Câu trả lời này thật tuyệt! Các tham số có thể được thông qua bởi các tham chiếu, tương tự như ý tưởng trong C ++.
Yun Huang

4
Sẽ thật tuyệt khi nhận được phản hồi từ một chuyên gia về câu trả lời đó. Tôi chưa bao giờ thấy rằng được sử dụng trong các kịch bản, có thể vì một lý do tốt. Dù sao: đó là +1 Nó đáng lẽ phải được bình chọn cho câu trả lời đúng
John

Đây không phải là cùng một fgmcâu trả lời được viết theo cách đơn giản sao? Điều này sẽ không hoạt động nếu chuỗi foochứa khoảng trắng, trong khi chuỗi fgmsẽ .. như đang hiển thị.
Luca Borrione

4
@XichenLi: cảm ơn vì đã để lại nhận xét với downvote của bạn; xin vui lòng xem chỉnh sửa của tôi. Bạn có thể nhận giá trị ban đầu của biến trong hàm với \$$1. Nếu bạn đang tìm kiếm một cái gì đó khác nhau, xin vui lòng cho tôi biết.
bstpierre

1
@timiscoding Điều đó có thể được sửa với a printf '%q' "$var". % q là một chuỗi định dạng để thoát shell. Sau đó chỉ cần vượt qua nó thô.
bb010g

99

Tất cả các câu trả lời ở trên bỏ qua những gì đã được nêu trong trang man của bash.

  • Tất cả các biến được khai báo bên trong một hàm sẽ được chia sẻ với môi trường gọi.
  • Tất cả các biến được khai báo cục bộ sẽ không được chia sẻ.

Mã ví dụ

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

Và đầu ra

$ 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

Cũng theo pdksh và ksh kịch bản này cũng làm như vậy!


10
Câu trả lời này có giá trị của nó. Tôi đến đây với suy nghĩ rằng tôi muốn trả về một chuỗi từ một hàm. Câu trả lời này khiến tôi nhận ra rằng đó chỉ là câu nói C # của tôi. Tôi nghi ngờ những người khác có thể có cùng kinh nghiệm.
LOAS

4
@ElmarZander Bạn đã sai, điều này hoàn toàn có liên quan. Đây là một cách đơn giản để đi vào phạm vi toàn cầu một giá trị phạm vi chức năng và một số người sẽ xem xét điều này tốt hơn / đơn giản hơn so với cách tiếp cận eval để xác định lại một biến toàn cục như được bstpierre vạch ra.
KomodoDave

local không thể chuyển sang các tập lệnh không bash, đó là một lý do khiến một số người tránh nó.
don sáng

Câu hỏi: Điều gì về các biến trong vòng lặp?
anu

1
Trên máy mac ($ bash --version GNU bash, phiên bản 3.2.57 (1) -release (x86_64-apple-darwin14) Bản quyền (C) 2007 Free Software Foundation, Inc.), đúng là một biến toàn cục phù hợp là đã khởi tạo, nhưng khi tôi cố gắng tạo hiệu ứng phụ cùng một biến trong hàm f2 khác, hiệu ứng phụ đó không được duy trì. Vì vậy, nó có vẻ rất không nhất quán và do đó không tốt cho việc sử dụng của tôi.
AnneTheAgile

45

Bash, kể từ phiên bản 4.3, feb 2014 (?), Có hỗ trợ rõ ràng cho các biến tham chiếu hoặc tham chiếu tên (namerefs), ngoài "eval", với hiệu suất và hiệu ứng gián tiếp có lợi tương tự, và có thể rõ ràng hơn trong các tập lệnh của bạn và cũng khó hơn để "quên" eval "và phải sửa lỗi này":

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

và cũng:

THÔNG SỐ

Một biến có thể được gán thuộc tính nameref bằng cách sử dụng tùy chọn -n cho các lệnh khai báo hoặc lệnh dựng sẵn cục bộ (xem mô tả khai báo và cục bộ bên dưới) để tạo nameref hoặc tham chiếu đến biến khác. Điều này cho phép các biến được thao tác gián tiếp. Bất cứ khi nào biến nameref được tham chiếu hoặc gán cho, thao tác thực sự được thực hiện trên biến được chỉ định bởi giá trị của biến nameref. Một nameref thường được sử dụng trong các hàm shell để chỉ một biến có tên được truyền dưới dạng đối số cho hàm. Chẳng hạn, nếu một tên biến được truyền cho hàm shell làm đối số đầu tiên của nó, thì chạy

      declare -n ref=$1

bên trong hàm tạo ra một biến nameref ref có giá trị là tên biến được truyền làm đối số đầu tiên. Tài liệu tham khảo và bài tập cho ref được coi là tài liệu tham khảo và bài tập cho biến có tên được chuyển là 1 $. Nếu biến điều khiển trong vòng lặp for có thuộc tính nameref, danh sách các từ có thể là danh sách các biến shell và tham chiếu tên sẽ được thiết lập cho mỗi từ trong danh sách, lần lượt, khi vòng lặp được thực thi. Biến mảng không thể được cho thuộc tính -n. Tuy nhiên, các biến nameref có thể tham chiếu các biến mảng và biến mảng được đăng ký. Namerefs có thể được hủy đặt bằng cách sử dụng tùy chọn -n cho phần dựng sẵn chưa đặt. Mặt khác, nếu unset được thực thi với tên của biến nameref làm đối số,

Ví dụ ( EDIT 2 : (cảm ơn Ron) đã đặt tên (tiền tố) tên biến nội bộ hàm, để giảm thiểu xung đột biến ngoài, cuối cùng sẽ trả lời đúng, vấn đề được nêu trong các nhận xét của 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
}

và thử nghiệm ví dụ này:

$ return_a_string result; echo $result
The date is 20160817

Lưu ý rằng bash "khai báo" dựng sẵn, khi được sử dụng trong một hàm, làm cho biến khai báo là "cục bộ" theo mặc định và "-n" cũng có thể được sử dụng với "cục bộ".

Tôi thích phân biệt các biến "khai báo quan trọng" với các biến "nhàm chán cục bộ", vì vậy sử dụng "khai báo" và "cục bộ" theo cách này đóng vai trò là tài liệu.

EDIT 1 - (Trả lời nhận xét bên dưới của Karsten) - Tôi không thể thêm nhận xét bên dưới nữa, nhưng nhận xét của Karsten khiến tôi suy nghĩ, vì vậy tôi đã thực hiện bài kiểm tra sau đây WORKS FINE, AFAICT - Karsten nếu bạn đọc điều này, vui lòng cung cấp một bộ chính xác trong số các bước kiểm tra từ dòng lệnh, hiển thị sự cố mà bạn cho là tồn tại, bởi vì các bước sau hoạt động tốt:

$ return_a_string ret; echo $ret
The date is 20170104

(Tôi đã chạy nó ngay bây giờ, sau khi dán hàm trên vào một thuật ngữ bash - như bạn có thể thấy, kết quả hoạt động tốt.)


4
Tôi hy vọng rằng điều này thấm qua đầu. eval nên là một phương sách cuối cùng. Đáng nói là các biến nameref chỉ khả dụng kể từ bash 4.3 (theo changelog ) (được phát hành trong feb 2014 [?]). Điều này rất quan trọng nếu tính di động là một mối quan tâm. Vui lòng trích dẫn hướng dẫn bash về thực tế declaretạo các biến cục bộ bên trong các hàm (thông tin đó không được cung cấp bởi help declare): "... Khi được sử dụng trong một hàm, khai báo và sắp chữ tạo cho mỗi tên cục bộ, như với lệnh cục bộ, trừ khi - tùy chọn g được cung cấp ... "
init_js

2
Điều này có cùng một vấn đề răng cưa như giải pháp eval. Khi bạn gọi một hàm và truyền vào tên của biến đầu ra, bạn phải tránh truyền tên của một biến được sử dụng cục bộ trong hàm bạn gọi. Đó là một vấn đề lớn về mặt đóng gói, vì bạn không thể thêm hoặc đổi tên các biến cục bộ mới trong một hàm mà không có bất kỳ hàm nào mà người gọi có thể muốn sử dụng tên đó cho tham số đầu ra.
Karsten

1
@Karsten đồng ý. trong cả hai trường hợp (eval và namerefs), bạn có thể phải chọn một tên khác. Một lợi thế với cách tiếp cận nameref so với eval là người ta không phải đối phó với các chuỗi thoát. Tất nhiên, bạn luôn có thể làm một cái gì đó như K=$1; V=$2; eval "$A='$V'";, nhưng một lỗi (ví dụ: một tham số trống hoặc bị bỏ qua), và nó sẽ nguy hiểm hơn. @zenaan vấn đề được nêu ra bởi @Karsten sẽ áp dụng nếu bạn chọn "tin nhắn" làm tên biến trả về, thay vì "ret".
init_js

3
Một hàm có lẽ phải được thiết kế ngay từ đầu để chấp nhận đối số nameref, vì vậy tác giả hàm nên nhận thức được khả năng xung đột tên và có thể sử dụng một số quy ước điển hình để tránh điều đó. Ví dụ: bên trong hàm X, đặt tên các biến cục bộ với quy ước "X_LOCAL_name".
Ron Burk

34

Giống như bstpierre ở trên, tôi sử dụng và khuyên bạn nên sử dụng các biến đầu ra đặt tên rõ ràng:

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
}

Lưu ý việc sử dụng trích dẫn $. Điều này sẽ tránh việc diễn giải nội dung $resultdưới dạng các ký tự đặc biệt. Tôi đã thấy rằng đây là một thứ tự cường độ nhanh hơnresult=$(some_func "arg1") thành ngữ bắt được tiếng vang. Sự khác biệt về tốc độ dường như còn đáng chú ý hơn khi sử dụng bash trên MSYS trong đó việc bắt lỗi từ các lệnh gọi hàm gần như là thảm họa.

Bạn có thể gửi một biến cục bộ vì người dân địa phương có phạm vi động trong bash:

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

4
Điều này giúp tôi vì tôi thích sử dụng nhiều câu lệnh echo cho mục đích gỡ lỗi / ghi nhật ký. Thành ngữ bắt tiếng vang thất bại vì nó bắt được tất cả chúng. Cảm ơn bạn!
AnneTheAgile

Đây là giải pháp thích hợp (tốt thứ hai)! Sạch sẽ, nhanh chóng, thanh lịch, hợp lý.
Evi1M4chine

+2 để giữ cho nó thật. Tôi đã định nói. Làm thế nào nhiều người có thể bỏ qua việc kết hợp một echobên trong của một chức năng, kết hợp với thay thế lệnh!
Anthony Rutledge

22

Bạn cũng có thể chụp đầu ra chức năng:

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

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

Trông có vẻ lạ, nhưng tốt hơn là sử dụng các biến toàn cầu IMHO. Truyền tham số hoạt động như bình thường, chỉ cần đặt chúng bên trong niềng răng hoặc backticks.


11
ngoài ghi chú cú pháp thay thế, đây có phải là điều tương tự chính xác mà op đã viết trong câu hỏi của anh ấy không?
Luca Borrione

12

Giải pháp đơn giản và mạnh mẽ nhất là sử dụng thay thế lệnh, như những người khác đã viết:

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

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

Nhược điểm là hiệu suất vì điều này đòi hỏi một quá trình riêng biệt.

Kỹ thuật khác được đề xuất trong chủ đề này, cụ thể là chuyển tên của một biến để gán cho làm đối số, có tác dụng phụ và tôi không đề xuất nó ở dạng cơ bản. Vấn đề là bạn có thể sẽ cần một số biến trong hàm để tính giá trị trả về và có thể xảy ra rằng tên của biến dự định lưu trữ giá trị trả về sẽ can thiệp vào một trong số chúng:

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

Tất nhiên, bạn có thể không khai báo các biến nội bộ của hàm là cục bộ, nhưng bạn thực sự nên luôn luôn làm điều đó vì nếu không, bạn có thể vô tình ghi đè lên một biến không liên quan từ phạm vi cha nếu có một biến có cùng tên .

Một cách giải quyết khác có thể là một tuyên bố rõ ràng về biến được truyền là toàn cục:

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

Nếu tên "x" được truyền dưới dạng đối số, hàng thứ hai của thân hàm sẽ ghi đè khai báo cục bộ trước đó. Nhưng bản thân các tên vẫn có thể can thiệp, vì vậy nếu bạn có ý định sử dụng giá trị được lưu trước đó trong biến đã truyền trước khi ghi giá trị trả về ở đó, hãy lưu ý rằng bạn phải sao chép nó vào một biến cục bộ khác ngay từ đầu; nếu không thì kết quả sẽ khó lường! Bên cạnh đó, điều này sẽ chỉ hoạt động trong phiên bản BASH mới nhất, cụ thể là 4.2. Nhiều mã di động hơn có thể sử dụng các cấu trúc có điều kiện rõ ràng có cùng tác dụng:

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

Có lẽ giải pháp tao nhã nhất là chỉ dành một tên toàn cầu cho các giá trị trả về hàm và sử dụng nó một cách nhất quán trong mọi chức năng bạn viết.


3
Cái này ^^ ^ ^. Các răng cưa vô tình phá vỡ đóng gói là vấn đề lớn với cả evaldeclare -ngiải pháp. Cách giải quyết của việc có một tên biến chuyên dụng duy nhất như resultcho tất cả các tham số đầu ra dường như là giải pháp duy nhất không yêu cầu chức năng để biết tất cả các trình gọi của nó để tránh xung đột.
Karsten

12

Như đã đề cập trước đây, cách "chính xác" để trả về một chuỗi từ một hàm là thay thế lệnh. Trong trường hợp hàm cũng cần xuất ra giao diện điều khiển (như @Mani đã đề cập ở trên), hãy tạo một fd tạm thời khi bắt đầu chức năng và chuyển hướng đến bàn điều khiển. Đóng fd tạm thời trước khi trả lại chuỗi của bạn.

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

thực thi tập lệnh không có thông số sản xuất ...

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

hy vọng điều này sẽ giúp mọi người

-Andy


6
Điều đó có công dụng của nó, nhưng về tổng thể, bạn nên tránh thực hiện chuyển hướng rõ ràng đến bàn điều khiển; đầu ra có thể đã được chuyển hướng hoặc tập lệnh có thể đang chạy trong bối cảnh không tồn tại tty. Bạn có thể khắc phục điều đó bằng cách sao chép 3>&1ở phần đầu của tập lệnh, sau đó thao tác &1 &3và giữ chỗ khác &4trong hàm. Xấu xí tất cả các vòng, mặc dù.
JMB

8

Bạn có thể sử dụng một biến toàn cục:

declare globalvar='some string'

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

string globalvar

echo "'${globalvar}'"

Điều này mang lại

'some other string'

6

Để minh họa nhận xét của tôi về câu trả lời của Andy, với thao tác mô tả tệp bổ sung để tránh sử dụng /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]"

Vẫn khó chịu, mặc dù.


3

Cách bạn có nó là cách duy nhất để làm điều này mà không phá vỡ phạm vi. Bash không có khái niệm về kiểu trả về, chỉ cần thoát mã và mô tả tệp (stdin / out / err, v.v.)


3

Giải quyết vấn đề của Vicky Ronnen , xem xét đoạn mã sau:

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



sẽ cho

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

Có thể kịch bản bình thường là sử dụng cú pháp được sử dụng trong test_inside_a_funchàm, do đó bạn có thể sử dụng cả hai phương thức trong phần lớn các trường hợp, mặc dù nắm bắt đầu ra là phương pháp an toàn hơn luôn hoạt động trong mọi tình huống, bắt chước giá trị trả về từ một hàm mà bạn có thể tìm trong các ngôn ngữ khác, như được Vicky Ronnenchỉ ra chính xác.


2

Các tùy chọn đã được liệt kê tất cả, tôi nghĩ. Chọn một thứ có thể đi vào một vấn đề về phong cách tốt nhất cho ứng dụng cụ thể của bạn và trong đó, tôi muốn cung cấp một phong cách cụ thể mà tôi thấy hữu ích. Trong bash, các biến và hàm không nằm trong cùng một không gian tên. Vì vậy, coi biến cùng tên là giá trị của hàm là một quy ước mà tôi thấy giảm thiểu xung đột tên và tăng cường khả năng đọc, nếu tôi áp dụng nó một cách chặt chẽ. Một ví dụ từ cuộc sống thực:

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

Và, một ví dụ về việc sử dụng các chức năng như vậy:

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

Như bạn có thể thấy, trạng thái trả lại là có để bạn sử dụng khi bạn cần hoặc bỏ qua nếu bạn không. Biến "trả lại" cũng có thể được sử dụng hoặc bỏ qua, nhưng tất nhiên chỉ sau hàm được gọi.

Tất nhiên, đây chỉ là một quy ước. Bạn có thể không đặt giá trị được liên kết trước khi quay lại (do đó, quy ước của tôi là luôn vô hiệu hóa nó khi bắt đầu hàm) hoặc chà đạp giá trị của nó bằng cách gọi lại hàm (có thể gián tiếp). Tuy nhiên, đó là một quy ước tôi thấy rất hữu ích nếu tôi thấy mình sử dụng nhiều chức năng bash.

Trái ngược với tình cảm rằng đây là một dấu hiệu, ví dụ như "chuyển sang perl", triết lý của tôi là các quy ước luôn quan trọng để quản lý sự phức tạp của bất kỳ ngôn ngữ nào.


2

Bạn có thể echomột chuỗi, nhưng bắt nó bằng cách chuyển ( |) hàm sang một thứ khác.

Bạn có thể làm điều đó với expr, mặc dù ShellCheck báo cáo việc sử dụng này là không dùng nữa.


Rắc rối là thứ ở bên phải của đường ống là một vỏ con. Vì vậy, myfunc | read OUTPUT ; echo $OUTPUTnăng suất không có gì. myfunc | ( read OUTPUT; echo $OUTPUT )không nhận được giá trị mong đợi và làm rõ những gì đang xảy ra ở phía bên tay phải. Nhưng tất nhiên OUTPUT không có sẵn ở nơi bạn cần ...
Ed Randall

2

Vấn đề chính của họ về bất kỳ lược đồ 'biến đầu ra có tên' nào mà người gọi có thể chuyển qua tên biến (dù sử dụng evalhay declare -n) là bí danh vô tình, tức là xung đột tên: Từ quan điểm đóng gói, thật tệ khi không thể thêm hoặc đổi tên một biến cục bộ trong một hàm mà không kiểm tra TẤT CẢ người gọi của hàm trước để đảm bảo rằng họ không muốn chuyển cùng tên đó với tham số đầu ra. (Hoặc theo một hướng khác, tôi không muốn phải đọc nguồn của hàm tôi đang gọi chỉ để đảm bảo tham số đầu ra tôi định sử dụng không phải là cục bộ trong hàm đó.)

Cách duy nhất là sử dụng một biến đầu ra chuyên dụng duy nhất như REPLY(như được đề xuất bởi Evi1M4chine ) hoặc một quy ước giống như quy tắc được đề xuất bởi Ron Burk .

Tuy nhiên, có thể có các hàm sử dụng một biến đầu ra cố định trong nội bộ , sau đó thêm một ít đường lên trên cùng để che giấu sự thật này khỏi người gọi , như tôi đã thực hiện với callhàm trong ví dụ sau. Hãy coi đây là một bằng chứng về khái niệm, nhưng những điểm chính là

  • Hàm luôn gán giá trị trả về cho REPLYvà cũng có thể trả về mã thoát như bình thường
  • Từ quan điểm của người gọi, giá trị trả về có thể được gán cho bất kỳ biến nào (cục bộ hoặc toàn cầu) bao gồm REPLY(xem wrapperví dụ). Mã thoát của hàm được truyền qua, do đó, sử dụng chúng trong ví dụ ifhoặc một whilecấu trúc tương tự hoạt động như mong đợi.
  • Cú pháp gọi hàm vẫn là một câu lệnh đơn giản.

Lý do điều này hoạt động là vì callbản thân hàm không có địa phương và không sử dụng biến nào khác ngoài việc REPLYtránh bất kỳ tiềm năng nào cho xung đột tên. Tại điểm đặt tên biến đầu ra do người gọi xác định, chúng tôi thực sự ở trong phạm vi của người gọi (về mặt kỹ thuật trong phạm vi giống hệt của callhàm), thay vì trong phạm vi của hàm được gọi.

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

Đầu ra:

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

1

mẫu bash để trả về cả hai đối tượng giá trị vô hướngmảng :

Định nghĩa

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
}

cầu khẩn

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

Trong các chương trình của tôi, theo quy ước, đây là những gì mà biến trước $REPLYđó readdùng để sử dụng cho mục đích chính xác đó.

function getSomeString {
  REPLY="tadaa"
}

getSomeString
echo $REPLY

Đây echoes

tadaa

Nhưng để tránh xung đột, bất kỳ biến toàn cầu nào khác sẽ làm.

declare result

function getSomeString {
  result="tadaa"
}

getSomeString
echo $result

Nếu điều đó là không đủ, tôi khuyên bạn nên sử dụng giải pháp 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=
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.