Come invocare bash, eseguire comandi all'interno della nuova shell e quindi restituire il controllo all'utente?


86

Deve essere davvero semplice o molto complesso, ma non sono riuscito a trovare nulla al riguardo ... Sto cercando di aprire una nuova istanza di bash, quindi eseguire alcuni comandi al suo interno e restituire il controllo all'utente al suo interno stessa istanza .

Provai:

$ bash -lic "some_command"

ma questo viene eseguito some_commandall'interno della nuova istanza, quindi la chiude. Voglio che rimanga aperto.

Un altro dettaglio che potrebbe influenzare le risposte: se riesco a farlo funzionare, lo userò nel mio .bashrcas alias, quindi punti bonus per aliasun'implementazione!


Non capisco l'ultima parte sugli alias. Gli alias vengono eseguiti nel contesto della shell corrente, quindi non hanno il problema che stai chiedendo di aver risolto (sebbene di solito, le funzioni sono migliori degli alias, per una serie di ragioni).
tripleee

2
Voglio essere in grado di mettere qualcosa come alias shortcut='bash -lic "some_command"'nel mio .bashrc, quindi eseguire shortcute avere una nuova shell con some_commandgià eseguita.
Félix Saparelli

Risposte:


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

dispensa la creazione di file temporanei. Domanda su altri siti:


Ho provato a farlo in Windows 10 (cercando di scrivere un file / script di "avvio" batch) e ottengoThe system cannot find the file specified.
Doctor Blue

1
@Scott esegue il debug passo dopo passo: 1) Funziona cat ~/.bashrc? 2) Funziona cat <(echo a)? 3) Funziona bash --rcfile somefile?
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1
@Scott abbiamo appena riscontrato questo problema su WSL (avviando bash da un file batch di avvio). La nostra soluzione era quella di eseguire l'escape di alcuni caratteri in modo che batch potesse capirlo: bash.exe --rcfile ^<^(echo 'source $HOME/.bashrc; ls; pwd'^)dovrebbe essere eseguito dal prompt dei comandi di Windows.
user2561747

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


40

Questa è una risposta tardiva, ma ho avuto lo stesso identico problema e Google mi ha inviato a questa pagina, quindi per completezza ecco come ho aggirato il problema.

Per quanto ne so, bashnon ha la possibilità di fare ciò che il poster originale voleva fare. L' -copzione tornerà sempre dopo che i comandi sono stati eseguiti.

Soluzione non funzionante : il tentativo più semplice e ovvio in questo senso è:

bash -c 'XXXX ; bash'

Questo funziona in parte (anche se con un ulteriore strato secondario). Tuttavia, il problema è che mentre una sub-shell erediterà le variabili di ambiente esportate, gli alias e le funzioni non vengono ereditati. Quindi questo potrebbe funzionare per alcune cose ma non è una soluzione generale.

Migliore : il modo per aggirare questo è creare dinamicamente un file di avvio e chiamare bash con questo nuovo file di inizializzazione, assicurandosi che il nuovo file di inizializzazione chiami il normale ~/.bashrcse necessario.

# 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

La cosa bella è che il file init temporaneo si cancella da solo non appena viene utilizzato, riducendo il rischio che non venga pulito correttamente.

Nota: non sono sicuro che / etc / bashrc venga solitamente chiamato come parte di una normale shell non di login. In tal caso potresti voler generare / etc / bashrc oltre al tuo file ~/.bashrc.


La rm -f $TMPFILEcosa lo fa. In realtà non ricordo il caso d'uso che ho avuto per questo, e ora uso tecniche di automazione più avanzate, ma questa è la strada da percorrere!
Félix Saparelli

8
Nessun file tmp con sostituzione del processobash --rcfile <(echo ". ~/.bashrc; a=b")
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

3
Il file temporaneo verrà pulito solo se lo script Bash viene eseguito correttamente. Una soluzione più robusta sarebbe quella di utilizzare trap.
tripleee

5

Puoi passare --rcfilea Bash per fargli leggere un file a tua scelta. Questo file verrà letto al posto del tuo .bashrc. (Se questo è un problema, fonte ~/.bashrcdall'altro script.)

Modifica: quindi una funzione per avviare una nuova shell con le cose da ~/.more.shsarebbe simile a:

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

... e in .more.shvoi avreste i comandi che volete eseguire all'avvio della shell. (Suppongo che sarebbe elegante evitare un file di avvio separato: non è possibile utilizzare l'input standard perché in tal caso la shell non sarà interattiva, ma è possibile creare un file di avvio da un documento qui in una posizione temporanea, quindi leggerlo.)


3

Puoi ottenere la funzionalità che desideri procurandoti lo script invece di eseguirlo. per esempio:

$ cat script
cmd1
cmd2
$. script
$ a questo punto cmd1 e cmd2 sono stati eseguiti all'interno di questa shell

Hmm. Funziona, ma non è possibile incorporare tutto in un alias=''?
Félix Saparelli

