_gem_dec() { shift $# ; . /dev/fd/3
} 3<<-FUNC
_${1}() { [ ! -e 'Gemfile' ] && {
command $1 "\$@" ; return \$?
} || bundle exec $1 "\$@"
}
FUNC
for func in guard rspec rake ; do _gem_dec $func ; done
echo "_guard ; _rspec ; _rake are all functions now."
จะ. source /dev/fd/3ถูกป้อนเข้าสู่_gem_dec()ฟังก์ชันทุกครั้งที่เรียกว่าเป็นงานที่ประเมินล่วงหน้าhere-document. _gem_dec'sเท่านั้นที่จะได้รับหนึ่งพารามิเตอร์และประเมินล่วงหน้าเป็นทั้งbundle execเป้าหมายและเป็นชื่อของฟังก์ชันที่มีการกำหนดเป้าหมาย
NOTE: . sourcing shell expansions results in twice-evaluated variables - just like eval. It can be risky.
ในกรณีข้างต้นฉันไม่คิดว่าจะมีความเสี่ยงใด ๆ
ถ้ารหัสบล็อกดังกล่าวข้างต้นจะถูกคัดลอกลงใน.bashrcไฟล์ไม่เพียง แต่จะฟังก์ชั่นเปลือก_guard(), _rspec()และ_rake()ได้รับการประกาศในการเข้าสู่ระบบ แต่_gem_dec()ฟังก์ชั่นยังจะสามารถใช้ได้สำหรับการดำเนินการในเวลาใด ๆ ที่เปลือกของคุณพรอมต์(หรืออื่น ๆ )และฟังก์ชั่นเทมเพลตใหม่สามารถ ได้รับการประกาศเมื่อใดก็ตามที่คุณต้องการด้วยเพียง:
_gem_dec $new_templated_function_name
และต้องขอบคุณ @Andrew ที่แสดงให้ฉันเห็นสิ่งเหล่านี้จะไม่ได้รับการกินโดย for loop.
แต่อย่างไร
ฉันใช้3file descriptor ด้านบนเพื่อไม่ให้เกิดการstdin, stdout, and stderr, or <&0 >&1 >&2เปิด - ถึงอย่างนั้นเป็นกรณีของข้อควรระวังเริ่มต้นอื่น ๆ ที่ฉันใช้ที่นี่เพราะฟังก์ชั่นที่ได้นั้นง่ายมากจึงไม่จำเป็นจริงๆ มันเป็นวิธีปฏิบัติที่ดีแม้ว่า การโทรshift $#เป็นอีกหนึ่งข้อควรระวังที่ไม่จำเป็น
แต่ถึงกระนั้นเมื่อไฟล์ที่ถูกระบุเป็น<inputหรือ>outputมี[optional num]<fileหรือ[optional num]>fileเปลี่ยนเส้นทางเคอร์เนลอ่านมันเป็นอธิบายไฟล์ซึ่งสามารถเข้าถึงได้ผ่านทางไฟล์พิเศษในcharacter device หากละเว้นตัวระบุจะถือว่าเป็นอินพุตและเอาต์พุต พิจารณาสิ่งนี้:/dev/fd/[0-9]*[optional num]0<file1>file
l='line %d\n' ; printf "$l" 1 2 3 4 5 6 >/dev/fd/1
> line 1
> line 2
> line 3
> line 4
> line 5
> line 6
( printf "$l" 4 5 6 >/dev/fd/3 ; printf "$l" 1 2 3 ) >/tmp/sample 3>/tmp/sample2
( cat /tmp/sample2 ) </tmp/sample
> line 4
> line 5
> line 6
( cat /dev/fd/0 ) </tmp/sample
> line 1
> line 2
> line 3
( cat /dev/fd/3 ) </tmp/sample 3</tmp/sample2
> line 4
> line 5
> line 6
และเนื่องจาก a here-documentเป็นเพียงวิธีการอธิบายไฟล์แบบอินไลน์ภายในโค้ดบล็อคเมื่อเรา:
<<'HEREDOC'
[$CODE]
HEREDOC
เราอาจทำเช่นกัน:
echo '[$CODE]' >/dev/fd/0
ด้วยความแตกต่างที่สำคัญอย่างหนึ่ง หากคุณไม่ได้"'\quote'"เป็น<<"'\LIMITER"'ของhere-documentเปลือกแล้วจะประเมินให้เปลือก$expansionเช่น:
echo "[$CODE]" >/dev/fd/0
ดังนั้นสำหรับ_gem_dec(),3<<-FUNC here-documentได้รับการประเมินเป็นไฟล์กับการป้อนข้อมูลเช่นเดียวกับที่มันจะถ้ามันเป็น3<~/some.file ยกเว้นว่าเป็นเพราะเราปล่อยให้FUNClimiterฟรีของคำพูดที่มันได้รับการประเมินครั้งแรกสำหรับ$expansion.สิ่งที่สำคัญเกี่ยวกับเรื่องนี้ก็คือว่ามันคือการใส่ความหมาย มันมีอยู่เพียงเพื่อ_gem_dec(),แต่มันก็ยังมีการประเมินก่อนที่จะ_gem_dec()ทำงานฟังก์ชั่นเพราะเปลือกของเราจะต้องอ่านและประเมินผล$expansionsก่อนที่จะส่งมันออกมาเป็นอินพุต
ให้ทำguard,เช่น:
_gem_dec guard
ดังนั้นก่อนอื่นเชลล์ต้องจัดการอินพุตซึ่งหมายถึงการอ่าน:
3<<-FUNC
_${1}() { [ ! -e 'Gemfile' ] && {
command $1 "\$@" ; return \$?
} || bundle exec $1 "\$@"
}
FUNC
ลงใน file descriptor 3 และประเมินผลสำหรับการขยายเชลล์ ถ้าในเวลานี้คุณวิ่ง:
cat /dev/fd/3
หรือ:
cat <&3
เนื่องจากทั้งคู่เป็นคำสั่งที่เทียบเท่าคุณจะเห็น *:
_guard() { [ ! -e 'Gemfile' ] && {
command guard "$@" ; return $?
} || bundle exec guard "$@"
}
... ก่อนหน้ารหัสใด ๆ ในฟังก์ชั่นรันเลย นี้เป็นฟังก์ชั่น<input,หลังจากทั้งหมด สำหรับตัวอย่างเพิ่มเติมดูคำตอบของฉันคำถามที่แตกต่างกันที่นี่
(* เทคนิคนี้ไม่เป็นความจริงอย่างสมบูรณ์เพราะฉันใช้ผู้นำ-dashก่อนหน้าhere-doc limiterทั้งหมดข้างต้นจะถูกจัดชิดซ้าย แต่ฉันใช้-dashเพื่อให้สามารถ<tab-insert>อ่านได้ตั้งแต่แรกดังนั้นฉันจะไม่ถอด<tab-inserts>ก่อน เสนอให้คุณอ่าน ... )
ส่วนที่ดีที่สุดเกี่ยวกับเรื่องนี้คือการอ้างอิง - โปรดสังเกตว่า'"ราคายังคงอยู่และมีเพียง\ราคาที่ถูกปล้น มันอาจจะเป็นเพราะเหตุนี้มากขึ้นกว่าที่อื่น ๆ ว่าถ้าคุณมีถึงสองครั้งประเมินเปลือก$expansionผมจะแนะนำhere-documentเพราะคำพูดที่มีมากง่ายกว่าeval
อย่างไรก็ตามในขณะนี้โค้ดข้างต้นเป็นเหมือนไฟล์เลี้ยงเหมือน3<~/heredoc.fileเพียงแค่รอฟังก์ชั่นจะได้รับไปและยอมรับการป้อนข้อมูลของตนใน_gem_dec()/dev/fd/3
ดังนั้นเมื่อเราเริ่มต้น_gem_dec()สิ่งแรกที่ฉันทำคือการโยนพารามิเตอร์ตำแหน่งทั้งหมดเพราะขั้นตอนต่อไปของเราคือการขยายตัวของเชลล์ที่ประเมินเป็นสองเท่าและฉันไม่ต้องการให้มี$expansionsการตีความ$1 $2 $3...พารามิเตอร์ใด ๆ ที่เป็นพารามิเตอร์ปัจจุบันของฉัน ดังนั้นฉัน:
shift $#
shiftทิ้งให้มากpositional parametersที่สุดเท่าที่คุณระบุและเริ่มจาก$1สิ่งที่เหลืออยู่ ดังนั้นถ้าผมเรียกว่า_gem_dec one two threeพรอมต์_gem_dec's $1 $2 $3พารามิเตอร์ตำแหน่งจะเป็นone two threeและรวมนับตำแหน่งปัจจุบันหรือ$#จะเป็น 3. หากฉันแล้วเรียกว่าshift 2,ค่าของoneและtwoจะได้รับการshiftedไปค่าของ$1จะเปลี่ยนไปthreeและ$#จะขยายไปยัง1.ดังนั้นshift $#เพียง โยนพวกเขาทั้งหมดออกไป การทำเช่นนี้เป็นข้อควรระวังอย่างเคร่งครัดและเป็นเพียงนิสัยที่ฉันพัฒนาขึ้นหลังจากทำสิ่งนี้มาชั่วระยะเวลาหนึ่ง ที่นี่มีการ(subshell)แพร่กระจายเล็กน้อยเพื่อประโยชน์ของความชัดเจน:
( set -- one two three ; echo "$1 $2 $3" ; echo $# )
> one two three
> 3
( set -- one two three ; shift 2 ; echo "$1 $2 $3" ; echo $# )
> three
> 1
( set -- one two three ; shift $# ; echo "$1 $2 $3" ; echo $# )
>
> 0
อย่างไรก็ตามขั้นตอนต่อไปคือสิ่งที่เวทมนตร์เกิดขึ้น หากคุณ. ~/some.shอยู่ที่ shell prompt ฟังก์ชันและตัวแปรสภาพแวดล้อมทั้งหมดที่ประกาศไว้~/some.shจะสามารถเรียกใช้งานได้ที่ shell prompt เดียวกันเป็นจริงที่นี่ยกเว้นเราแฟ้มพิเศษสำหรับอธิบายไฟล์ของเราหรือ- ซึ่งเป็นที่ที่เราไฟล์ในสายการผลิตที่ได้รับการ pathed - และเราได้ประกาศฟังก์ชั่นของเรา และนั่นคือวิธีการทำงาน. sourcecharacter device. /dev/fd/3here-document
_guard
ตอนนี้สิ่งที่_guardฟังก์ชั่นของคุณควรจะทำ
ภาคผนวก:
วิธีที่ดีในการพูดช่วยรักษาตำแหน่งของคุณ:
f() { . /dev/fd/3
} 3<<-ARGS
args='${args:-"$@"}'
ARGS
แก้ไข:
เมื่อแรกที่ฉันตอบคำถามนี้ฉันมุ่งเน้นไปที่ปัญหาของการประกาศเชลล์ที่function()สามารถประกาศฟังก์ชั่นอื่น ๆ ที่จะคงอยู่ใน$ENViron ironเชลล์ปัจจุบันมากกว่าที่ฉันทำกับสิ่งที่ผู้ถามจะทำกับฟังก์ชั่นถาวรดังกล่าว ตั้งแต่นั้นมาฉันก็ตระหนักว่าโซลูชันที่ฉันเสนอมา แต่เดิม3<<-FUNCใช้แบบฟอร์มนี้:
3<<-FUNC
_${1}() {
if [ -e 'Gemfile' ]; then
bundle exec $1 "\$@"
else
command _${1} "\$@"
}
FUNC
อาจจะไม่ได้ทำงานตามที่คาดไว้สำหรับถามเพราะผมมีการเปลี่ยนแปลงโดยเฉพาะชื่อของฟังก์ชั่นที่เปิดเผยจาก$1ไป_${1}ซึ่งถ้าเรียกว่าเหมือน_gem_dec guardเช่นจะส่งผลในการ_gem_decประกาศฟังก์ชั่นที่มีชื่อว่าเป็นนอกคอกเพียง_guardguard
หมายเหตุ:พฤติกรรมดังกล่าวเป็นเรื่องของนิสัยสำหรับฉัน - ฉันมักจะทำงานในข้อสันนิษฐานว่าฟังก์ชั่นเปลือกควรครอบครองเฉพาะของตัวเอง_namespaceเพื่อหลีกเลี่ยงการบุกรุกบนnamespaceเชลล์commandsที่เหมาะสม
นี้ไม่ได้เป็นนิสัยสากลแม้ว่าจะเป็นที่แจ้งในการใช้งานถามของการเรียกร้องcommand$1
การตรวจสอบเพิ่มเติมทำให้ฉันเชื่อในสิ่งต่อไปนี้:
ผู้ถามต้องการฟังก์ชั่นของเชลล์ที่ชื่อguard, rspec, or rakeเมื่อเรียกใช้จะรวบรวมrubyฟังก์ชั่นใหม่ที่มีชื่อเดียวกันifกับไฟล์ที่Gemfileมีอยู่ใน$PATH OR
if Gemfileไม่มีอยู่ฟังก์ชันของเชลล์ควรเรียกใช้rubyฟังก์ชันที่ชื่อเดียวกัน
สิ่งนี้จะไม่ทำงานก่อนหน้านี้เพราะฉันได้เปลี่ยน$1ชื่อcommandที่อ่านโดย:
command _${1}
ซึ่งจะไม่มีผลในการดำเนินการของrubyฟังก์ชั่นที่ฟังก์ชั่นเปลือกรวบรวมเป็น:
bundle exec $1
ฉันหวังว่าคุณจะเห็น(ในที่สุดฉันก็)ว่าดูเหมือนว่าผู้ถามจะใช้เพียงcommandเพื่อระบุทางอ้อมnamespaceเพราะcommandจะต้องการเรียกไฟล์ปฏิบัติการใน$PATHกว่าฟังก์ชั่นเชลล์ในชื่อเดียวกัน
หากการวิเคราะห์ของฉันถูกต้อง(เพราะฉันหวังว่าผู้ถามจะยืนยัน)สิ่งนี้:
_${1}() { [ ! -e 'Gemfile' ] && {
command $1 "\$@" ; return \$?
} || bundle exec $1 "\$@"
}
ดีกว่าที่ควรจะตอบสนองเงื่อนไขเหล่านั้นมีข้อยกเว้นว่าการเรียกร้องguardที่จะแจ้งให้เพียงความพยายามที่จะดำเนินการแฟ้มที่ปฏิบัติการใน$PATHชื่อguardขณะที่เรียกร้อง_guardที่พร้อมท์จะตรวจสอบสำหรับGemfile'sการดำรงอยู่และรวบรวมตามหรือดำเนินการในการปฏิบัติการguard ด้วยวิธีนี้ได้รับการคุ้มครองและอย่างน้อยที่สุดฉันก็เข้าใจเจตนาของผู้ถามยังคงเป็นจริง$PATHnamespace
ในความเป็นจริงการสันนิษฐานว่าฟังก์ชั่นเชลล์ของเรา_${1}()และเอ็กซีคิ้วท์${PATH}/${1}เป็นสองวิธีเท่านั้นที่เชลล์ของเราสามารถตีความการเรียกใช้$1หรือ_${1}จากนั้นการใช้commandฟังก์ชั่นเลยก็ทำซ้ำซ้อนทั้งหมด ถึงกระนั้นฉันก็ปล่อยให้มันยังคงอยู่เพราะฉันไม่ชอบทำผิดซ้ำสองครั้ง ... ต่อเนื่องกัน
หากสิ่งนี้ไม่เป็นที่ยอมรับของผู้ถามและเขา / เธอต้องการที่จะทำสิ่งที่ถูกต้อง_ทั้งหมดในรูปแบบปัจจุบันการแก้ไขข้อผิดพลาด_underscoreควรเป็นสิ่งที่ผู้ถามทุกคนต้องทำเพื่อตอบสนองความต้องการของเขา / เธอตามที่ฉันเข้าใจ
นอกเหนือจากการเปลี่ยนแปลงนั้นฉันได้แก้ไขฟังก์ชั่นการใช้&&และ / หรือ||เงื่อนไขการลัดวงจรของเชลล์มากกว่าif/thenไวยากรณ์ดั้งเดิม ด้วยวิธีนี้commandคำสั่งจะถูกประเมินเท่านั้นที่ทุกคนถ้าไม่ได้อยู่ในGemfile การปรับเปลี่ยนนี้ไม่จำเป็นต้องมีการเพิ่มอย่างไรก็ตามเพื่อให้แน่ใจว่าคำสั่งจะไม่ทำงานในกรณีที่ไม่มีอยู่ แต่ฟังก์ชันจะส่งคืนสิ่งอื่นที่ไม่ใช่0$PATHreturn $?bundleGemfileruby $1
สุดท้ายฉันควรทราบว่าโซลูชันนี้ใช้โครงสร้างเปลือกแบบพกพาเท่านั้น กล่าวอีกนัยหนึ่งสิ่งนี้ควรให้ผลลัพธ์ที่เหมือนกันในเชลล์ใด ๆ ที่อ้างถึงความเข้ากันได้ของ POSIX ขณะที่มันจะแน่นอนเป็นเรื่องไร้สาระสำหรับผมที่จะเรียกร้องทุกระบบ POSIX ได้จะต้องจัดการกับruby bundleคำสั่งอย่างน้อยสนองเปลือกเรียกร้องให้มันควรประพฤติเดียวกันโดยไม่คำนึงถึงว่าเปลือกเรียกเป็นหรือsh dashนอกจากนี้การทำงานจะดังกล่าวข้างต้นเป็นไปตามคาด(ทะนงอย่างน้อยครึ่งหนึ่งสติshoptsอยู่แล้ว)ทั้งในและbashzsh
for loop?ฉันหมายความว่าตัวแปรที่ประกาศfor loopโดยทั่วไปหายไป - ฉันคาดหวังฟังก์ชั่นเดียวกันด้วยเหตุผลเดียวกัน