สิ่งนี้ต้องการ bash 4.1 หากคุณใช้{fd}
หรือlocal -n
.
ส่วนที่เหลือควรทำงานใน bash 3.x ฉันหวังว่า ฉันไม่แน่ใจอย่างสมบูรณ์เนื่องจากprintf %q
- นี่อาจเป็นคุณสมบัติ bash 4
สรุป
ตัวอย่างของคุณสามารถแก้ไขได้ดังต่อไปนี้เพื่อเก็บเอฟเฟกต์ที่ต้องการ:
_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
function test1_() { passback e; }
function test1() {
e=4
echo "hello"
}
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 สำหรับสิ่งนี้:
: 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
ที่ใช้ไม่ได้ในbash
3.x ทั่วไป)
หลีกเลี่ยงการเปลี่ยนแปลง d()
วิธีสุดท้ายมีข้อบกพร่องใหญ่บางประการ:
d()
ต้องมีการเปลี่ยนแปลง
- จำเป็นต้องใช้รายละเอียดภายในบางอย่าง
xcapture
เพื่อส่งผ่านเอาต์พุต
- โปรดสังเกตว่าเงานี้ (เผาไหม้) ตัวแปรหนึ่งชื่อ
output
ดังนั้นเราจึงไม่สามารถส่งคืนนี้ได้
- จำเป็นต้องร่วมมือกับ
_passback
เราสามารถกำจัดสิ่งนี้ได้หรือไม่?
แน่นอนเราทำได้! เราอยู่ในกะลาดังนั้นจึงมีทุกสิ่งที่เราต้องการเพื่อทำให้เสร็จ
หากคุณดูใกล้ ๆ กับการโทรหาeval
คุณจะเห็นว่าเราสามารถควบคุมตำแหน่งนี้ได้ 100% "ข้างใน" eval
เราอยู่ในส่วนย่อยดังนั้นเราสามารถทำทุกอย่างที่ต้องการได้โดยไม่ต้องกลัวว่าจะทำอะไรไม่ดีกับเปลือกของผู้ปกครอง
ใช่ดีดังนั้นเรามาเพิ่มกระดาษห่ออื่นตอนนี้อยู่ในeval
:
_passback() { while [ 0 -lt $# ]; do printf '%q=%q;' "$1" "${!1}"; shift; done; }
_xcapture() { "${@:2}" > >(printf "%q=%q;" "$1" "$(cat)"); _passback x; }
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; echo
x
a
_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
ส่งคืน คุณก็คงต้องการสิ่งนี้เช่นกัน มันต้องการการปรับแต่งที่มากขึ้นแม้ว่า:
_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 "$@")"; }
fails_() { passback x y; }
fails() { x=$1; y=69; echo FAIL; return 23; }
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
โปรดทราบว่าbash
4.1 ขึ้น{fd}
ไปต้องใช้ FD ที่ไม่ได้ใช้
(บางทีฉันอาจจะเพิ่มโซลูชันที่นี่เมื่อฉันมารอบ ๆ )
โปรดทราบว่านี่คือเหตุผลที่ฉันใช้เพื่อวางไว้ในฟังก์ชั่นที่แยกจากกันเช่น_capture
เนื่องจากการรวมสิ่งนี้ทั้งหมดไว้ในบรรทัดเดียวเป็นไปได้ แต่จะทำให้อ่านและทำความเข้าใจได้ยากขึ้นเรื่อย ๆ
บางทีคุณอาจต้องการจับภาพ STDERR ของฟังก์ชันที่เรียกว่าด้วย หรือคุณต้องการส่งผ่านตัวอธิบายไฟล์มากกว่าหนึ่งตัวจากและไปยังตัวแปร
ฉันยังไม่มีวิธีแก้ปัญหาอย่างไรก็ตามนี่เป็นวิธีจับ FD มากกว่าหนึ่งดังนั้นเราอาจส่งผ่านตัวแปรด้วยวิธีนี้ได้เช่นกัน
อย่าลืม:
สิ่งนี้ต้องเรียกใช้ฟังก์ชันเชลล์ไม่ใช่คำสั่งภายนอก
ไม่มีวิธีง่ายๆในการส่งผ่านตัวแปรสภาพแวดล้อมออกจากคำสั่งภายนอก (ด้วยLD_PRELOAD=
น่าจะเป็นไปได้!) แต่นี่เป็นสิ่งที่แตกต่างไปจากเดิมอย่างสิ้นเชิง
คำสุดท้าย
นี่ไม่ใช่ทางออกเดียวที่เป็นไปได้ เป็นตัวอย่างหนึ่งในการแก้ปัญหา
เช่นเคยคุณมีหลายวิธีในการแสดงสิ่งต่างๆในเปลือกหอย ดังนั้นอย่าลังเลที่จะปรับปรุงและค้นหาสิ่งที่ดีกว่า
วิธีแก้ปัญหาที่นำเสนอนี้ค่อนข้างห่างไกลจากความสมบูรณ์แบบ:
- มันเกือบจะไม่ใช่ testet เลยดังนั้นโปรดยกโทษให้กับการพิมพ์ผิด
- มีพื้นที่สำหรับการปรับปรุงอีกมากดูด้านบน
- มันใช้คุณสมบัติหลายอย่างตั้งแต่สมัยใหม่
bash
ดังนั้นจึงยากที่จะโอนไปยังเปลือกหอยอื่น ๆ
- และอาจมีนิสัยแปลก ๆ ที่ฉันไม่ได้คิด
อย่างไรก็ตามฉันคิดว่ามันค่อนข้างใช้งานง่าย:
- เพิ่ม "ไลบรารี" เพียง 4 บรรทัด
- เพิ่ม "คำอธิบายประกอบ" เพียง 1 บรรทัดสำหรับฟังก์ชันเชลล์ของคุณ
- เสียสละตัวอธิบายไฟล์เพียงไฟล์เดียวชั่วคราว
- และแต่ละขั้นตอนควรเข้าใจได้ง่ายแม้หลายปีต่อมา