Como invocar o bash, executar comandos dentro do novo shell e devolver o controle ao usuário?

86

Deve ser muito simples ou muito complexo, mas não consegui encontrar nada sobre isso ... Estou tentando abrir uma nova instância do bash, executar alguns comandos dentro dela e devolver o controle ao usuário dentro dela mesma instância .

Eu tentei:

$ bash -lic "some_command"

mas isso é executado some_commanddentro da nova instância e, em seguida, fecha-a. Eu quero que fique aberto.

Mais um detalhe que pode afetar as respostas: se eu conseguir fazer isso funcionar, vou usá-lo no .bashrc(s) meu (s) apelido (s), portanto, pontos de bônus para uma aliasimplementação!

Félix Saparelli
fonte
Não entendi a última parte sobre apelidos. Os aliases são executados no contexto do shell atual, portanto, não apresentam o problema que você está pedindo para resolver (embora normalmente as funções sejam melhores do que os aliases, por uma série de razões).
tripleee
2
Eu quero ser capaz de colocar algo como alias shortcut='bash -lic "some_command"'no meu .bashrc e executar shortcute ter um novo shell some_commandjá executado nele.
Félix Saparelli

Respostas:

63
bash --rcfile <(echo '. ~/.bashrc; some_command')

dispensa a criação de arquivos temporários. Pergunta em outros sites:

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fonte
Tentei fazer isso no Windows 10 (tentando escrever um arquivo / script de "inicialização" em lote) e receboThe system cannot find the file specified.
Doctor Blue
1
@Scott depura passo a passo: 1) Funciona cat ~/.bashrc? 2) Funciona cat <(echo a)? 3) Funciona bash --rcfile somefile?
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
@Scott acabamos de encontrar esse problema no WSL (iniciando o bash a partir de um arquivo em lote de inicialização). Nossa solução foi escapar alguns dos caracteres para que o batch pudesse entendê-lo: bash.exe --rcfile ^<^(echo 'source $HOME/.bashrc; ls; pwd'^)deve ser executado no prompt de comando do Windows.
user2561747
Existe uma maneira de fazer 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
39

Esta é uma resposta tardia, mas eu tive exatamente o mesmo problema e o Google me enviou para esta página, portanto, para completar, aqui está como eu resolvi o problema.

Pelo que eu posso dizer, bashnão tem a opção de fazer o que o autor do cartaz original queria fazer. A -copção sempre retornará após a execução dos comandos.

Solução quebrada : a tentativa mais simples e óbvia de contornar isso é:

bash -c 'XXXX ; bash'

Isso funciona parcialmente (embora com uma camada extra de sub-casca). No entanto, o problema é que, embora um sub-shell herde as variáveis ​​de ambiente exportadas, aliases e funções não são herdados. Portanto, isso pode funcionar para algumas coisas, mas não é uma solução geral.

Melhor : a maneira de contornar isso é criar dinamicamente um arquivo de inicialização e chamar o bash com esse novo arquivo de inicialização, certificando-se de que seu novo arquivo init chame o regular, ~/.bashrcse necessário.

# Create a temporary file
TMPFILE=$(mktemp)

# Add stuff to the temporary file
echo "source ~/.bashrc" > $TMPFILE
echo "<other commands>" >> $TMPFILE
echo "rm -f $TMPFILE" >> $TMPFILE

# Start the new bash shell 
bash --rcfile $TMPFILE

O bom é que o arquivo init temporário irá deletar a si mesmo assim que for usado, reduzindo o risco de não ser limpo corretamente.

Observação: não tenho certeza se / etc / bashrc geralmente é chamado como parte de um shell normal sem login. Nesse caso, você pode querer fonte de / etc / bashrc assim como seu ~/.bashrc.

Daveraja
fonte
A rm -f $TMPFILEcoisa faz isso. Na verdade, não me lembro do caso de uso que tive para isso e agora uso técnicas de automação mais avançadas, mas este é o caminho a seguir!
Félix Saparelli
7
Nenhum arquivo tmp com substituição de processobash --rcfile <(echo ". ~/.bashrc; a=b")
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
3
O arquivo temporário só será limpo se o script Bash for executado com êxito. Uma solução mais robusta seria usar trap.
tripleee
5

Você pode passar --rcfilepara o Bash para que ele leia um arquivo de sua escolha. Este arquivo será lido em vez do seu .bashrc. (Se isso for um problema, fonte ~/.bashrcdo outro script.)

Edit: Portanto, uma função para iniciar um novo shell com o material de ~/.more.shseria semelhante a:

more() { bash --rcfile ~/.more.sh ; }

