เรียกใช้เชลล์ย่อย bash เชิงโต้ตอบด้วยคำสั่งเริ่มต้นโดยไม่ต้องกลับไปที่เปลือก (“ super”) ทันที


86

ฉันต้องการเรียกใช้คำสั่งย่อย bash (1) เรียกใช้คำสั่งไม่กี่คำ (2) จากนั้นยังคงอยู่ใน subshell นั้นเพื่อทำตามที่ฉันต้องการ ฉันสามารถทำสิ่งเหล่านี้เป็นรายบุคคล:

  1. เรียกใช้คำสั่งโดยใช้-cแฟล็ก:

    $> bash -c "ls; pwd; <other commands...>"

    อย่างไรก็ตามมันจะกลับสู่เชลล์ "super" ทันทีหลังจากเรียกใช้งานคำสั่ง ฉันยังสามารถเรียกใช้ subshell แบบโต้ตอบ:

  2. เริ่มbashกระบวนการใหม่:

    $> bash

    และมันจะไม่ออกจาก subshell จนกว่าฉันจะพูดอย่างชัดเจน ... แต่ฉันไม่สามารถเรียกใช้คำสั่งเริ่มต้นใด ๆ ทางออกที่ใกล้ที่สุดที่ฉันพบคือ:

    $> bash -c "ls; pwd; <other commands>; exec bash"

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

ฉันต้องการทำสิ่งนี้ในบรรทัดเดียว เมื่อฉันออกจาก subshell ฉันควรกลับไปที่เปลือก "super" ปกติโดยไม่เกิดปัญหา ต้องมีวิธี ~~

NB: สิ่งที่ฉันไม่ได้ขอ ...

  1. ไม่ถามว่าจะรับหน้า bash man ได้ที่ไหน
  2. ไม่ถามว่าจะอ่านคำสั่งเริ่มต้นจากไฟล์ได้อย่างไร ... ฉันรู้วิธีการทำสิ่งนี้ไม่ใช่วิธีการแก้ปัญหาที่ฉันต้องการ
  3. ไม่สนใจที่จะใช้หน้าจอ tmux หรือ gnu
  4. ไม่สนใจที่จะให้บริบทกับเรื่องนี้ เช่นคำถามนี้ตั้งใจให้เป็นเรื่องทั่วไปไม่ใช่เพื่อวัตถุประสงค์เฉพาะใด ๆ
  5. ถ้าเป็นไปได้ฉันต้องการหลีกเลี่ยงการใช้วิธีการแก้ปัญหาที่ประสบความสำเร็จตามที่ฉันต้องการ แต่ด้วยวิธี "สกปรก" ฉันแค่ต้องการทำสิ่งนี้ในบรรทัดเดียว โดยเฉพาะฉันไม่ต้องการทำอะไรxterm -e 'ls'

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

@glennjackman ขอโทษฉันไม่คุ้นเคยกับศัพท์แสง "วิธีแก้ปัญหาที่คาดหวัง" คืออะไร? นอกจากนี้exec bashวิธีแก้ปัญหาเกี่ยวข้องกับสอง subshells แยก ฉันต้องการ subshell หนึ่งอัน
SABBATINI Luca

3
ความสวยงามของexecมันคือการแทนที่ subshell แรกด้วยอันที่สองดังนั้นคุณเหลือเพียง 1 shell ที่อยู่ใต้แม่ หากคำสั่งเริ่มต้นของคุณตั้งค่าตัวแปรสภาพแวดล้อมพวกเขาจะมีอยู่ในเปลือก exec'ed
เกล็นแจ็คแมน

2
เป็นไปได้เช่นเดียวกันstackoverflow.com/questions/7120426/ …
Ciro Santilli 事件改造中心中心法轮功六四事件

2
และปัญหาที่เกิดขึ้นexecคือคุณสูญเสียสิ่งใดก็ตามที่ไม่ได้ถูกส่งผ่านไปยัง subshells ผ่านทางสภาพแวดล้อมเช่นตัวแปรที่ไม่ได้ถูกส่งออกฟังก์ชั่นนามแฝง ...
Curt J. Sampson

