จะแก้ไขตัวแปรส่วนกลางภายในฟังก์ชันใน bash ได้อย่างไร?


109

ฉันกำลังทำงานกับสิ่งนี้:

GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu)

ฉันมีสคริปต์ดังต่อไปนี้:

#!/bin/bash

e=2

function test1() {
  e=4
  echo "hello"
}

test1 
echo "$e"

ซึ่งผลตอบแทน:

hello
4

แต่ถ้าฉันกำหนดผลลัพธ์ของฟังก์ชันให้กับตัวแปรตัวแปร global eจะไม่ถูกแก้ไข:

#!/bin/bash

e=2

function test1() {
  e=4
  echo "hello"
}

ret=$(test1)

echo "$ret"
echo "$e"

ผลตอบแทน:

hello
2

ฉันเคยได้ยินเกี่ยวกับการใช้ evalในกรณีนี้ดังนั้นฉันจึงทำสิ่งนี้ในtest1:

eval 'e=4'

แต่ผลเดียวกัน.

ช่วยอธิบายหน่อยได้ไหมว่าทำไมจึงไม่แก้ไข ฉันจะบันทึกเสียงสะท้อนของtest1ฟังก์ชันในretและแก้ไขตัวแปรส่วนกลางด้วยได้อย่างไร


ต้องกลับสวัสดีไหม คุณสามารถ echo $ e เพื่อให้มันกลับมา หรือสะท้อนทุกสิ่งที่คุณต้องการแล้วแยกวิเคราะห์ผลลัพธ์?

คำตอบ:


101

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

myfunc() {
    echo "Hello"
}

var="$(myfunc)"

echo "$var"

ให้:

Hello

สำหรับค่าตัวเลขตั้งแต่ 0-255 คุณสามารถใช้returnเพื่อส่งผ่านหมายเลขเป็นสถานะออก:

mysecondfunc() {
    echo "Hello"
    return 4
}

var="$(mysecondfunc)"
num_var=$?

echo "$var - num is $num_var"

ให้:

Hello - num is 4

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

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

@JohnDoe: คุณไม่สามารถส่งคืน "สตริงอาร์เรย์" จากฟังก์ชันได้ สิ่งที่คุณทำได้คือพิมพ์สตริง อย่างไรก็ตามคุณสามารถทำสิ่งนี้ได้:setarray() { declare -ag "$1=(a b c)"; }
rici

36

สิ่งนี้ต้องการ bash 4.1 หากคุณใช้{fd}หรือlocal -n.

ส่วนที่เหลือควรทำงานใน bash 3.x ฉันหวังว่า ฉันไม่แน่ใจอย่างสมบูรณ์เนื่องจากprintf %q- นี่อาจเป็นคุณสมบัติ bash 4

สรุป

ตัวอย่างของคุณสามารถแก้ไขได้ดังต่อไปนี้เพื่อเก็บเอฟเฟกต์ที่ต้องการ:

# Add following 4 lines:
_passback() { while [ 1 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; return $1; }
passback() { _passback "$@" "$?"; }
_capture() { { out="$("${@:2}" 3<&-; "$2_" >&3)"; ret=$?; printf "%q=%q;" "$1" "$out"; } 3>&1; echo "(exit $ret)"; }
capture() { eval "$(_capture "$@")"; }

e=2

# Add following line, called "Annotation"
function test1_() { passback e; }
function test1() {
  e=4
  echo "hello"
}

# Change following line to:
capture ret test1 

echo "$ret"
echo "$e"

พิมพ์ตามต้องการ:

hello
4

โปรดทราบว่าโซลูชันนี้:

  • ใช้ได้ผลe=1000เช่นกัน
  • รักษา$?ถ้าคุณต้องการ$?

ผลข้างเคียงที่ไม่ดีเพียงประการเดียวคือ:

  • bashมันต้องที่ทันสมัย
  • ส้อมค่อนข้างบ่อย
  • ต้องการคำอธิบายประกอบ (ตั้งชื่อตามฟังก์ชันของคุณพร้อมกับเพิ่ม_)
  • มันเสียสละ file descriptor 3.
    • คุณสามารถเปลี่ยนเป็น FD อื่นได้หากต้องการ
      • ใน_captureเพียงแทนที่ occurances ทั้งหมด3ที่มีจำนวนอื่น (สูงกว่า)

ต่อไปนี้ (ซึ่งค่อนข้างยาวขออภัยด้วย) หวังว่าจะอธิบายถึงวิธีการปรับแต่งสูตรนี้กับสคริปต์อื่น ๆ ด้วย

ปัญหา

d() { let x++; date +%Y%m%d-%H%M%S; }

x=0
d1=$(d)
d2=$(d)
d3=$(d)
d4=$(d)
echo $x $d1 $d2 $d3 $d4

เอาต์พุต

0 20171129-123521 20171129-123521 20171129-123521 20171129-123521

ในขณะที่ผลลัพธ์ที่ต้องการคือ

4 20171129-123521 20171129-123521 20171129-123521 20171129-123521

สาเหตุของปัญหา

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

หากคุณทำการจับเอาท์พุทโดยปกติจะทำงานใน subshell ดังนั้นการส่งผ่านตัวแปรกลับจึงทำได้ยาก

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

มีหลายวิธีในการแก้ปัญหาให้ดีที่สุดขึ้นอยู่กับความต้องการของคุณ

นี่คือคำแนะนำทีละขั้นตอนเกี่ยวกับวิธีการทำ

ส่งกลับตัวแปรไปยังพาเรนต์เชลล์

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

_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; }

