วิธีคืนค่าสตริงจากฟังก์ชัน Bash


461

ฉันต้องการคืนสตริงจากฟังก์ชัน Bash

ฉันจะเขียนตัวอย่างใน java เพื่อแสดงสิ่งที่ฉันต้องการจะทำ:

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

String variable = getSomeString();

ตัวอย่างด้านล่างใช้งานได้ดี แต่มีวิธีที่ดีกว่าในการทำเช่นนี้หรือไม่

function getSomeString {
   echo "tadaa"
}

VARIABLE=$(getSomeString)

4
นอกเหนือจากนั้นfunction funcName {คือไวยากรณ์ดั้งเดิมของ POSIX ล่วงหน้าที่สืบทอดมาจาก ksh ก่อนหน้า (ซึ่งมีความแตกต่างทางความหมายที่ bash ไม่ให้เกียรติ) funcName() {โดยไม่functionควรนำมาใช้แทน; ดูwiki.bash-hackers.org/scripting/obsolete
Charles Duffy

ลิงค์นั้นบอกว่าจะใช้ NAME () COMPOUND-CMD หรือฟังก์ชั่น NAME {CMDS; } ดังนั้นfunction myFunction { blah; }ก็ดี มันfunction myFunction() { blah }ไม่ดีเช่นการใช้วงเล็บกับฟังก์ชั่นคำหลัก
Will

คำตอบ:


290

ไม่มีวิธีที่ดีกว่าที่ฉันรู้ Bash รู้เฉพาะรหัสสถานะ (จำนวนเต็ม) และสตริงที่เขียนไปยัง stdout


15
+1 @ tomas-f: คุณต้องระวังสิ่งที่คุณมีในฟังก์ชั่นนี้ "getSomeString ()" เนื่องจากมีรหัสใด ๆ ซึ่งในที่สุดเสียงสะท้อนจะหมายความว่าคุณได้รับสตริงส่งคืนที่ไม่ถูกต้อง
มณี

11
นี่เป็นเพียงความผิดธรรมดา คุณสามารถส่งคืนข้อมูลที่กำหนดเองภายในตัวแปรส่งคืน ซึ่งเห็นได้ชัดว่าเป็นวิธีที่ดีกว่า
Evi1M4chine

36
@ Evi1M4chine อืม ... ไม่คุณไม่สามารถทำได้ คุณสามารถตั้งค่าตัวแปรส่วนกลางและเรียกมันว่า "คืน" ตามที่ฉันเห็นคุณทำในสคริปต์ของคุณ แต่จากนั้นก็คือโดยการประชุมไม่ผูกโปรแกรมกับการดำเนินการของรหัสของคุณ "เห็นได้ชัดว่าเป็นวิธีที่ดีกว่า"? อืมไม่. การทดแทนคำสั่งมีความชัดเจนและเป็นโมดูลมากขึ้น
สัญลักษณ์แทน

6
"การแทนที่คำสั่งนั้นชัดเจนกว่าและโมดูลาร์" นั้นจะเกี่ยวข้องถ้าคำถามเกี่ยวกับคำสั่ง; คำถามนี้เป็นวิธีคืนสตริงจากฟังก์ชัน bash! วิธีในการทำสิ่งที่ OP ได้ถามไว้มีตั้งแต่ Bash 4.3 (2014?) - ดูคำตอบของฉันด้านล่าง
zenaan

4
คำถามดั้งเดิมประกอบด้วยวิธีที่ง่ายที่สุดในการทำและใช้ได้ดีในกรณีส่วนใหญ่ ค่าที่ส่งคืนของ Bash ควรเรียกว่า "รหัสส่งคืน" เนื่องจากค่าเหล่านี้จะน้อยกว่าค่าส่งคืนมาตรฐานในการเขียนสคริปต์และอื่น ๆ เช่นรหัสออกจากคำสั่งเชลล์ตัวเลข (คุณสามารถทำสิ่งต่าง ๆ ได้เช่นกันsomefunction && echo 'success') หากคุณนึกถึงฟังก์ชั่นที่เหมือนคำสั่งอื่นมันก็สมเหตุสมผลดี คำสั่งจะไม่ "ส่งคืน" สิ่งใดเมื่อออกจากระบบนอกเหนือจากรหัสสถานะ แต่อาจส่งออกสิ่งต่าง ๆ ในระหว่างที่คุณสามารถจับ
Beejor

193

คุณสามารถให้ฟังก์ชันรับตัวแปรเป็นอาร์กิวเมนต์แรกและแก้ไขตัวแปรด้วยสตริงที่คุณต้องการส่งคืน

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

พิมพ์ "foo bar rab oof"

แก้ไข : เพิ่มข้อความในสถานที่ที่เหมาะสมเพื่ออนุญาตให้ช่องว่างในสตริงที่อยู่ความคิดเห็นของ @Luca Borrione

แก้ไข : เป็นการสาธิตให้ดูโปรแกรมต่อไปนี้ นี่เป็นวิธีการแก้ปัญหาทั่วไป: มันช่วยให้คุณสามารถรับสตริงเข้าตัวแปรท้องถิ่น

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

ภาพพิมพ์นี้:

+ 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

แก้ไข : แสดงให้เห็นว่าคุณค่าของตัวแปรดั้งเดิมมีอยู่ในฟังก์ชั่นตามที่ถูกวิพากษ์วิจารณ์อย่างไม่ถูกต้องโดย @Xichen Li ในความคิดเห็น

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

สิ่งนี้ให้ผลลัพธ์:

+ 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 ++
หยุนหวาง

4
มันคงจะดีถ้าได้รับคำตอบจากผู้เชี่ยวชาญเกี่ยวกับคำตอบนั้น ฉันไม่เคยเห็นที่ใช้ในสคริปต์อาจเป็นเหตุผลที่ดี ต่อไป: นั่นคือ +1 มันควรได้รับการโหวตสำหรับคำตอบที่ถูกต้อง
John

นี่ไม่ใช่fgmคำตอบเดียวที่เขียนในลักษณะที่เรียบง่ายใช่ไหม สิ่งนี้จะไม่ทำงานหากสตริงfooมีช่องว่างสีขาวในขณะที่ข้อความfgmจะ .. ในขณะที่เขากำลังแสดง
Luca Borrione

4
@ XichenLi: ขอบคุณที่ฝากความคิดเห็นไว้กับ downvote ของคุณ; โปรดดูการแก้ไขของฉัน \$$1คุณจะได้รับค่าเริ่มต้นของตัวแปรในการทำงานด้วย หากคุณกำลังมองหาสิ่งที่แตกต่างโปรดแจ้งให้เราทราบ
bstpierre

1
@timiscoding printf '%q' "$var"ที่สามารถแก้ไขได้ด้วย % q เป็นสตริงรูปแบบสำหรับการหลบหนีของเชลล์ จากนั้นก็ผ่านมันดิบ
bb010g

99

คำตอบทั้งหมดข้างต้นไม่สนใจสิ่งที่ระบุไว้ในหน้า man of bash

  • ตัวแปรทั้งหมดที่ประกาศภายในฟังก์ชั่นจะถูกแชร์กับสภาพแวดล้อมการโทร
  • ตัวแปรทั้งหมดที่ประกาศในเครื่องจะไม่แชร์

รหัสตัวอย่าง

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

และเอาท์พุท

$ 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

ภายใต้ pdksh และ ksh สคริปต์นี้ก็ทำเช่นเดียวกัน!


10
คำตอบนี้มีข้อดี ฉันเข้ามาที่นี่โดยคิดว่าฉันต้องการส่งคืนสตริงจากฟังก์ชัน คำตอบนี้ทำให้ฉันรู้ว่านั่นเป็นเพียง C # -habits พูดคุยของฉัน ฉันสงสัยว่าคนอื่นอาจมีประสบการณ์แบบเดียวกัน
LOAS

4
@ElmarZander คุณผิดนี่เป็นเรื่องที่เกี่ยวข้องทั้งหมด นี่เป็นวิธีที่ง่ายในการเข้าสู่ขอบเขตทั่วโลกเป็นค่าขอบเขตของฟังก์ชันและบางคนอาจพิจารณาว่าดีกว่า / ง่ายกว่าวิธี eval เพื่อกำหนดตัวแปรทั่วโลกตามที่กำหนดโดย bstpierre
KomodoDave

ท้องถิ่นไม่สามารถพกพาไปยังสคริปต์ที่ไม่ใช่การทุบตีซึ่งเป็นเหตุผลหนึ่งที่บางคนหลีกเลี่ยง
ดอนสดใส

คำถาม: แล้วตัวแปรในลูปล่ะ
anu

1
สำหรับ mac ($ bash - รุ่น GNU bash รุ่น 3.2.57 (1) - ปล่อย (x86_64-apple-darwin14) ลิขสิทธิ์ (C) มูลนิธิซอฟต์แวร์เสรี 2007, Inc) ถูกต้องแล้วว่าตัวแปรทั่วโลกตรงกันคือ เริ่มต้นได้ แต่เมื่อฉันลองผลข้างเคียงตัวแปรเดียวกันในฟังก์ชัน f2 อื่นผลข้างเคียงนั้นจะไม่คงอยู่ ดังนั้นดูเหมือนว่าจะไม่สอดคล้องกันมากและไม่ดีสำหรับการใช้งานของฉัน
AnneTheAgile

45

Bash ตั้งแต่เวอร์ชั่น 4.3, feb 2014 (?) ได้รับการสนับสนุนอย่างชัดเจนสำหรับตัวแปรอ้างอิงหรือการอ้างอิงชื่อ (namerefs) นอกเหนือจาก "eval" ด้วยประสิทธิภาพที่เป็นประโยชน์และผลกระทบทางอ้อมซึ่งอาจชัดเจนในสคริปต์ของคุณและยากขึ้น เพื่อ "ลืมที่จะ 'eval' และต้องแก้ไขข้อผิดพลาดนี้":

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

และนอกจากนี้ยังมี:

พารามิเตอร์

ตัวแปรสามารถกำหนดแอตทริบิวต์ nameref ได้โดยใช้ตัวเลือก -n ไปยังคำสั่ง declare หรือ local builtin (ดูคำอธิบายของการประกาศและ local ด้านล่าง) เพื่อสร้าง nameref หรืออ้างอิงกับตัวแปรอื่น สิ่งนี้ยอมให้ตัวแปรถูกควบคุมทางอ้อม เมื่อใดก็ตามที่มีการอ้างอิงหรือกำหนดตัวแปร nameref การดำเนินการจะดำเนินการกับตัวแปรที่ระบุโดยค่าของตัวแปร nameref โดยทั่วไปแล้ว nameref จะใช้ในฟังก์ชั่นเชลล์เพื่ออ้างถึงตัวแปรที่มีชื่อถูกส่งผ่านเป็นอาร์กิวเมนต์เพื่อฟังก์ชั่น ตัวอย่างเช่นหากชื่อตัวแปรถูกส่งผ่านไปยังฟังก์ชันเชลล์เป็นอาร์กิวเมนต์แรกให้เรียกใช้

      declare -n ref=$1

ภายในฟังก์ชั่นสร้างการอ้างอิงตัวแปร nameref ที่มีค่าเป็นชื่อตัวแปรที่ส่งผ่านเป็นอาร์กิวเมนต์แรก การอ้างอิงและการมอบหมายให้อ้างอิงจะถือว่าเป็นการอ้างอิงและการมอบหมายให้กับตัวแปรที่มีชื่อผ่านเป็น⋅ $ 1 หากตัวแปรควบคุมใน for for มีแอตทริบิวต์ nameref รายการคำสามารถเป็นรายการของตัวแปร shell และการอ้างอิงชื่อจะถูกสร้างขึ้นสำหรับแต่ละคำในรายการในทางกลับกันเมื่อมีการดำเนินการวนรอบ ไม่สามารถกำหนดตัวแปรอาร์เรย์ได้ อย่างไรก็ตามตัวแปร nameref สามารถอ้างอิงตัวแปรอาร์เรย์และตัวแปรอาร์เรย์ที่ห้อยลงมาได้ Namerefs สามารถตั้งค่าได้โดยใช้ตัวเลือก -n เพื่อ unset builtin มิฉะนั้นหากดำเนินการยกเลิกการตั้งค่าด้วยชื่อของตัวแปร nameref เป็นอาร์กิวเมนต์

ตัวอย่างเช่น ( แก้ไข 2 : (ขอขอบคุณคุณรอน) กำหนดชื่อ (นำหน้า) ชื่อตัวแปรฟังก์ชันภายในเพื่อลดการชนตัวแปรภายนอกซึ่งในที่สุดควรตอบได้อย่างถูกต้องปัญหาที่เกิดขึ้นในคอมเม้นท์โดย 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
}

และทดสอบตัวอย่างนี้:

$ return_a_string result; echo $result
The date is 20160817

โปรดทราบว่าการทุบตี "ประกาศ" builtin เมื่อใช้ในฟังก์ชั่นทำให้ตัวแปรประกาศ "ท้องถิ่น" โดยค่าเริ่มต้นและ "-n" ยังสามารถใช้กับ "ท้องถิ่น"

ฉันต้องการแยกความแตกต่าง "ประกาศสำคัญ" จากตัวแปร "น่าเบื่อท้องถิ่น" ดังนั้นการใช้ "ประกาศ" และ "ท้องถิ่น" ด้วยวิธีนี้ทำหน้าที่เป็นเอกสารประกอบ

แก้ไข 1 - (ตอบสนองต่อความคิดเห็นด้านล่างโดย Karsten) - ฉันไม่สามารถเพิ่มความคิดเห็นด้านล่างอีกต่อไป แต่ความคิดเห็นของ Karsten ทำให้ฉันคิดดังนั้นฉันจึงทำการทดสอบต่อไปนี้ซึ่ง WORKS FINE, AFAICT - Karsten ถ้าคุณอ่านสิ่งนี้ ของขั้นตอนการทดสอบจากบรรทัดคำสั่งแสดงปัญหาที่คุณคิดว่ามีอยู่เพราะขั้นตอนต่อไปนี้ใช้งานได้ดี:

$ return_a_string ret; echo $ret
The date is 20170104

(ตอนนี้ฉันวิ่งแค่นี้หลังจากวางฟังก์ชันด้านบนเป็นคำทุบตี - อย่างที่คุณเห็นผลลัพธ์ก็ใช้ได้ดี)


4
มันเป็นความหวังของฉันที่สิ่งนี้แพร่กระจายไปสู่จุดสูงสุด eval ควรเป็นทางเลือกสุดท้าย สิ่งที่ควรค่าแก่การกล่าวถึงคือตัวแปร nameref มีให้บริการตั้งแต่ทุบตี 4.3 (ตามการเปลี่ยนแปลง ) (เผยแพร่ใน feb 2014 [?]) สิ่งนี้สำคัญหากการพกพาเป็นเรื่องที่น่ากังวล โปรดอ้างอิงคู่มือทุบตีเกี่ยวกับความจริงที่declareสร้างตัวแปรท้องถิ่นภายในฟังก์ชั่น (ไม่ได้รับข้อมูลนั้นhelp declare): "... เมื่อใช้ในฟังก์ชั่นการประกาศและเรียงพิมพ์ทำให้แต่ละชื่อท้องถิ่นเช่นเดียวกับคำสั่งท้องถิ่นยกเว้น - จัดหาตัวเลือก g ... "
init_js

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

1
@ Karsten เห็นด้วย ในทั้งสองกรณี (eval และ namerefs) คุณอาจต้องเลือกชื่ออื่น ข้อดีอย่างหนึ่งของแนวทาง nameref เหนือ eval ก็คือไม่จำเป็นต้องจัดการกับสตริงการหลบหนี แน่นอนคุณสามารถทำสิ่งที่ชอบได้K=$1; V=$2; eval "$A='$V'";ตลอดเวลา แต่มีข้อผิดพลาดหนึ่งข้อ (เช่นพารามิเตอร์ที่ว่างเปล่าหรือละเว้น) และอาจเป็นอันตรายได้มากกว่า @zenaan ปัญหาที่ยกขึ้นโดย @Karsten นำไปใช้หากคุณเลือก "message" เป็นชื่อตัวแปรส่งคืนแทนที่จะเป็น "ret"
init_js

3
ฟังก์ชั่นคงจะต้องได้รับการออกแบบมาตั้งแต่ต้นเพื่อยอมรับอาร์กิวเมนต์ nameref ดังนั้นผู้เขียนฟังก์ชั่นควรตระหนักถึงความเป็นไปได้ของการชนกันของชื่อและสามารถใช้หลักการทั่วไปเพื่อหลีกเลี่ยงปัญหานั้น เช่นภายในฟังก์ชัน X ให้ตั้งชื่อตัวแปรท้องถิ่นด้วยระเบียบ "X_LOCAL_name"
Ron Burk

34

เช่นเดียวกับbstpierreด้านบนฉันใช้และแนะนำให้ใช้ตัวแปรเอาท์พุทอย่างชัดเจน:

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
}

สังเกตการใช้คำพูดของ $ สิ่งนี้จะหลีกเลี่ยงการตีความเนื้อหา$resultเป็นอักขระพิเศษของเชลล์ ฉันได้พบว่านี่เป็นลำดับความสำคัญเร็วกว่าresult=$(some_func "arg1")สำนวนการจับเสียงสะท้อน ความแตกต่างของความเร็วนั้นดูน่าทึ่งยิ่งกว่าการใช้ bash บน MSYS ซึ่ง stdout ที่จับจากการเรียกใช้ฟังก์ชันนั้นเกือบจะเป็นหายนะ

คุณสามารถส่งตัวแปรท้องถิ่นได้เนื่องจากชาวบ้านถูกกำหนดขอบเขตแบบไดนามิกใน bash:

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

4
สิ่งนี้ช่วยฉันได้เพราะฉันต้องการใช้หลาย ๆ คำสั่ง echo สำหรับการดีบัก / การบันทึก สำนวนการจับเสียงสะท้อนนั้นล้มเหลวเพราะมันจับภาพทุกเสียง ขอบคุณ!
AnneTheAgile

นี่คือทางออกที่เหมาะสม (ดีที่สุด) ที่สอง! สะอาดรวดเร็วหรูหรามีเหตุผล
Evi1M4chine

+2 สำหรับการทำให้เป็นจริง ฉันกำลังจะพูด ผู้คนจำนวนมากจะเพิกเฉยต่อการรวมechoภายในของฟังก์ชันอย่างไรรวมกับการแทนที่คำสั่ง!
Anthony Rutledge

22

คุณสามารถจับเอาท์พุทฟังก์ชั่น:

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

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

ดูแปลก แต่ดีกว่าการใช้ตัวแปรทั่วโลก IMHO การผ่านพารามิเตอร์ทำงานได้ตามปกติเพียงแค่ใส่วงเล็บหรือ backticks


11
นอกเหนือจากหมายเหตุไวยากรณ์ทางเลือกแล้วนี่ไม่ใช่สิ่งเดียวกันที่ op ได้เขียนไว้ในคำถามของเขาเองหรือ
Luca Borrione

12

ทางออกที่ตรงไปตรงมาและแข็งแกร่งที่สุดคือการใช้การทดแทนคำสั่งตามที่คนอื่นเขียน:

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

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

ข้อเสียคือประสิทธิภาพเนื่องจากต้องใช้กระบวนการแยกต่างหาก

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

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

แน่นอนคุณอาจไม่ได้ประกาศตัวแปรภายในของฟังก์ชั่นเป็นท้องถิ่น แต่จริงๆแล้วคุณควรทำเช่นนั้นเสมอมิฉะนั้นคุณอาจเขียนทับตัวแปรที่ไม่เกี่ยวข้องจากขอบเขตพาเรนต์โดยบังเอิญถ้ามีชื่อเดียวกัน .

วิธีแก้ไขปัญหาหนึ่งที่เป็นไปได้คือการประกาศตัวแปรที่ส่งผ่านเป็นโกลบอลอย่างชัดเจน:

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

หากชื่อ "x" ถูกส่งผ่านเป็นอาร์กิวเมนต์แถวที่สองของร่างกายฟังก์ชันจะเขียนทับการประกาศท้องถิ่นก่อนหน้า แต่ชื่อตัวเองอาจยังรบกวนดังนั้นหากคุณตั้งใจจะใช้ค่าที่เก็บไว้ก่อนหน้านี้ในตัวแปรส่งผ่านก่อนที่จะเขียนค่าส่งคืนที่นั่นโปรดทราบว่าคุณต้องคัดลอกลงในตัวแปรท้องถิ่นอื่นที่จุดเริ่มต้น; มิฉะนั้นผลลัพธ์จะไม่สามารถคาดเดาได้! นอกจากนี้จะใช้งานได้เฉพาะในเวอร์ชันล่าสุดของ BASH คือ 4.2 โค้ดแบบพกพาอื่น ๆ อาจใช้โครงสร้างที่มีเงื่อนไขอย่างชัดเจนด้วยเอฟเฟกต์เดียวกัน

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

บางทีทางออกที่งดงามที่สุดก็คือการจองชื่อส่วนกลางหนึ่งชื่อสำหรับค่าส่งคืนฟังก์ชันและใช้อย่างสม่ำเสมอในทุกฟังก์ชั่นที่คุณเขียน


3
นี่ ^^^ การใช้นามแฝงโดยไม่ตั้งใจที่ทำให้การห่อหุ้มนั้นเป็นปัญหาใหญ่ทั้งกับevalและdeclare -nวิธีแก้ปัญหา วิธีแก้ไขปัญหาของการมีชื่อตัวแปรเฉพาะเดียวเช่นresultพารามิเตอร์เอาท์พุททั้งหมดดูเหมือนจะเป็นทางออกเดียวที่ไม่จำเป็นต้องใช้ฟังก์ชั่นในการรู้ว่าผู้โทรทุกคนต้องหลีกเลี่ยงความขัดแย้ง
Karsten

12

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

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

กำลังเรียกใช้งานสคริปต์โดยไม่มีพารามิเตอร์สร้าง ...

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

หวังว่าสิ่งนี้จะช่วยให้ผู้คน

แอนดี้


6
สิ่งนี้มีประโยชน์ แต่โดยรวมคุณควรหลีกเลี่ยงการเปลี่ยนเส้นทางไปยังคอนโซลอย่างชัดเจน ผลลัพธ์อาจถูกเปลี่ยนเส้นทางไปแล้วหรือสคริปต์อาจทำงานในบริบทที่ไม่มี tty อยู่ คุณสามารถแก้ไขได้โดยทำซ้ำ3>&1ที่ส่วนหัวของสคริปต์จากนั้นจัดการ&1 &3และตัวยึดตำแหน่งอื่น&4ภายในฟังก์ชัน น่าเกลียดทุกรอบแม้ว่า
jmb

8

คุณสามารถใช้ตัวแปรโกลบอล:

declare globalvar='some string'

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

string globalvar

echo "'${globalvar}'"

สิ่งนี้จะช่วยให้

'some other string'

6

เพื่อแสดงความคิดเห็นของฉันเกี่ยวกับคำตอบของ Andy ด้วยการจัดการไฟล์อธิบายเพิ่มเติมเพื่อหลีกเลี่ยงการใช้/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]"