คำตอบ:


77

สามารถทำได้อย่างง่ายดายด้วยการตั้งชื่อไปป์ชั่วคราว :

bash --init-file <(echo "ls; pwd")

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


9
สิ่งนี้น่าจะหมายความ$HOME/.bashrcว่าไม่ได้ถูกดำเนินการ มันจะต้องมีการรวมจากท่อชื่อชั่วคราว
Hubro

9
เพื่อชี้แจงบางสิ่งเช่นนี้:bash --init-file <(echo ". \"$HOME/.bashrc\"; ls; pwd")
Hubro

5
นี่มันแย่มาก แต่ก็ใช้งานได้ ฉันไม่อยากจะเชื่อว่า bash ไม่รองรับสิ่งนี้โดยตรง
Pat Niemeyer

2
@Gus ที่.เป็นคำพ้องสำหรับsourceคำสั่ง: ss64.com/bash/source.html
Jonathan Potter

1
มีวิธีที่จะทำให้มันทำงานกับการสลับผู้ใช้เช่นsudo bash --init-file <(echo "ls; pwd")หรือsudo -iu username bash --init-file <(echo "ls; pwd")ไม่?
jeremysprofile

10

คุณสามารถทำได้ในลักษณะวงเวียนด้วยไฟล์ temp แม้ว่ามันจะใช้สองบรรทัด:

echo "ls; pwd" > initfile
bash --init-file initfile

สำหรับเอฟเฟกต์ที่ดีคุณสามารถสร้างไฟล์ชั่วคราวได้ด้วยการรวมไฟล์ไว้rm $BASH_SOURCEในนั้น
Eduardo Ivanec

Eduardo ขอบคุณ นั่นเป็นวิธีแก้ปัญหาที่ดี แต่ ... คุณกำลังบอกว่าไม่สามารถทำสิ่งนี้ได้โดยไม่ต้องทำเรื่องไฟล์ I / O มีเหตุผลที่ชัดเจนว่าทำไมฉันต้องการเก็บไว้เป็นคำสั่งในตัวเองเพราะช่วงเวลาที่ไฟล์เข้ามาผสมฉันจะต้องเริ่มกังวลเกี่ยวกับวิธีการสร้างไฟล์ชั่วคราวแบบสุ่มแล้วตามที่คุณพูดถึงการลบพวกเขา มันต้องใช้ความพยายามมากขึ้นด้วยวิธีนี้ถ้าฉันต้องการที่จะเข้มงวด ดังนั้นความปรารถนาในการแก้ปัญหาที่เรียบง่ายและสง่างาม
SABBATINI Luca

1
@SABBATINILuca: ฉันไม่ได้พูดอะไรแบบนั้น นี่เป็นเพียงวิธีหนึ่งและmktempจะแก้ปัญหาไฟล์ชั่วคราวตามที่ @cjc ชี้ให้เห็น Bash สามารถรองรับการอ่านคำสั่ง init จาก stdin แต่เท่าที่ฉันสามารถบอกได้ Specyfing -ในฐานะไฟล์ init และไพพ์ทำงานครึ่งหนึ่ง แต่ Bash ออกแล้ว (อาจเป็นเพราะตรวจพบPip ) IMHO คือทางออกที่หรูหรา
Eduardo Ivanec

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

11
นี่เป็นคำถามเก่า แต่ Bash สามารถสร้างไปป์ชั่วคราวที่มีชื่อโดยใช้ไวยากรณ์ต่อไปนี้: bash --init-file <(echo "ls; pwd")
Lie Ryan

5

ลองใช้สิ่งนี้แทน:

$> bash -c "ls;pwd;other commands;$SHELL"

$SHELLexitมันทำให้เปลือกเปิดในโหมดโต้ตอบรอให้ใกล้ชิดกับ


9
FYI นี่เป็นการเปิดเชลล์ใหม่หลังจากนั้นดังนั้นหากคำสั่งใด ๆ มีผลกระทบต่อสถานะของเชลล์ปัจจุบัน (เช่นการจัดหาไฟล์) มันอาจไม่ทำงานตามที่คาดไว้
ThiefMaster

3