d() { let x++; d=$(date +%Y%m%d-%H%M%S); _passback x d; }

x=0
eval `d`
d1=$d
eval `d`
d2=$d
eval `d`
d3=$d
eval `d`
d4=$d
echo $x $d1 $d2 $d3 $d4

พิมพ์

4 20171129-124945 20171129-124945 20171129-124945 20171129-124945

โปรดทราบว่าสิ่งนี้ใช้ได้กับสิ่งที่เป็นอันตรายเช่นกัน:

danger() { danger="$*"; passback danger; }
eval `danger '; /bin/echo *'`
echo "$danger"

พิมพ์

; /bin/echo *

นี่คือสาเหตุprintf '%q'ที่คุณสามารถนำมาใช้ซ้ำในบริบทเชลล์ได้อย่างปลอดภัย

แต่นี่คือความเจ็บปวดใน ..

สิ่งนี้ไม่เพียง แต่ดูน่าเกลียดเท่านั้น แต่ยังมีการพิมพ์มากอีกด้วยดังนั้นจึงมีข้อผิดพลาดได้ง่าย ความผิดพลาดเพียงครั้งเดียวคุณก็ถึงวาระใช่ไหม?

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

Augment วิธีที่เชลล์ประมวลผลสิ่งต่างๆ

ลองย้อนกลับไปนึกถึง API บางตัวที่ช่วยให้เราแสดงออกได้อย่างง่ายดายว่าเราต้องการทำอะไร

เราต้องการทำอะไรกับd()ฟังก์ชันนี้?

เราต้องการจับเอาท์พุทเป็นตัวแปร ตกลงแล้วมาใช้ API สำหรับสิ่งนี้:

# This needs a modern bash 4.3 (see "help declare" if "-n" is present,
# we get rid of it below anyway).
: capture VARIABLE command args..
capture()
{
local -n output="$1"
shift
output="$("$@")"
}

ตอนนี้แทนที่จะเขียน

d1=$(d)

เราเขียนได้

capture d1 d

ดูเหมือนว่าเราจะไม่ได้เปลี่ยนแปลงอะไรมากนักเนื่องจากตัวแปรจะไม่ถูกส่งกลับจากdไปยังพาเรนต์เชลล์อีกครั้งและเราต้องพิมพ์เพิ่มอีกเล็กน้อย

อย่างไรก็ตามตอนนี้เราสามารถทุ่มเต็มพลังของกระสุนได้เพราะมันถูกห่อหุ้มด้วยฟังก์ชันอย่างดี

ลองนึกถึงอินเทอร์เฟซที่ใช้ซ้ำได้ง่าย

สิ่งที่สองคือเราต้องการที่จะแห้ง (อย่าทำซ้ำตัวเอง) ดังนั้นเราจึงไม่ต้องการพิมพ์สิ่งที่ชอบ

x=0
capture1 x d1 d
capture1 x d2 d
capture1 x d3 d
capture1 x d4 d
echo $x $d1 $d2 $d3 $d4

xที่นี่ไม่ได้เป็นเพียงซ้ำซ้อนมันผิดพลาดง่ายเสมอ repeate ในบริบทที่ถูกต้อง จะเกิดอะไรขึ้นถ้าคุณใช้มัน 1,000 ครั้งในสคริปต์แล้วเพิ่มตัวแปร? แน่นอนคุณไม่ต้องการเปลี่ยนแปลงสถานที่ทั้งหมด 1,000 แห่งที่มีการเรียกร้องให้dเกี่ยวข้อง

