วิธีดำเนินการเอาต์พุตของคำสั่งภายในเชลล์ปัจจุบัน


92

ฉันทราบดีถึงยูทิลิตี้source(aka .) ซึ่งจะนำเนื้อหาจากไฟล์และเรียกใช้งานภายในเชลล์ปัจจุบัน

ตอนนี้ฉันกำลังเปลี่ยนข้อความบางส่วนเป็นคำสั่งเชลล์แล้วเรียกใช้งานดังต่อไปนี้:

$ ls | sed ... | sh

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

ปัญหาของฉันคือนั่นหมายถึงการเริ่มต้นเชลล์ย่อยใหม่ ฉันอยากให้คำสั่งทำงานภายในเชลล์ปัจจุบันของฉัน เช่นเดียวกับที่ฉันสามารถทำได้source some-fileถ้าฉันมีคำสั่งในไฟล์ข้อความ

ฉันไม่ต้องการสร้างไฟล์ชั่วคราวเพราะรู้สึกว่าสกปรก

หรือฉันต้องการเริ่มเชลล์ย่อยของฉันด้วยลักษณะเดียวกับเชลล์ปัจจุบันของฉัน

อัพเดต

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

อัปเดตที่น่าเศร้า

อ่า/dev/stdinสิ่งนี้ดูสวยมาก แต่ในกรณีที่ซับซ้อนกว่านั้นมันไม่ได้ผล

ฉันมีสิ่งนี้:

find . -type f -iname '*.doc' | ack -v '\.doc$' | perl -pe 's/^((.*)\.doc)$/git mv -f $1 $2.doc/i' | source /dev/stdin

ซึ่งทำให้มั่นใจได้ว่า.docไฟล์ทั้งหมดจะมีส่วนขยายที่ลดลง

และสิ่งใดที่สามารถจัดการได้โดยบังเอิญxargsแต่นั่นก็นอกเหนือจากประเด็น

find . -type f -iname '*.doc' | ack -v '\.doc$' | perl -pe 's/^((.*)\.doc)$/$1 $2.doc/i' | xargs -L1 git mv

ดังนั้นเมื่อฉันเรียกใช้อดีตมันจะออกทันทีไม่มีอะไรเกิดขึ้น


คำสั่งที่ซับซ้อนของคุณใช้งานได้หรือไม่เมื่อคุณไพพ์ไปยังไฟล์ temp ก่อนแล้วจึงกำหนดแหล่งที่มา ถ้าไม่ปัญหากับผลลัพธ์ที่สร้างขึ้นคืออะไร? ผลลัพธ์ของคำสั่งของคุณจะไม่ทำงานหากชื่อไฟล์ของคุณมีช่องว่างอยู่หรือถ้าลำดับบางอย่างไม่ได้หลีกเลี่ยงอย่างถูกต้อง ฉันต้องการเพิ่มราคาประมาณ $ 1 และ $ 2.doc เป็นอย่างต่ำ
Kaleb Pederson

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

@kaleb ผลลัพธ์ทำงานได้ดี ในกรณีนี้แม้ว่าฉันจะไปที่ sh ชื่อไฟล์มีพื้นที่ปลอดภัย แต่ขอขอบคุณที่แจ้งให้ทราบ @nos git ตัวแปรสภาพแวดล้อมบนเชลล์เดิม และอีกครั้งนี่เป็นเพียงตัวอย่างเท่านั้น คำถามคือเพื่อชีวิต
kch

source / dev / stdin ไม่ได้ผลสำหรับฉันเมื่อต้องการตัวแปรที่กำหนดให้เกาะติด geirha บน freenode bash ชี้ให้ฉันไปที่mywiki.wooledge.org/BashFAQ/024และแนะนำให้ฉันลองใช้แหล่งการทดแทนกระบวนการ <(คำสั่ง) ซึ่งเหมาะกับฉัน
srcerer

คำตอบ:


88
$ ls | sed ... | source /dev/stdin

UPDATE: ใช้งานได้ใน bash 4.0 เช่นเดียวกับ tcsh และ dash (ถ้าคุณเปลี่ยนsourceเป็น.) เห็นได้ชัดว่านี่คือบั๊กกี้ใน bash 3.2 จากบันทึกประจำรุ่น bash 4.0 :

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


โอ้และเนื่องจากคุณแสดงรายชื่อเชลล์มันก็ใช้งานได้ใน zsh ด้วย
kch

