เป็นไปได้หรือไม่ที่จะทำการทดแทนคำสั่ง shell โดยไม่ใช้เชลล์ย่อย?


11

ฉันมีสถานการณ์ที่เรียกการทดแทนคำสั่งโดยไม่ใช้ subshell ฉันมีสิ่งก่อสร้างเช่นนี้:

pushd $(mktemp -d)

ตอนนี้ฉันต้องการออกและลบไดเรกทอรีชั่วคราวในครั้งเดียว:

rmdir $(popd)

อย่างไรก็ตามมันใช้งานไม่ได้เพราะpopdไม่ได้ส่งคืนไดเรกทอรีที่ถูกตอก (มันจะส่งคืนไดเรกทอรีใหม่ที่เป็นปัจจุบันในปัจจุบัน) และเพราะมันถูกทำใน subshell

สิ่งที่ต้องการ

dirs -l -1 ; popd &> /dev/null

จะส่งคืนไดเรกทอรี popped แต่ไม่สามารถใช้เช่นนี้:

rmdir $(dirs -l -1 ; popd &> /dev/null)

เพราะpopdจะส่งผลกระทบต่อ subshell เท่านั้น สิ่งที่เรียกว่าเป็นความสามารถในการทำเช่นนี้:

rmdir { dirs -l -1 ; popd &> /dev/null; }

แต่นั่นเป็นไวยากรณ์ที่ไม่ถูกต้อง มันเป็นไปได้ที่จะบรรลุผลนี้หรือไม่?

(หมายเหตุ: ฉันรู้ว่าฉันสามารถบันทึกไดเรกทอรีชั่วคราวในตัวแปรได้ฉันพยายามหลีกเลี่ยงความจำเป็นที่จะต้องทำและเรียนรู้สิ่งใหม่ในกระบวนการ!)


1
ฉันจะบันทึกไดเรกทอรีเป็นตัวแปร วิธีนี้traphandler สามารถล้างไดเร็กทอรีได้หากโปรเซสถูกสัญญาณโดยสัญญาณ
thrig

คำตอบคือไม่มี
h0tw1r3

ดูเหมือนว่าบนfishเทียบเท่ากับการทดแทนคำสั่งเปลี่ยน()โฟลเดอร์เปลือกนอก มันมักจะน่ารำคาญ แต่ในกรณีเช่นนี้มันมีประโยชน์ฉันแน่ใจ
trysis

คำตอบ:


10

การเลือกชื่อคำถามของคุณค่อนข้างสับสน

pushd/ popd, cshคุณสมบัติที่คัดลอกโดยbashและzsh, เป็นวิธีจัดการสแต็คของไดเรกทอรีที่จำได้

pushd /some/dir