ดังนั้นxเราจึงสามารถเขียน:

_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; }

d() { let x++; output=$(date +%Y%m%d-%H%M%S); _passback output x; }

xcapture() { local -n output="$1"; eval "$("${@:2}")"; }

x=0
xcapture d1 d
xcapture d2 d
xcapture d3 d
xcapture d4 d
echo $x $d1 $d2 $d3 $d4

เอาต์พุต

4 20171129-132414 20171129-132414 20171129-132414 20171129-132414

แค่นี้ก็ดูดีมากแล้ว (แต่ยังมีสิ่งlocal -nที่ใช้ไม่ได้ในbash3.x ทั่วไป)

หลีกเลี่ยงการเปลี่ยนแปลง d()

วิธีสุดท้ายมีข้อบกพร่องใหญ่บางประการ:

  • d() ต้องมีการเปลี่ยนแปลง
  • จำเป็นต้องใช้รายละเอียดภายในบางอย่างxcaptureเพื่อส่งผ่านเอาต์พุต
    • โปรดสังเกตว่าเงานี้ (เผาไหม้) ตัวแปรหนึ่งชื่อoutputดังนั้นเราจึงไม่สามารถส่งคืนนี้ได้
  • จำเป็นต้องร่วมมือกับ _passback

เราสามารถกำจัดสิ่งนี้ได้หรือไม่?

แน่นอนเราทำได้! เราอยู่ในกะลาดังนั้นจึงมีทุกสิ่งที่เราต้องการเพื่อทำให้เสร็จ

หากคุณดูใกล้ ๆ กับการโทรหาevalคุณจะเห็นว่าเราสามารถควบคุมตำแหน่งนี้ได้ 100% "ข้างใน" evalเราอยู่ในส่วนย่อยดังนั้นเราสามารถทำทุกอย่างที่ต้องการได้โดยไม่ต้องกลัวว่าจะทำอะไรไม่ดีกับเปลือกของผู้ปกครอง

ใช่ดีดังนั้นเรามาเพิ่มกระดาษห่ออื่นตอนนี้อยู่ในeval:

_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; }
# !DO NOT USE!
_xcapture() { "${@:2}" > >(printf "%q=%q;" "$1" "$(cat)"); _passback x; }  # !DO NOT USE!
# !DO NOT USE!
xcapture() { eval "$(_xcapture "$@")"; }

d() { let x++; date +%Y%m%d-%H%M%S; }

x=0
xcapture d1 d
xcapture d2 d
xcapture d3 d
xcapture d4 d
echo $x $d1 $d2 $d3 $d4

พิมพ์

4 20171129-132414 20171129-132414 20171129-132414 20171129-132414                                                    

อย่างไรก็ตามสิ่งนี้มีข้อเสียเปรียบที่สำคัญอีกครั้ง:

  • !DO NOT USE!เครื่องหมายจะมีเพราะมีสภาพการแข่งขันที่ดีมากในเรื่องนี้ที่คุณไม่สามารถมองเห็นได้อย่างง่ายดาย:
    • >(printf ..)เป็นงานพื้นหลัง ดังนั้นมันอาจยังคงดำเนินการในขณะที่_passback xกำลังทำงานอยู่
    • คุณสามารถดูนี้ด้วยตัวคุณเองถ้าคุณเพิ่มsleep 1;ก่อนหรือ printf จากนั้นเอาต์พุตหรือก่อนตามลำดับ_passback_xcapture a d; echoxa
  • _passback xไม่ควรจะเป็นส่วนหนึ่งของ_xcaptureเพราะนี้ทำให้ยากที่จะนำมาใช้สูตรว่า
  • นอกจากนี้เรายังมีทางแยกที่ไม่ได้ใช้งานอยู่ที่นี่ (the $(cat)) แต่เนื่องจากวิธีนี้ทำให้!DO NOT USE!ฉันใช้เส้นทางที่สั้นที่สุด

อย่างไรก็ตามสิ่งนี้แสดงให้เห็นว่าเราสามารถทำได้โดยไม่ต้องดัดแปลงd()(และไม่มีlocal -n)!