2
ฉันคิดว่านี่เป็นความคิดที่แยบยลจนกระทั่งฉันได้ลองใช้ใน msys / mingw (ที่ไม่มีโฟลเดอร์ / dev (หรือแม้แต่อุปกรณ์)! ฉันลองใช้ eval, $ () และ backticks หลายชุดร่วมกัน `` ฉันทำอะไรไม่ได้เลย ดังนั้นในที่สุดฉันก็เปลี่ยนเส้นทางเอาต์พุตไปยังไฟล์ temp หาที่มาของไฟล์ temp และลบออกโดยทั่วไป "sed ... > /tmp/$$.tmp && ./tmp/$$.tmp && rm / tmp / $$. tmp "ใครมีวิธีแก้ปัญหา msys / mingw ที่ไม่มีไฟล์ temp ???
chriv

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

ในทุบตีโดยไม่ต้องตั้งค่าตัวเลือก lastpipe นี้สร้าง subshell เหมือน| bashจะ
ว่าคนอื่น

1
เนื่องจาก MacOS X มักจะมี bash 3.2 (บางที Yosemite มี 4.0?) และความต้องการกลอุบาย lastpipe ในกรณีการใช้งานของฉัน (ส่งออกตัวแปรทั้งหมดในไฟล์โดยการประมวลผลล่วงหน้าแต่ละบรรทัดด้วย sed เพื่อเพิ่ม 'export') ฉันเลือก เข้ากับeval "$(sed ...)"แนวทาง - แต่ขอขอบคุณสำหรับการแก้ไขข้อผิดพลาด 3.2!
Alex Dupuy

135

evalคำสั่งที่มีอยู่สำหรับวัตถุประสงค์อย่างนี้

eval "$( ls | sed... )"

เพิ่มเติมจากคู่มือทุบตี :

ประเมิน

          eval [arguments]

อาร์กิวเมนต์จะเชื่อมต่อกันเป็นคำสั่งเดียวซึ่งจะถูกอ่านและดำเนินการและสถานะการออกจะส่งคืนเป็นสถานะออกของ eval หากไม่มีอาร์กิวเมนต์หรือมีเพียงอาร์กิวเมนต์ว่างสถานะส่งคืนจะเป็นศูนย์


8
ปัญหาเดียวที่นี่คือคุณอาจต้องแทรกเพื่อแยกคำสั่งของคุณ ฉันใช้วิธีนี้เพื่อทำงานกับ AIX, Sun, HP และ Linux
Tanktalus

Tanktalus ขอบคุณสำหรับความคิดเห็นนั้นมันทำให้สคริปต์ของฉันทำงานได้ ในเครื่องของฉัน eval ไม่ได้แยกคำสั่งในบรรทัดใหม่และการใช้ซอร์สไม่สามารถใช้งานได้แม้กับเซมิโคลอน Eval ด้วยเซมิโคลอนเป็นทางออก ฉันหวังว่าฉันจะให้คะแนนคุณได้บ้าง
Milan Babuškov

2
@ MilanBabuškov: ฉันจัดอันดับเขาให้คุณ ;-)
Phil

3
ยอดเยี่ยมมาก อย่างไรก็ตามสำหรับกรณีการใช้งานของฉันฉันต้องใส่เครื่องหมายคำพูดรอบ $ () ของฉันเช่นนั้น: eval "$( ssh remote-host 'cat ~/.bash_profile' )"โปรดทราบว่าฉันใช้ bash 3.2
Zachary Murray

3
สิ่งนี้ใช้ได้กับฉันโดยที่คำตอบที่ยอมรับไม่ได้
Dan Tenenbaum

37

ว้าวฉันรู้ว่านี่เป็นคำถามเก่า แต่ฉันพบว่าตัวเองมีปัญหาเดียวกันเมื่อเร็ว ๆ นี้ (นั่นคือสิ่งที่ฉันมาที่นี่)

อย่างไรก็ตาม - ฉันไม่ชอบsource /dev/stdinคำตอบ แต่ฉันคิดว่าฉันเจอคำตอบที่ดีกว่า มันง่ายจริง ๆ หลอกลวง:

echo ls -la | xargs xargs

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

ls | ... | xargs -L 1 xargs

-L 1ตัวเลือกวิธีที่คุณใช้ (มากที่สุด) 1 เส้นต่อการดำเนินการคำสั่ง หมายเหตุ:ถ้าบรรทัดของคุณลงท้ายด้วยช่องว่างต่อท้ายมันจะถูกต่อเข้ากับบรรทัดถัดไป! ดังนั้นตรวจสอบให้แน่ใจว่าแต่ละบรรทัดลงท้ายด้วยเว้นวรรค

สุดท้ายคุณสามารถทำได้

ls | ... | xargs -L 1 xargs -t

เพื่อดูว่าคำสั่งใดบ้างที่ดำเนินการ (-t คือ verbose)

หวังว่าจะมีคนอ่านสิ่งนี้!


