Execute um subshell bash interativo com comandos iniciais sem retornar ao shell ("super") imediatamente

87

Eu quero executar um subshell bash, (1) executar alguns comandos, (2) e permanecer nesse subshell para fazer o que eu quiser. Eu posso fazer cada um destes individualmente:

  1. Execute o comando usando o -csinalizador:

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

    no entanto, ele retorna imediatamente ao shell "super" após a execução dos comandos. Também posso apenas executar um subshell interativo:

  2. Iniciar novo bashprocesso:

    $> bash

    e não sairá do subshell até que eu o diga explicitamente ... mas não consigo executar nenhum comando inicial. A solução mais próxima que encontrei é:

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

    que funciona, mas não da maneira que eu queria, pois executa os comandos fornecidos em um subshell e, em seguida, abre um separado para interação.

Eu quero fazer isso em uma única linha. Depois de sair do subshell, devo retornar ao shell "super" comum sem incidentes. Deve haver uma maneira ~~

NB: O que não estou perguntando ...

  1. não perguntando onde obter a página de manual do bash
  2. sem perguntar como ler comandos de inicialização de um arquivo ... Eu sei como fazer isso, não é a solução que estou procurando
  3. não estou interessado em usar a tela tmux ou gnu
  4. não está interessado em dar contexto a isso. Ou seja, a questão é para ser geral, e não para qualquer finalidade específica
  5. se possível, quero evitar o uso de soluções alternativas que atinjam o que quero, mas de maneira "suja". Eu só quero fazer isso em uma única linha. Em particular, não quero fazer algo comoxterm -e 'ls'
SABBATINI Luca
fonte
Posso imaginar uma solução Expect, mas dificilmente é a única que você deseja. De que maneira a exec bashsolução é inadequada para você?
glenn jackman
@glennjackman desculpe, eu não estou familiarizado com o jargão. O que é uma "solução esperada"? Além disso, a exec bashsolução envolve dois subshells separados. Eu quero um subshell contínuo.
SABBATINI Luca
3
A vantagem execé que ele substitui o primeiro subshell pelo segundo, então você fica apenas 1 shell abaixo do pai. Se seus comandos de inicialização configurarem variáveis ​​de ambiente, elas existirão no shell executado.
glenn jackman
2
possível mesmo stackoverflow.com/questions/7120426/…
Ciro Santilli #: 22316
2
E o problema com execé que você perde nada que não seja passada para subshells através do ambiente, tais como variáveis não exportado, funções, aliases ...
Curt J. Sampson

Respostas:

77

Isso pode ser feito facilmente com pipes nomeados temporários :

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

O crédito por esta resposta vai para o comentário de Lie Ryan . Achei isso realmente útil, e é menos perceptível nos comentários, então achei que deveria ser sua própria resposta.

Jonathan Potter
fonte
9
Presumivelmente, isso significa que $HOME/.bashrcnão é executado. Teria que ser incluído no pipe nomeado temporário.
Hubro 4/08
9
Para esclarecer, algo como isto:bash --init-file <(echo ". \"$HOME/.bashrc\"; ls; pwd")
Hubro
5
Isso é tão nojento, mas funciona. Não acredito que o bash não suporta isso diretamente.
Pat Niemeyer
2
@ Gus, .é um sinônimo para o sourcecomando: ss64.com/bash/source.html .
Jonathan Potter
11
Existe uma maneira de fazê-lo funcionar com a troca de usuário, como sudo bash --init-file <(echo "ls; pwd")ou sudo -iu username bash --init-file <(echo "ls; pwd")?
Jeremysprofile
10

Você pode fazer isso de maneira indireta com um arquivo temporário, embora isso leve duas linhas:

echo "ls; pwd" > initfile
bash --init-file initfile
Eduardo Ivanec
fonte
Para um efeito agradável, você pode fazer com que o arquivo temporário se remova incluindo rm $BASH_SOURCEnele.
Eduardo Ivanec
Eduardo obrigado. Essa é uma boa solução, mas ... você está dizendo que isso não pode ser feito sem ter que mexer com a E / S do arquivo. Há razões óbvias pelas quais eu preferiria manter isso como um comando independente, porque no momento em que os arquivos entram na mistura, terei que começar a me preocupar com a criação de arquivos temporários aleatórios e, como você mencionou, excluindo-os. Só requer muito mais esforço dessa maneira, se eu quero ser rigoroso. Daí o desejo de uma solução mais minimalista e elegante.
SABBATINI Luca
11
@SABBATINILuca: Não estou dizendo nada assim. Essa é apenas uma maneira e mktempresolve o problema do arquivo temporário, como o @cjc apontou. O Bash poderia suportar a leitura dos comandos init do stdin, mas até onde eu sei, não. Especificar -como arquivo init e canalizá- los pela metade funciona, mas o Bash sai (provavelmente porque detectou o pipeline). A solução elegante, IMHO, é usar exec.
Eduardo Ivanec
11
Isso também não substitui sua inicialização normal do bash? @SABBATINILuca, o que você está tentando alcançar com isso, por que você precisa gerar um shell para executar automaticamente alguns comandos e depois mantê-lo aberto?
user9517
11
Essa é uma pergunta antiga, mas o Bash pode criar pipes nomeados temporários usando a seguinte sintaxe: bash --init-file <(echo "ls; pwd").
Lie Ryan
5

Tente isso:

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

$SHELL Ele abre o shell no modo interativo, aguardando um encerramento com ele exit.

ExpoBi
fonte
9
FYI, isso abre um novo shell depois, por isso, se qualquer um dos comandos afetam o estado do shell atual (por exemplo, abastecimento de um arquivo) pode não funcionar como esperado
ThiefMaster
3

A "solução Expect" à qual me referi está programando um shell bash com a linguagem de programação 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"

Você executaria isso como: ./subshell.exp "ls; pwd"

Glenn Jackman
fonte
Acho que isso também teria a vantagem de registrar os comandos na história, estou errado? Também estou curioso se bashrc / profile é executado neste caso?
muhuk
Confirmado que isso permite que você coloque comandos no histórico, o que as outras soluções não. Isso é ótimo para iniciar um processo em um arquivo .screenrc - se você sair do processo iniciado, não fechará a janela da tela.
Dan Sandberg
1

Por que não usar subshells nativos?

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

A inclusão de comandos entre parênteses torna o bash spawn um subprocesso para executar esses comandos, para que você possa, por exemplo, alterar o ambiente sem afetar o shell pai. Isso é basicamente mais legível equivalente ao bash -c "ls; pwd; exec $BASH".

Se isso ainda parecer detalhado, há duas opções. Uma é ter esse trecho como função:

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

Outra é exec $BASHdiminuir:

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

Eu, pessoalmente, gosto de Rabordar mais, pois não há necessidade de brincar com cordas que escapam.

toriningen
fonte
11
Não tenho certeza se existem algumas dicas usando exec para o cenário que o OP tem em mente, mas para mim essa é a melhor solução proposta, porque ela usa comandos simples do bash sem problemas de escape de string.
Peter
1

Se sudo -E bashnão funcionar, use o seguinte, que atendeu às minhas expectativas até agora:

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

Defino HOME = $ HOME porque quero que minha nova sessão tenha HOME definida como HOME do usuário, em vez de HOME da raiz, o que acontece por padrão em alguns sistemas.

caralho
fonte
0

menos elegante do que --init-file, mas talvez mais instrumentável:

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

while read -a commands
do
    eval ${commands[@]}
done
RubyTuesdayDONO
fonte