โปรดทราบว่าเราไม่จำเป็นต้องใช้_xcaptureเลยเนื่องจากเราสามารถเขียนทุกอย่างได้ถูกต้องในไฟล์eval.

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

แก้ไขการแข่งขัน

ตอนนี้เรามาแก้ไขสภาพการแข่งขัน

เคล็ดลับที่อาจจะรอจนกว่าจะprintfมีการปิดมัน STDOUT xและการส่งออกแล้ว

มีหลายวิธีในการเก็บถาวรสิ่งนี้:

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

การทำตามเส้นทางสุดท้ายอาจมีลักษณะดังนี้ (โปรดทราบว่าprintfสุดท้ายแล้วเพราะทำงานได้ดีกว่าที่นี่):

_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; }

_xcapture() { { printf "%q=%q;" "$1" "$("${@:2}" 3<&-; _passback x >&3)"; } 3>&1; }

xcapture() { eval "$(_xcapture "$@")"; }

d() { let x++; date +%Y%m%d-%H%M%S; }

x=0
xcapture d1 d
xcapture d2 d
xcapture d3 d
xcapture d4 d
echo $x $d1 $d2 $d3 $d4

เอาต์พุต

4 20171129-144845 20171129-144845 20171129-144845 20171129-144845

เหตุใดจึงถูกต้อง

  • _passback x พูดคุยโดยตรงกับ STDOUT
  • แต่เป็น STDOUT จะต้องมีการบันทึกในคำสั่งภายในครั้งแรกที่เรา "บันทึก" ลงใน fd3 (คุณสามารถใช้คนอื่น ๆ แน่นอน) ด้วย '3> & 1' >&3และจากนั้นนำมาใช้กับ
  • $("${@:2}" 3<&-; _passback x >&3)เสร็จสิ้นหลังจากที่_passbackเมื่อ subshell ปิด STDOUT
  • ดังนั้นสิ่งที่printfไม่สามารถเกิดขึ้นก่อน_passbackโดยไม่คำนึงว่า_passbackจะใช้เวลานานแค่ไหน
  • โปรดทราบว่าไฟล์ printfคำสั่งไม่ได้ดำเนินการก่อนที่จะประกอบบรรทัดคำสั่งทั้งหมดดังนั้นเราจึงไม่สามารถเห็นสิ่งประดิษฐ์จากprintfวิธีprintfการใช้งาน

ดังนั้นก่อน_passbackดำเนินการจากนั้นprintf.

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

โปรดสังเกตไฟล์ 3<&-ที่ปกป้อง FD3 ที่จะส่งผ่านไปยังฟังก์ชัน

ทำให้ทั่วไปมากขึ้น

_capture ประกอบด้วยชิ้นส่วนซึ่งเป็นของ d()เสียจากมุมมองของการนำกลับมาใช้ใหม่ วิธีแก้ปัญหานี้

ทำวิธีที่สิ้นหวังด้วยการแนะนำอีกสิ่งหนึ่งฟังก์ชั่นเพิ่มเติมซึ่งจะต้องส่งคืนสิ่งที่ถูกต้องซึ่งตั้งชื่อตามฟังก์ชันเดิมด้วย _แนบ

ฟังก์ชันนี้เรียกตามฟังก์ชันจริงและสามารถขยายสิ่งต่างๆ ด้วยวิธีนี้สามารถอ่านเป็นคำอธิบายประกอบได้ดังนั้นจึงน่าอ่านมาก:

_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; }
_capture() { { printf "%q=%q;" "$1" "$("${@:2}" 3<&-; "$2_" >&3)"; } 3>&1; }
capture() { eval "$(_capture "$@")"; }

d_() { _passback x; }
d() { let x++; date +%Y%m%d-%H%M%S; }

x=0
capture d1 d
capture d2 d
capture d3 d
capture d4 d
echo $x $d1 $d2 $d3 $d4

ยังคงพิมพ์

4 20171129-151954 20171129-151954 20171129-151954 20171129-151954

อนุญาตให้เข้าถึงรหัสส่งคืน

มีเพียงบิตที่หายไป:

v=$(fn)กำหนด$?สิ่งที่fnส่งคืน คุณก็คงต้องการสิ่งนี้เช่นกัน มันต้องการการปรับแต่งที่มากขึ้นแม้ว่า:

