Esegui una subshell bash interattiva con comandi iniziali senza tornare immediatamente alla shell ("super")


87

Voglio eseguire una subshell bash, (1) eseguire alcuni comandi, (2) e quindi rimanere in quella subshell per fare come mi pare. Posso fare ognuno di questi singolarmente:

  1. Esegui il comando utilizzando -cflag:

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

    tuttavia, ritorna immediatamente alla shell "super" dopo l'esecuzione dei comandi. Posso anche solo eseguire una subshell interattiva:

  2. Inizia un nuovo bashprocesso:

    $> bash

    e non uscirà dalla subshell fino a quando non lo dirò esplicitamente ... ma non posso eseguire alcun comando iniziale. La soluzione più vicina che ho trovato è:

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

    che funziona, ma non nel modo in cui volevo, poiché esegue i comandi forniti in una subshell e quindi ne apre una separata per l'interazione.

Voglio farlo su una sola riga. Una volta che esco dalla subshell, dovrei tornare alla normale shell "super" senza incidenti. Ci deve essere un modo ~~

NB: Quello che non sto chiedendo ...

  1. non chiedendo dove trovare la pagina man di bash
  2. non chiedendo come leggere i comandi di inizializzazione da un file ... So come fare, non è la soluzione che sto cercando
  3. non interessato ad usare lo schermo tmux o gnu
  4. non interessato a dare un contesto a questo. Vale a dire, la domanda è pensata per essere generale e non per uno scopo specifico
  5. se possibile, voglio evitare di usare soluzioni alternative che riescano a realizzare ciò che voglio, ma in modo "sporco". Voglio solo farlo su una sola riga. In particolare, non voglio fare qualcosa del generexterm -e 'ls'

Posso immaginare una soluzione Expect, ma non è certo la linea che desideri. In che modo la exec bashsoluzione non è adatta a te?
Glenn Jackman,

@glennjackman scusa, non ho familiarità con il gergo. Che cos'è una "soluzione attesa"? Inoltre, la exec bashsoluzione prevede due subshells separati. Voglio una subshell continua.
SABBATINI Luca

3
Il bello execè che sostituisce la prima subshell con la seconda, quindi rimani solo 1 shell sotto il genitore. Se i tuoi comandi di inizializzazione impostano le variabili di ambiente, saranno presenti nella shell eseguita.
Glenn Jackman,


2
E il problema execè che perdi tutto ciò che non viene trasmesso alle sottosche dall'ambiente, come variabili non esportate, funzioni, alias, ...
Curt J. Sampson,

Risposte:


78

Questo può essere fatto facilmente con pipe denominate temporanee :

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

Il merito di questa risposta va al commento di Lie Ryan . L'ho trovato davvero utile ed è meno evidente nei commenti, quindi ho pensato che dovesse essere la sua risposta.


9
Questo presumibilmente significa che $HOME/.bashrcnon viene eseguito però. Dovrebbe essere incluso dalla pipe denominata temporanea.
Hubro,

9
Per chiarire, qualcosa del genere:bash --init-file <(echo ". \"$HOME/.bashrc\"; ls; pwd")
Hubro

5
Questo è così disgustoso ma funziona. Non posso credere che Bash non lo supporti direttamente.
Pat Niemeyer,

2
@Gus, .è sinonimo del sourcecomando: ss64.com/bash/source.html .
Jonathan Potter,

1
C'è un modo per farlo funzionare con il cambio utente, come sudo bash --init-file <(echo "ls; pwd")o sudo -iu username bash --init-file <(echo "ls; pwd")?
jeremysprofile,

10

Puoi farlo in modo circolare con un file temporaneo, anche se ci vorranno due righe:

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

Per un buon effetto, puoi fare in modo che il file temporaneo rimuova se stesso includendolo rm $BASH_SOURCEin esso.
Eduardo Ivanec,

Eduardo, grazie. Questa è una buona soluzione, ma ... stai dicendo che questo non può essere fatto senza dover giocherellare con l'I / O dei file. Ci sono ovvie ragioni per cui preferirei mantenere questo come comando autonomo, poiché nel momento in cui i file entrano nel mix dovrò iniziare a preoccuparmi di come creare file temporanei casuali e quindi, come hai detto, eliminarli. Richiede solo molto più sforzo in questo modo se voglio essere rigoroso. Da qui il desiderio di una soluzione più minimalista ed elegante.
SABBATINI Luca

1
@SABBATINILuca: non sto dicendo niente del genere. Questo è solo un modo e mktemprisolve il problema del file temporaneo come sottolineato da @cjc. Bash potrebbe supportare la lettura dei comandi init da stdin, ma per quanto ne so non lo è. Specificare -come file init e convogliarli a metà funziona, ma poi Bash esce (probabilmente perché ha rilevato la pipeline). La soluzione elegante, IMHO, è usare exec.
Eduardo Ivanec,

1
Questo non sostituisce anche la tua normale inizializzazione bash? @SABBATINILuca cosa stai cercando di ottenere con questo perché hai bisogno di spawn una shell auto eseguire alcuni comandi e quindi tenere aperta quella shell?
user9517

11
Questa è una vecchia domanda, ma Bash può creare pipe denominate temporanee usando la sintassi seguente: bash --init-file <(echo "ls; pwd").
Lie Ryan,

5

Prova questo invece:

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

$SHELL Rende la shell aperta in modalità interattiva, in attesa di chiusura exit.


9
Cordiali saluti, in seguito apre una nuova shell, quindi se uno qualsiasi dei comandi influenza lo stato della shell corrente (ad es. Approvvigionamento di un file), potrebbe non funzionare come previsto
ThiefMaster

3

La "soluzione Expect" a cui mi riferivo è la programmazione di una shell bash con il linguaggio di programmazione Expect :

#!/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"

Lo faresti come: ./subshell.exp "ls; pwd"


Immagino che questo avrebbe il vantaggio di registrare anche i comandi nella cronologia, sbaglio? Sono anche curioso di sapere se bashrc / profile viene eseguito in questo caso?
Muhuk,

Confermato che ciò ti consente di inserire comandi nella cronologia, cosa che le altre soluzioni non fanno. Questo è ottimo per avviare un processo in un file .screenrc: se esci dal processo avviato, non chiudi la finestra dello schermo.
Dan Sandberg,

1

Perché non usare i subshells nativi?

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

Racchiudere i comandi tra parentesi rende bash spawn un sottoprocesso per eseguire questi comandi, quindi è possibile, ad esempio, modificare l'ambiente senza influire sulla shell padre. Questo è sostanzialmente più leggibile equivalente a bash -c "ls; pwd; exec $BASH".

Se sembra ancora dettagliato, ci sono due opzioni. Uno è di avere questo frammento come funzione:

$ 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
$

Un altro è quello di exec $BASHabbreviare:

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

Personalmente mi piace Ravvicinarmi di più, poiché non è necessario giocare con le stringhe di escape.


1
Non sono sicuro che ci siano avvertimenti sull'uso di exec per lo scenario che l'OP ha in mente, ma per me questa è la soluzione migliore proposta, perché usa semplici comandi bash senza problemi di escape della stringa.
Peter,

1

Se sudo -E bashnon funziona, utilizzo quanto segue, che finora ha soddisfatto le mie aspettative:

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

Ho impostato HOME = $ HOME perché voglio che la mia nuova sessione abbia HOME impostato sulla HOME del mio utente, piuttosto che sulla HOME di root, che avviene per impostazione predefinita su alcuni sistemi.


0

meno elegante di --init-file, ma forse più strumentabile:

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

while read -a commands
do
    eval ${commands[@]}
done
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.