ยังคงน่ารังเกียจแม้ว่า


3

วิธีที่คุณมีมันเป็นวิธีเดียวที่จะทำสิ่งนี้โดยไม่ทำลาย Bash ไม่มีแนวคิดของประเภทการส่งคืนเพียงแค่ออกจากรหัสและตัวอธิบายไฟล์ (stdin / out / err ฯลฯ )


3

ที่อยู่Vicky Ronnen 's หัวขึ้นพิจารณารหัสต่อไปนี้:

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



จะให้

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

บางทีสถานการณ์ปกติคือการใช้ไวยากรณ์ที่ใช้ในtest_inside_a_funcฟังก์ชั่นดังนั้นคุณสามารถใช้ทั้งสองวิธีในกรณีส่วนใหญ่แม้ว่าการจับเอาท์พุทเป็นวิธีที่ปลอดภัยกว่าจะทำงานในทุกสถานการณ์เสมอและเลียนแบบค่าที่ส่งคืนจากฟังก์ชันที่คุณสามารถทำได้ ค้นหาในภาษาอื่น ๆ ตามที่Vicky Ronnenระบุไว้อย่างถูกต้อง


2

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

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

และตัวอย่างของการใช้ฟังก์ชั่นดังกล่าว:

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

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

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

ตรงข้ามกับความรู้สึกที่ว่านี่เป็นสัญญาณหนึ่งที่ควรจะเป็นเช่น "ย้ายไปยัง Perl" ปรัชญาของฉันก็คือการประชุมมีความสำคัญเสมอสำหรับการจัดการความซับซ้อนของภาษาใด ๆ