# This is all the interface you need.
# Remember, that this burns FD=3!
_passback() { while [ 1 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; return $1; }
passback() { _passback "$@" "$?"; }
_capture() { { out="$("${@:2}" 3<&-; "$2_" >&3)"; ret=$?; printf "%q=%q;" "$1" "$out"; } 3>&1; echo "(exit $ret)"; }
capture() { eval "$(_capture "$@")"; }

# Here is your function, annotated with which sideffects it has.
fails_() { passback x y; }
fails() { x=$1; y=69; echo FAIL; return 23; }

# And now the code which uses it all
x=0
y=0
capture wtf fails 42
echo $? $x $y $wtf

พิมพ์

23 42 69 FAIL

ยังมีช่องว่างให้ปรับปรุงอีกมาก

  • _passback() สามารถกำจัดได้ด้วย passback() { set -- "$@" "$?"; while [ 1 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; return $1; }
  • _capture() สามารถกำจัดได้ด้วย capture() { eval "$({ out="$("${@:2}" 3<&-; "$2_" >&3)"; ret=$?; printf "%q=%q;" "$1" "$out"; } 3>&1; echo "(exit $ret)")"; }

  • โซลูชันนี้ก่อให้เกิดมลพิษต่อ file descriptor (ที่นี่ 3) โดยใช้ภายใน คุณต้องจำไว้ว่าหากคุณผ่าน FD
    โปรดทราบว่าbash4.1 ขึ้น{fd}ไปต้องใช้ FD ที่ไม่ได้ใช้
    (บางทีฉันอาจจะเพิ่มโซลูชันที่นี่เมื่อฉันมารอบ ๆ )
    โปรดทราบว่านี่คือเหตุผลที่ฉันใช้เพื่อวางไว้ในฟังก์ชั่นที่แยกจากกันเช่น_captureเนื่องจากการรวมสิ่งนี้ทั้งหมดไว้ในบรรทัดเดียวเป็นไปได้ แต่จะทำให้อ่านและทำความเข้าใจได้ยากขึ้นเรื่อย ๆ

  • บางทีคุณอาจต้องการจับภาพ STDERR ของฟังก์ชันที่เรียกว่าด้วย หรือคุณต้องการส่งผ่านตัวอธิบายไฟล์มากกว่าหนึ่งตัวจากและไปยังตัวแปร
    ฉันยังไม่มีวิธีแก้ปัญหาอย่างไรก็ตามนี่เป็นวิธีจับ FD มากกว่าหนึ่งดังนั้นเราอาจส่งผ่านตัวแปรด้วยวิธีนี้ได้เช่นกัน

อย่าลืม:

สิ่งนี้ต้องเรียกใช้ฟังก์ชันเชลล์ไม่ใช่คำสั่งภายนอก

ไม่มีวิธีง่ายๆในการส่งผ่านตัวแปรสภาพแวดล้อมออกจากคำสั่งภายนอก (ด้วยLD_PRELOAD=น่าจะเป็นไปได้!) แต่นี่เป็นสิ่งที่แตกต่างไปจากเดิมอย่างสิ้นเชิง

คำสุดท้าย

นี่ไม่ใช่ทางออกเดียวที่เป็นไปได้ เป็นตัวอย่างหนึ่งในการแก้ปัญหา

เช่นเคยคุณมีหลายวิธีในการแสดงสิ่งต่างๆในเปลือกหอย ดังนั้นอย่าลังเลที่จะปรับปรุงและค้นหาสิ่งที่ดีกว่า

วิธีแก้ปัญหาที่นำเสนอนี้ค่อนข้างห่างไกลจากความสมบูรณ์แบบ:

  • มันเกือบจะไม่ใช่ testet เลยดังนั้นโปรดยกโทษให้กับการพิมพ์ผิด
  • มีพื้นที่สำหรับการปรับปรุงอีกมากดูด้านบน
  • มันใช้คุณสมบัติหลายอย่างตั้งแต่สมัยใหม่bashดังนั้นจึงยากที่จะโอนไปยังเปลือกหอยอื่น ๆ
  • และอาจมีนิสัยแปลก ๆ ที่ฉันไม่ได้คิด

อย่างไรก็ตามฉันคิดว่ามันค่อนข้างใช้งานง่าย:

  • เพิ่ม "ไลบรารี" เพียง 4 บรรทัด
  • เพิ่ม "คำอธิบายประกอบ" เพียง 1 บรรทัดสำหรับฟังก์ชันเชลล์ของคุณ
  • เสียสละตัวอธิบายไฟล์เพียงไฟล์เดียวชั่วคราว
  • และแต่ละขั้นตอนควรเข้าใจได้ง่ายแม้หลายปีต่อมา

3
คุณสุดยอดมาก
Eliran Malka

14

บางทีคุณอาจใช้ไฟล์เขียนลงในไฟล์ภายในฟังก์ชันอ่านจากไฟล์หลังจากนั้น ฉันเปลี่ยนeเป็นอาร์เรย์แล้ว ในตัวอย่างช่องว่างนี้ใช้เป็นตัวคั่นเมื่ออ่านอาร์เรย์ย้อนกลับ

#!/bin/bash

declare -a e
e[0]="first"
e[1]="secondddd"

function test1 () {
 e[2]="third"
 e[1]="second"
 echo "${e[@]}" > /tmp/tempout
 echo hi
}

ret=$(test1)

echo "$ret"

read -r -a e < /tmp/tempout
echo "${e[@]}"
echo "${e[0]}"
echo "${e[1]}"
echo "${e[2]}"

เอาท์พุต:

hi
first second third
first
second
third

14

คุณกำลังทำอะไรคุณกำลังดำเนินการ test1

$(test1)

ในเชลล์ย่อย (เชลล์ย่อย) และเชลล์ย่อยไม่สามารถแก้ไขอะไรในพาเรนต์ได้

คุณสามารถค้นหาได้ในคู่มือ bash

โปรดตรวจสอบ: สิ่งต่าง ๆ ให้ผลลัพธ์เป็น subshell ที่นี่


7

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

#! /bin/bash

remove_later=""
new_tmp_file() {
    file=$(mktemp)
    remove_later="$remove_later $file"
    eval $1=$file
}
remove_tmp_files() {
    rm $remove_later
}
trap remove_tmp_files EXIT

new_tmp_file tmpfile1
new_tmp_file tmpfile2

ดังนั้นในกรณีของคุณนั่นคือ:

#!/bin/bash

e=2

function test1() {
  e=4
  eval $1="hello"
}

test1 ret

echo "$ret"
echo "$e"

ใช้งานได้และไม่มีข้อ จำกัด เกี่ยวกับ "ค่าที่ส่งคืน"


1

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

อ้างอิง :

การทดแทนคำสั่งคำสั่งที่จัดกลุ่มด้วยวงเล็บและคำสั่งแบบอะซิงโครนัสถูกเรียกใช้ในสภาวะแวดล้อม subshell ที่ซ้ำกับสภาพแวดล้อมเชลล์


@JohnDoe ฉันไม่แน่ใจว่าเป็นไปได้ คุณอาจต้องทบทวนการออกแบบสคริปต์ของคุณใหม่
โปรแกรมเมอร์บางคนเข้า

โอ้ แต่ฉันต้องการรวบรวมอาร์เรย์ส่วนกลางภายในฟังก์ชันถ้าไม่ฉันต้องทำซ้ำรหัสจำนวนมาก (ทำซ้ำรหัสของฟังก์ชัน -30 บรรทัด - 15 ครั้ง - หนึ่งครั้งต่อการโทร -) ไม่มีวิธีอื่นไม่ใช่หรือ
harrison4

1

วิธีแก้ปัญหานี้โดยไม่ต้องแนะนำฟังก์ชันที่ซับซ้อนและปรับเปลี่ยนฟังก์ชันเดิมอย่างหนักคือการเก็บค่าไว้ในไฟล์ชั่วคราวและอ่าน / เขียนเมื่อจำเป็น

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

ตัวอย่างเช่นคุณสามารถมี:

# Usage read_value path_to_tmp_file
function read_value {
  cat "${1}"
}

# Usage: set_value path_to_tmp_file the_value
function set_value {
  echo "${2}" > "${1}"
}
#----

# Original code:

function test1() {
  e=4
  set_value "${tmp_file}" "${e}"
  echo "hello"
}


# Create the temp file
# Note that tmp_file is available in test1 as well
tmp_file=$(mktemp)

# Your logic
e=2
# Store the value
set_value "${tmp_file}" "${e}"

# Run test1
test1

# Read the value modified by test1
e=$(read_value "${tmp_file}")
echo "$e"

ข้อเสียเปรียบคือคุณอาจต้องใช้ไฟล์ temp หลายไฟล์สำหรับตัวแปรต่างๆ และคุณอาจต้องออกsyncคำสั่งเพื่อคงเนื้อหาบนดิสก์ระหว่างการเขียนและการอ่าน


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