31

ลองใช้การทดแทนกระบวนการซึ่งแทนที่เอาต์พุตของคำสั่งด้วยไฟล์ชั่วคราวซึ่งสามารถหาที่มาได้:

source <(echo id)

7
นี่คือคาถาอาคมประเภทใด
paulotorrens

1
นี่เป็นความคิดแรกของฉันเช่นกัน แม้ว่าฉันจะชอบวิธีการประเมินที่ดีกว่า @PauloTorrens <(xyz) เพียงเรียกใช้งาน xyz และแทนที่ <(xyz) ด้วยชื่อไฟล์ที่จะเขียนผลลัพธ์ของ xyz มันง่ายมากที่จะเข้าใจว่ามันทำงานอย่างไรโดยการทำตัวอย่างเช่นการecho <(echo id)ให้ผลลัพธ์/dev/fd/12(ตัวอย่าง 12) cat <(echo id)ให้ผลลัพธ์idจากนั้นsource <(echo id)ให้ผลลัพธ์เหมือนกับการเขียนเพียงอย่างid
netigger

2
@PauloTorrens นี้เรียกว่าขั้นตอนการเปลี่ยนตัว ดูเอกสารที่เชื่อมโยงสำหรับคำอธิบายอย่างเป็นทางการ แต่คำตอบสั้น ๆ คือ "<()" เป็นไวยากรณ์พิเศษที่ออกแบบมาสำหรับกรณีเช่นนี้
Mark Stosberg

1
@MarkStosberg คุณรู้หรือไม่ว่านี่เป็นไวยากรณ์พิเศษหรือเพียงแค่(...)เปลี่ยนเส้นทางย่อย
paulotorrens

2
@PauloTorrens เป็นไวยากรณ์พิเศษสำหรับการทดแทนกระบวนการ
Mark Stosberg

6

ฉันเชื่อว่านี่คือ "คำตอบที่ถูกต้อง" สำหรับคำถาม:

ls | sed ... | while read line; do $line; done

นั่นคือเราสามารถต่อท่อเข้าไปในwhileวงได้ readคำสั่งคำสั่งจะใช้เวลาหนึ่งบรรทัดจากมันและกำหนดให้กับตัวแปรstdin$line$lineจากนั้นจะกลายเป็นคำสั่งที่ดำเนินการภายในลูป และจะดำเนินต่อไปจนกว่าจะไม่มีบรรทัดเพิ่มเติมในอินพุต

สิ่งนี้ยังใช้ไม่ได้กับโครงสร้างการควบคุมบางอย่าง (เช่นลูปอื่น) แต่จะเหมาะกับใบเรียกเก็บเงินในกรณีนี้


5
`ls | sed ...`

จัดเรียงของฉันรู้สึกเหมือนls | sed ... | source -จะสวย แต่น่าเสียดายที่sourceไม่เข้าใจในความหมาย-stdin


หลังจากที่ได้เห็นคำตอบของ mark4o แล้วคราวนี้รู้สึกว่ามันถูกต้องบนใบหน้าของเราหรือเปล่า?
kch

เฮ้ใช่ ฉันไม่เคยจำว่ามีของอยู่
ความวุ่นวาย

1

ฉันคิดว่าโซลูชันของคุณคือการแทนที่คำสั่งด้วย backticks: http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_04.html

ดูหัวข้อ 3.4.5


3
หากคุณใช้ bash $( )ไวยากรณ์อาจเป็นที่ต้องการ 'หลักสูตร backticks ทำงานในเปลือกหอยมากขึ้น ...
dmckee --- อดีตผู้ดูแลลูกแมว

6
$ (command) และ $ ((1 + 1)) ทำงานในเชลล์ posix ทั้งหมด ฉันคิดว่าหลายคนถูกเลื่อนออกไปโดยการทำเครื่องหมายเป็นกลุ่มเป็นข้อผิดพลาดทางไวยากรณ์ แต่นั่นเป็นเพียงเพราะ vim กำลังไฮไลต์สำหรับ Bourne เชลล์ดั้งเดิมซึ่งใช้น้อยมาก หากต้องการให้กลุ่มไฮไลต์ถูกต้องให้ใส่สิ่งนี้ใน. vimrc ของคุณ: ให้ g: is_posix = 1
pixelbeat

1

ในการใช้โซลูชันของ mark4o บน bash 3.2 (macos) คุณสามารถใช้สตริง here แทน pipelines ดังตัวอย่างนี้:

. /dev/stdin <<< "$(grep '^alias' ~/.profile)"

หรือใช้ backticks:. / dev / stdin <<< `grep '^ alias' ~ / .profile`
William H. Hooper

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