1
Non utilizzare mai alias; usa invece le funzioni. Puoi mettere: 'foo () {cmd1; cmd2; } 'all'interno degli script di avvio, quindi l'esecuzione di foo eseguirà i due comandi nella shell corrente. Nota che nessuna di queste soluzioni ti dà una nuova shell, ma puoi fare 'exec bash' e poi 'foo'
William Pursell

1
Mai dire mai. gli alias hanno il loro posto
glenn jackman

C'è un esempio di un caso d'uso in cui una funzione non può essere utilizzata al posto di un alias?
William Pursell

3

Aggiungi a ~/.bashrcuna sezione come questa:

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

Quindi puoi avviare la subshell con sub.


Punti per l'originalità. L'alias stesso è probabilmente migliore come env subshell=true bash(poiché ciò non trapelerà $subshellai comandi successivi): utilizzo di env rispetto all'utilizzo di esportazione .
Félix Saparelli

Hai ragione. Ma ho notato che funziona anche senza env. Per controllare se è definito o meno, io uso echo $subshell(invece di env | grep subshellquello che usi).
dashohoxha

3

La risposta accettata è davvero utile! Solo per aggiungere che la sostituzione del processo (es. <(COMMAND)) Non è supportata in alcune shell (es dash.).

Nel mio caso, stavo cercando di creare un'azione personalizzata (fondamentalmente uno script di shell a una riga) nel file manager Thunar per avviare una shell e attivare l'ambiente virtuale Python selezionato. Il mio primo tentativo è stato:

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

dov'è %fil percorso per l'ambiente virtuale gestito da Thunar. Ho ricevuto un errore (eseguendo Thunar dalla riga di comando):

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

Poi ho capito che il mio sh(essenzialmente dash) non supporta la sostituzione del processo.

La mia soluzione è stata quella di invocare bashal livello più alto per interpretare la sostituzione del processo, a scapito di un livello aggiuntivo di shell:

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

In alternativa, ho provato a utilizzare here-document per dashma senza successo. Qualcosa di simile a:

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

PS: non ho abbastanza reputazione per pubblicare commenti, i moderatori non esitate a spostarlo nei commenti o rimuoverlo se non è utile con questa domanda.


Se uno non vuole bash in cima (o non lo ha), ci sono vari modi documentati per implementare direttamente la sostituzione del processo, principalmente con fifos.
Félix Saparelli

2

In accordo con la risposta di daveraja , ecco uno script bash che risolverà lo scopo.

Considera una situazione se stai usando C-shell e vuoi eseguire un comando senza lasciare il contesto / finestra della C-shell come segue,

Comando da eseguire : cerca la parola esatta "Testing" nella directory corrente in modo ricorsivo solo nei file * .h, * .c

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

Soluzione 1 : accedere a bash da C-shell ed eseguire il comando

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

Soluzione 2 : scrivi il comando desiderato in un file di testo ed eseguilo usando bash

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

Soluzione 3 : eseguire il comando sulla stessa riga utilizzando bash

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

Soluzione 4 : crea uno sciprt (una tantum) e usalo per tutti i comandi futuri

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

Lo script è il seguente,

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

2
Questa è una risposta completamente diversa per un problema completamente diverso. (È bello che tu l'abbia capito, ma non appartiene a questo thread. Se questo non esiste su questo sito, considera di aprire una nuova domanda con la tua seconda frase, quindi rispondi immediatamente tu stesso con il resto ... è così che dovrebbe essere gestito ☺) Vedi anche la risposta di Ciro Santilli per un modo per farlo senza costruire un file temporaneo.
Félix Saparelli

Contrassegnato come non risposta, in quanto non è un tentativo di rispondere a questa domanda (anche se è davvero un ottimo tentativo di rispondere a una diversa!)
Charles Duffy

0
$ bash --init-file <(echo 'some_command')
$ bash --rcfile <(echo 'some_command')

Nel caso in cui non puoi o non vuoi usare la sostituzione del processo:

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

Un altro modo:

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

sh-solo modo ( dash, busybox):

$ ENV=script sh

Buone alternative, ma trarrebbe vantaggio dall'avere la sezione superiore (prima della ENV=scriptparte) rimossa o riformulata per evitare di essere confusa con un palese copia-incolla della risposta principale.
Félix Saparelli

@ FélixSaparelli Ad essere sinceri tutte le soluzioni tranne quella ENV+ shsono presenti nelle altre risposte, ma per lo più sepolte in tonnellate di testo o circondate da codice non necessario / non essenziale. Credo che avere un riassunto breve e chiaro sarebbe di beneficio a tutti.
x-yuri

0

Ecco un'altra variante (funzionante):

Questo apre un nuovo terminale gnome, quindi nel nuovo terminale viene eseguito bash. Il file rc dell'utente viene letto per primo, quindi ls -laviene inviato un comando per l'esecuzione alla nuova shell prima che diventi interattivo. L'ultima eco aggiunge una nuova riga in più necessaria per terminare l'esecuzione.

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

Trovo anche utile a volte decorare il terminale, ad esempio con colori per un migliore orientamento.

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

-1

Esecuzione di comandi in una shell in background

Basta aggiungere &alla fine del comando, ad esempio:

bash -c some_command && another_command &
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.