"วิธีแก้ปัญหาที่คาดหวัง" ฉันหมายถึงคือการเขียนโปรแกรมเปลือกทุบตีกับภาษาการเขียนโปรแกรมคาดหวัง :

#!/usr/bin/env expect
set init_commands [lindex $argv 0]
set bash_prompt {\$ $}              ;# adjust to suit your own prompt
spawn bash
expect -re $bash_prompt {send -- "$init_commands\r"}
interact
puts "exiting subshell"

คุณต้องการเรียกใช้เช่น: ./subshell.exp "ls; pwd"


ฉันเดาว่านี่จะมีประโยชน์ในการลงทะเบียนคำสั่งในประวัติศาสตร์ด้วยฉันผิดหรือเปล่า? นอกจากนี้ฉันอยากรู้ว่า bashrc / โปรไฟล์ถูกดำเนินการในกรณีนี้หรือไม่?
muhuk

ยืนยันว่าสิ่งนี้ช่วยให้คุณสามารถใส่คำสั่งในประวัติศาสตร์ซึ่งโซลูชันอื่นไม่ได้ นี่เป็นวิธีที่ยอดเยี่ยมสำหรับการเริ่มต้นกระบวนการในไฟล์. screenrc - หากคุณออกจากกระบวนการที่เริ่มต้นคุณจะไม่ปิดหน้าต่างหน้าจอ
Dan Sandberg

1

ทำไมไม่ใช้ subshells ดั้งเดิม

$ ( ls; pwd; exec $BASH; )
bar     foo     howdy
/tmp/hello/
bash-4.4$ 
bash-4.4$ exit
$

การล้อมรอบคำสั่งที่มีวงเล็บทำให้ bash วางไข่เป็น subprocess เพื่อรันคำสั่งเหล่านี้ดังนั้นคุณสามารถเปลี่ยนแปลงสภาพแวดล้อมโดยไม่กระทบเปลือกแม่ bash -c "ls; pwd; exec $BASH"นี้จะเทียบเท่าโดยทั่วไปสามารถอ่านได้มากขึ้น

หากยังคงดู verbose มีสองตัวเลือก หนึ่งคือการมีตัวอย่างนี้เป็นฟังก์ชั่น:

$ run() { ( eval "$@"; exec $BASH; ) }
$ run 'ls; pwd;'
bar     foo     howdy
/tmp/hello/
bash-4.4$ exit
$ run 'ls;' 'pwd;'
bar     foo     howdy
/tmp/hello/
bash-4.4$ exit
$

อีกวิธีคือทำให้exec $BASHสั้นลง:

$ R() { exec $BASH; }
$ ( ls; pwd; R )
bar     foo     howdy
/tmp/hello/
bash-4.4$ exit
$

ฉันชอบRแนวทางมากกว่านี้เพราะไม่จำเป็นต้องเล่นกับการหลบหนีสตริง


1
ฉันไม่แน่ใจว่ามี caveeats ใด ๆ ที่ใช้ exec สำหรับสถานการณ์ที่ OP มีอยู่ในใจหรือไม่ แต่สำหรับฉันนี่เป็นทางออกที่ดีที่สุดที่เสนอเพราะใช้คำสั่ง bash ธรรมดาโดยไม่มีปัญหาใด ๆ
ปีเตอร์

1

หากsudo -E bashใช้งานไม่ได้ฉันใช้สิ่งต่อไปนี้ซึ่งตรงตามความคาดหวังของฉัน:

sudo HOME=$HOME bash --rcfile $HOME/.bashrc

ฉันตั้งค่าหน้าแรก = $ HOME เพราะฉันต้องการให้เซสชันใหม่ของฉันตั้งค่าหน้าแรกเป็นหน้าแรกของผู้ใช้แทนที่จะเป็นหน้าแรกของรูทซึ่งเกิดขึ้นเป็นค่าเริ่มต้นในบางระบบ


0

สง่างามน้อยกว่า--init-fileแต่อาจใช้ประโยชน์ได้มากกว่า:

fn(){
    echo 'hello from exported function'
}

while read -a commands
do
    eval ${commands[@]}
done
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.