... e .more.shvocê teria os comandos que deseja executar quando o shell iniciar. (Suponho que seria elegante evitar um arquivo de inicialização separado - você não pode usar a entrada padrão porque o shell não será interativo, mas você pode criar um arquivo de inicialização a partir de um documento aqui em um local temporário e depois lê-lo.)

triplo
fonte
3

Você pode obter a funcionalidade desejada fornecendo o script em vez de executá-lo. por exemplo:

script $ cat
cmd1
cmd2
$. roteiro
$ neste ponto cmd1 e cmd2 foram executados dentro deste shell
William Pursell
fonte
Hmm. Isso funciona, mas não há como incorporar tudo em um alias=''?
Félix Saparelli
1
Nunca use apelidos; em vez disso, use funções. Você pode colocar: 'foo () {cmd1; cmd2; } 'dentro de seus scripts de inicialização e, em seguida, executar foo executará os dois comandos no shell atual. Observe que nenhuma dessas soluções fornece um novo shell, mas você pode fazer 'exec bash' e 'foo'
William Pursell
1
Nunca diga nunca. aliases têm seu lugar
glenn jackman
Existe um exemplo de caso de uso em que uma função não pode ser usada em vez de um alias?
William Pursell
3

Anexar a ~/.bashrcuma seção como esta:

if [ "$subshell" = 'true' ]
then
    # commands to execute only on a subshell
    date
fi
alias sub='subshell=true bash'

Então você pode iniciar o subshell com sub.

dashohoxha
fonte
Pontos de originalidade. O alias em si é provavelmente melhor como env subshell=true bash(já que não vazará $subshellpara os comandos subsequentes): Usando env vs. usando export .
Félix Saparelli
Você está certo. Mas percebi que também funciona sem env. Para verificar se está definido ou não, eu uso echo $subshell(em vez env | grep subshelldaquele que você usa).
dashohoxha
3

A resposta aceita é muito útil! Apenas para adicionar que a substituição de processo (ou seja, <(COMMAND)) não é suportada em alguns shells (por exemplo, dash).

No meu caso, estava tentando criar uma ação personalizada (basicamente um script de shell de uma linha) no gerenciador de arquivos Thunar para iniciar um shell e ativar o ambiente virtual Python selecionado. Minha primeira tentativa foi:

urxvt -e bash --rcfile <(echo ". $HOME/.bashrc; . %f/bin/activate;")

onde %festá o caminho para o ambiente virtual manipulado por Thunar. Recebi um erro (executando Thunar na linha de comando):

/bin/sh: 1: Syntax error: "(" unexpected

Então percebi que meu sh(essencialmente dash) não suporta a substituição de processos.

Minha solução foi invocar bashno nível superior para interpretar a substituição do processo, à custa de um nível extra de shell:

bash -c 'urxvt -e bash --rcfile <(echo "source $HOME/.bashrc; source %f/bin/activate;")'

Como alternativa, tentei usar here-document, dashmas sem sucesso. Algo como:

echo -e " <<EOF\n. $HOME/.bashrc; . %f/bin/activate;\nEOF\n" | xargs -0 urxvt -e bash --rcfile

PS: Não tenho reputação suficiente para postar comentários, moderadores, fiquem à vontade para mover para comentários ou removê-los se não ajudar com esta questão.

Haimo
fonte
Se alguém não quiser o bash no topo (ou não o tiver), existem várias maneiras documentadas de implementar a substituição de processos diretamente, principalmente com fifos.
Félix Saparelli 02 de
2

De acordo com a resposta de daveraja , aqui está um script bash que resolverá o propósito.

Considere uma situação se você estiver usando o C-shell e quiser executar um comando sem sair do contexto / janela do C-shell da seguinte forma,

Comando a ser executado : Pesquise a palavra exata 'Teste' no diretório atual recursivamente apenas em arquivos * .h, * .c

grep -nrs --color -w --include="*.{h,c}" Testing ./

Solução 1 : entre no bash do C-shell e execute o comando

bash
grep -nrs --color -w --include="*.{h,c}" Testing ./
exit

Solução 2 : escreva o comando pretendido em um arquivo de texto e execute-o usando o bash

echo 'grep -nrs --color -w --include="*.{h,c}" Testing ./' > tmp_file.txt
bash tmp_file.txt

Solução 3 : execute o comando na mesma linha usando o bash

bash -c 'grep -nrs --color -w --include="*.{h,c}" Testing ./'

Solução 4 : crie um sciprt (uma única vez) e use-o para todos os comandos futuros

alias ebash './execute_command_on_bash.sh'
ebash grep -nrs --color -w --include="*.{h,c}" Testing ./

O script é o seguinte,