2

คุณสามารถechoสตริง แต่จับได้โดยการ piping ( |) ฟังก์ชั่นอย่างอื่น

คุณสามารถทำได้exprแม้ว่าShellCheckจะรายงานการใช้งานนี้ว่าเลิกใช้แล้ว


ปัญหาคือสิ่งที่อยู่ทางด้านขวาของไปป์นั้นเป็น subshell ดังนั้นmyfunc | read OUTPUT ; echo $OUTPUTไม่มีอะไรให้ myfunc | ( read OUTPUT; echo $OUTPUT )จะได้รับค่าที่คาดหวังและชี้แจงสิ่งที่เกิดขึ้นทางด้านขวามือ แต่แน่นอน OUTPUT จะไม่สามารถใช้ได้แล้วตามที่คุณต้องการ ...
เอ็ด Randall

2

ปัญหาสำคัญของโครงการ 'output output ตัวแปร' ใด ๆ ที่ผู้โทรสามารถส่งผ่านในชื่อตัวแปร (ไม่ว่าจะใช้evalหรือdeclare -n) คือ aliasing โดยไม่ตั้งใจเช่น clashes ชื่อ: จากมุมมอง encapsulation มันน่ากลัวที่จะไม่สามารถเพิ่มหรือเปลี่ยนชื่อ ตัวแปรโลคัลในฟังก์ชันโดยไม่ตรวจสอบผู้เรียกทั้งหมดของฟังก์ชันก่อนเพื่อให้แน่ใจว่าพวกเขาไม่ต้องการผ่านชื่อเดียวกันกับพารามิเตอร์เอาต์พุต (หรือในอีกทางหนึ่งฉันไม่ต้องการอ่านแหล่งที่มาของฟังก์ชั่นที่ฉันเรียกใช้เพื่อให้แน่ใจว่าพารามิเตอร์เอาต์พุตที่ฉันต้องการใช้ไม่ใช่เฉพาะในฟังก์ชั่นนั้น)