พุชไดเร็กทอรีการทำงานปัจจุบันลงบนสแต็กจากนั้นเปลี่ยนไดเร็กทอรีการทำงานปัจจุบัน (จากนั้นพิมพ์/some/dirตามด้วยเนื้อหาของสแต็กนั้น (คั่นด้วยช่องว่าง)

popd

พิมพ์เนื้อหาของสแต็ก (อีกครั้งคั่นด้วยช่องว่าง) จากนั้นเปลี่ยนเป็นองค์ประกอบด้านบนของสแต็กและปรากฏจากสแต็ก

(ระวังด้วยว่าบางไดเร็กทอรีจะถูกแสดงที่นั่นพร้อมกับ~/xหรือ~user/xหมายเหตุ)

ดังนั้นหากสแต็กปัจจุบันมี/aและ/bไดเรกทอรีปัจจุบันคือ/hereและคุณกำลังทำงาน:

 pushd /tmp/whatever
 popd

pushdจะพิมพ์/tmp/whatever /here /a /bและการpopdส่งออกจะไม่/here /a /b /tmp/whateverที่เป็นอิสระจากการใช้การทดแทนคำสั่งหรือไม่ popdไม่สามารถใช้เพื่อรับพา ธ ของไดเรกทอรีก่อนหน้าและโดยทั่วไปเอาต์พุตไม่สามารถประมวลผลการโพสต์ได้ (ดู$dirstackหรือ$DIRSTACKอาร์เรย์ของเชลล์บางตัวเพื่อเข้าถึงองค์ประกอบของสแต็กไดเรกทอรีนั้น)

บางทีคุณอาจต้องการ:

pushd "$(mktemp -d)" &&
popd &&
rmdir "$OLDPWD"

หรือ

cd "$(mktemp -d)" &&
cd - &&
rmdir "$OLDPWD"

แม้ว่าฉันจะใช้:

tmpdir=$(mktemp -d) || exit
(
  cd "$tmpdir" || exit # in a subshell 
  # do what you have to do in that tmpdir
)
rmdir "$tmpdir"

ไม่ว่าในกรณีใดpushd "$(mktemp -d)"ไม่ทำงานpushdใน subshell หากเป็นเช่นนั้นจะไม่สามารถเปลี่ยนไดเรกทอรีทำงานได้ นั่นคือmktempที่ทำงานใน subshell เนื่องจากเป็นคำสั่งแยกต่างหากจึงต้องรันในกระบวนการแยกต่างหาก มันเขียนเอาต์พุตบนไพพ์และกระบวนการเชลล์อ่านมันที่ปลายอีกด้านของไพพ์

ksh93 สามารถหลีกเลี่ยงกระบวนการที่แยกต่างหากเมื่อคำสั่งถูกสร้างขึ้น แต่แม้จะมีก็ยังคงเป็น subshell (สภาพแวดล้อมการทำงานที่แตกต่างกัน) ซึ่งเวลานี้จะถูกจำลองขึ้นมาแทนที่จะอาศัยสภาพแวดล้อมที่แยกต่างหากตามปกติโดยการฟอร์ก ยกตัวอย่างเช่นในksh93, a=0; echo "$(a=1; echo test)"; echo "$a"ไม่มีส้อมมีส่วนเกี่ยวข้อง แต่ก็ยังเอาท์พุทecho "$a"0

ที่นี่หากคุณต้องการเก็บเอาท์พุทของmktempตัวแปรในเวลาเดียวกับที่คุณส่งมันไปpushdด้วยzshคุณสามารถทำได้:

pushd ${tmpdir::="$(mktemp -d)"}

กับกระสุนคล้ายบอร์นอื่น ๆ :

unset tmpdir
pushd "${tmpdir=$(mktemp -d)}"

หรือหากต้องการใช้เอาต์พุต$(mktemp -d)หลายครั้งโดยไม่เก็บไว้ในตัวแปรอย่างชัดเจนคุณสามารถใช้zshฟังก์ชั่นที่ไม่ระบุชื่อ:

(){pushd ${1?} && cd - && rmdir $1} "$(mktemp -d)"

ฉันเข้าใจpushdและpopdทำงานตามที่คุณอธิบายและพวกเขาเป็นอิสระจากการแทนที่คำสั่ง - ไม่มีความสับสน! $OLDPWDแต่คุณตอบจากปัญหาของตัวเองด้วยการเปิดเผย popd; rmdir $OLDPWDฉันจะทำ นั่นเป็นคำตอบสำหรับปัญหาของฉัน - ทุกอย่างเพียงแค่ยืนยันสิ่งที่ฉันคิด การทดแทนคำสั่งดูเหมือนจะเป็นวิธีการแก้ปัญหา แต่ไม่ใช่เพราะ subshell และคุณไม่สามารถทำการทดแทนคำสั่งโดยไม่มี subshell ได้ดังนั้นขอบคุณที่เปิดเผย OLDPWD - นั่นคือสิ่งที่ฉันต้องการ!
starfry

@starfry rmdir $(popd)ทำให้เกิดpopdการทำงานใน sub-shell ซึ่งหมายความว่ามันจะไม่เปลี่ยนไดเรกทอรีปัจจุบัน แต่แม้ว่ามันจะไม่ได้ทำงานใน subshell ผลลัพธ์ของpopdจะไม่เป็นไดเรกทอรีชั่วคราวมันจะเป็นรายการที่คั่นด้วยช่องว่าง ของไดเรกทอรีไม่รวมถึง temp dir ซึ่งฉันกำลังบอกว่าคุณสับสน
Stéphane Chazelas

แต่ฉันคิดว่าฉันได้กล่าวว่าในคำถามของฉัน: "popd ไม่ส่งคืนไดเรกทอรี popped (มันจะส่งกลับไดเรกทอรีใหม่ในปัจจุบันในขณะนี้) และเพราะมันถูกดำเนินการใน subshell" เป็นที่ยอมรับว่าจะส่งคืนรายการ แต่สิ่งแรกในรายการคือรายการที่ฉันอ้างถึง
starfry

@starfry โอเคขอโทษ สมมติว่าฉันเป็นคนที่สับสนกับชื่อของคำถามและไม่ได้อ่านส่วนที่เหลืออย่างระมัดระวังพอ
Stéphane Chazelas

คำตอบของคุณก็ยังดีและชี้ให้ฉันในทิศทางที่ถูกต้อง OLDPWDฉันไม่เคยรู้เกี่ยวกับตัวแปรเชลล์ที่ ฉันต้องจำไว้ว่าให้อ่านman bashตั้งแต่ต้นจนจบ!
starfry

2

คุณสามารถยกเลิกการเชื่อมโยงไดเรกทอรีก่อนที่จะทิ้งมันไป:

rmdir "$(pwd -P)" && popd

หรือ

rmdir "$(pwd -P)" && cd ..   # yes, .. is still usable

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


0

ใน bash dirsจัดเตรียมรายการของไดเร็กทอรีที่จดจำโดยวิธี pushd / popd

รวมทั้งdirs -1พิมพ์ไดเรกทอรีสุดท้ายที่รวมอยู่ในรายการ

ดังนั้นหากต้องการลบไดเรกทอรีที่สร้างไว้ก่อนหน้านี้โดยการpushd $(mktmp -d)ใช้งานให้ใช้:

rmdir $(dirs -1)

จากนั้นpopdไดเรกทอรีที่ถูกลบไปแล้วจากรายการ:

popd > /dev/null

ทั้งหมดในหนึ่งบรรทัด:

rmdir $(dirs -1); popd > /dev/null

และเพิ่มตัวเลือก (-l) เพื่อหลีกเลี่ยงการ~/xหรือ~user/xสัญกรณ์:

rmdir $(dirs -l -1); popd > /dev/null

ซึ่งคล้ายกับบรรทัดที่คุณขออย่างน่าทึ่ง
ยกเว้นว่าฉันจะไม่ใช้&>มันจะซ่อนข้อผิดพลาดใด ๆ popdที่รายงานโดย

หมายเหตุ: ไดเรกทอรีจะยังคงอยู่หลังจากrmdirนั้นในpwdตอนนั้น และจะถูกยกเลิกการเชื่อมโยงจริง ๆ หลังจากpopdคำสั่ง (ไม่มีการใช้ลิงก์ที่เหลืออยู่)


มีตัวเลือกให้ใช้สำหรับเชลล์ที่สนับสนุนตัวแปร "OLDPWD" (เชลล์ที่คล้ายกับ bourne ส่วนใหญ่: ksh, bash, zsh มี$OLDPWD) เป็นที่น่าสนใจที่จะทราบว่าksh ไม่ได้ใช้ dirs, pod, pushd โดยค่าเริ่มต้น (lksh, dash และอื่น ๆ ยังไม่มี popd ให้ใช้ดังนั้นไม่สามารถใช้งานได้ที่นี่):

popd && rmdir "$OLDPWD"

หรือมากกว่าเป็นสำนวน (รายการหอยเหมือนข้างบน):

popd && rmdir ~-

ปัญหาเกี่ยวกับสิ่งนี้และสิ่งที่ฉันพยายามแก้ไขคือคุณไม่สามารถrmdirไดเรกทอรีปัจจุบันซึ่งเป็นที่ที่คุณจะทำเมื่อทำสิ่งนี้ คุณต้องทำให้เกิดผลเสียpopdก่อนrmdirแต่คุณต้องรู้ว่าจะลบอะไรและpopdไม่ได้บอกคุณว่า ฉันก็เหมือนกับคุณคิดว่าdirs -l -1เป็นคำตอบ แต่เมื่อค้นพบว่าคำตอบนั้นใช้งาน$OLDPWDได้จริง
starfry

@starfry ฉันเชื่อว่าคำตอบที่สั้นที่สุดคือการใช้: popd && rmdir ~-.

@starfry จริง ๆ แล้วคุณสามารถลบไดเรกทอรีปัจจุบันโดยไม่มีปัญหาหากไม่มีข้อมูล (โดยไม่บังคับให้ลบ) คุณสามารถโทรrmdir "$PWD"ได้เลย นอกจากนี้ไดเรกทอรีปัจจุบันควรเป็นไดเรกทอรีที่สร้างขึ้นโดยmktmp -d(ถ้าpushd $(mktemp -d)ถูกดำเนินการล่วงหน้า) และไปที่pushdpwd ดังนั้นใช่หลังจาก pushdและก่อนหน้า popd ไดเรกทอรีที่สร้างขึ้นจะถูกเก็บไว้ใน$(dirs -1)ฉันไม่เห็นปัญหาใด ๆ
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.