#!/bin/bash
# =========================================================================
# References:
# https://stackoverflow.com/a/13343457/5409274
# https://stackoverflow.com/a/26733366/5409274
# https://stackoverflow.com/a/2853811/5409274
# https://stackoverflow.com/a/2853811/5409274
# https://www.linuxquestions.org/questions/other-%2Anix-55/how-can-i-run-a-command-on-another-shell-without-changing-the-current-shell-794580/
# https://www.tldp.org/LDP/abs/html/internalvariables.html
# https://stackoverflow.com/a/4277753/5409274
# =========================================================================

# Enable following line to see the script commands
# getting printing along with their execution. This will help for debugging.
#set -o verbose

E_BADARGS=85

if [ ! -n "$1" ]
then
  echo "Usage: `basename $0` grep -nrs --color -w --include=\"*.{h,c}\" Testing ."
  echo "Usage: `basename $0` find . -name \"*.txt\""
  exit $E_BADARGS
fi  

# Create a temporary file
TMPFILE=$(mktemp)

# Add stuff to the temporary file
#echo "echo Hello World...." >> $TMPFILE

#initialize the variable that will contain the whole argument string
argList=""
#iterate on each argument
for arg in "$@"
do
  #if an argument contains a white space, enclose it in double quotes and append to the list
  #otherwise simply append the argument to the list
  if echo $arg | grep -q " "; then
   argList="$argList \"$arg\""
  else
   argList="$argList $arg"
  fi
done

#remove a possible trailing space at the beginning of the list
argList=$(echo $argList | sed 's/^ *//')

# Echoing the command to be executed to tmp file
echo "$argList" >> $TMPFILE

# Note: This should be your last command
# Important last command which deletes the tmp file
last_command="rm -f $TMPFILE"
echo "$last_command" >> $TMPFILE

#echo "---------------------------------------------"
#echo "TMPFILE is $TMPFILE as follows"
#cat $TMPFILE
#echo "---------------------------------------------"

check_for_last_line=$(tail -n 1 $TMPFILE | grep -o "$last_command")
#echo $check_for_last_line

#if tail -n 1 $TMPFILE | grep -o "$last_command"
if [ "$check_for_last_line" == "$last_command" ]
then
  #echo "Okay..."
  bash $TMPFILE
  exit 0
else
  echo "Something is wrong"
  echo "Last command in your tmp file should be removing itself"
  echo "Aborting the process"
  exit 1
fi
Rohan Ghige
fonte
2
Esta é uma resposta completamente diferente para um problema completamente diferente. (É legal que você tenha percebido isso, mas não pertence a este tópico. Se isso não existia neste site, considere abrir uma nova pergunta com sua segunda frase e, em seguida, respondê-la imediatamente com o resto ... é assim que isso deve ser tratado ☺) Veja também a resposta de Ciro Santilli para uma maneira de fazer isso sem criar um arquivo temporário.
Félix Saparelli
Não é uma resposta sinalizada, pois não é uma tentativa de responder a esta pergunta (embora seja uma ótima tentativa de responder a outra!)
Charles Duffy
0

Aqui está outra variante (funcional):

Isso abre um novo terminal gnome e, no novo terminal, executa o bash. O arquivo rc do usuário é lido primeiro e, em seguida, um comando ls -laé enviado para execução ao novo shell antes de se tornar interativo. O último eco adiciona uma nova linha extra que é necessária para terminar a execução.

gnome-terminal -- bash -c 'bash --rcfile <( cat ~/.bashrc; echo ls -la ; echo)'

Também acho útil às vezes decorar o terminal, por exemplo, com cores para melhor orientação.

gnome-terminal --profile green -- bash -c 'bash --rcfile <( cat ~/.bashrc; echo ls -la ; echo)'
user1656671
fonte
-1

Executar comandos em um shell de fundo

Basta adicionar &ao final do comando, por exemplo:

bash -c some_command && another_command &
Raphtlw
fonte
-1
$ bash --init-file <(echo 'some_command')
$ bash --rcfile <(echo 'some_command')

Caso você não possa ou não queira usar a substituição de processo:

$ cat script
some_command
$ bash --init-file script

Outra maneira:

$ bash -c 'some_command; exec bash'
$ sh -c 'some_command; exec sh'

sh-somente forma ( dash, busybox):

$ ENV=script sh
x-yuri
fonte
Boas alternativas, mas se beneficiariam se a seção superior (antes da ENV=scriptparte) fosse removida ou reformulada para evitar ser confundida com um copiador gritante da resposta superior.
Félix Saparelli
@FélixSaparelli Para ser franco, todas as soluções, exceto a ENV+, shestão presentes nas outras respostas, mas principalmente enterradas em toneladas de texto ou cercadas por código desnecessário / não essencial. Acredito que ter um resumo breve e claro seria benéfico para todos.
x-yuri