วิธีเดียวที่จะใช้ตัวแปรเอาต์พุตเดี่ยวเช่นREPLY(ตามที่แนะนำโดยEvi1M4chine ) หรือแบบแผนที่Ron Burkแนะนำ

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

  • ฟังก์ชั่นจะกำหนดค่าส่งคืนให้เสมอREPLYและยังสามารถส่งคืนรหัสออกได้ตามปกติ
  • จากมุมมองของผู้โทรสามารถส่งคืนค่าไปยังตัวแปรใด ๆ (ท้องถิ่นหรือทั่วโลก) รวมถึงREPLY(ดูwrapperตัวอย่าง) รหัสทางออกของฟังก์ชั่นจะถูกส่งผ่านเพื่อใช้พวกเขาใน e กรัมifหรือwhileหรือโครงสร้างที่คล้ายกันผลงานตามที่คาดไว้
  • วากยสัมพันธ์การเรียกใช้ฟังก์ชั่นยังคงเป็นคำสั่งง่ายๆ

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

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

เอาท์พุท:

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

1

รูปแบบbashเพื่อส่งคืนทั้งเกลาและอาร์เรย์ค่าวัตถุ

คำนิยาม

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
}

การภาวนา

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

ในโปรแกรมของฉันตามแบบแผนนี่คือสิ่งที่$REPLYตัวแปรที่มีอยู่แล้วนั้นreadใช้สำหรับวัตถุประสงค์ที่แน่นอน

function getSomeString {
  REPLY="tadaa"
}

getSomeString
echo $REPLY

echoes นี้

tadaa

แต่เพื่อหลีกเลี่ยงความขัดแย้งตัวแปรระดับโลกอื่น ๆ จะทำ

declare result

function getSomeString {
  result="tadaa"
}

getSomeString
echo $result

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