it-swarm.it

Cattura automaticamente l'output dell'ultimo comando in una variabile usando Bash?

Vorrei poter utilizzare il risultato dell'ultimo comando eseguito in un comando successivo. Per esempio,

$ find . -name foo.txt
./home/user/some/directory/foo.txt

Ora diciamo che voglio essere in grado di aprire il file in un editor, o eliminarlo o fare qualcos'altro con esso, ad es.

mv <some-variable-that-contains-the-result> /some/new/location

Come posso farlo? Forse usando qualche variabile bash?

Aggiornamento:

Per chiarire, non voglio assegnare le cose manualmente. Quello che sto cercando è qualcosa come le variabili bash integrate, ad es.

ls /tmp
cd $_

$_ contiene l'ultimo argomento del comando precedente. Voglio qualcosa di simile, ma con l'output dell'ultimo comando.

Aggiornamento finale:

La risposta di Seth ha funzionato abbastanza bene. Un paio di cose da tenere a mente:

  • non dimenticare di touch /tmp/x quando provi la soluzione per la prima volta
  • il risultato verrà memorizzato solo se il codice di uscita dell'ultimo comando ha avuto esito positivo
134
armandino

Questa è una soluzione davvero confusa, ma sembra funzionare per lo più qualche volta. Durante i test, ho notato che a volte non funzionava molto bene quando si riceveva un ^C dalla riga di comando, anche se l'ho modificato un po 'per comportarmi un po' meglio.

Questo hack è solo un hack in modalità interattiva e sono abbastanza sicuro che non lo consiglierei a nessuno. È probabile che i comandi in background causino comportamenti ancora meno definiti del normale. Le altre risposte sono un modo migliore per ottenere programmaticamente i risultati.


Detto questo, ecco la "soluzione":

Prompt_COMMAND='LAST="`cat /tmp/x`"; exec >/dev/tty; exec > >(tee /tmp/x)'

Impostare questa variabile ambientale bash ed emettere i comandi come desiderato. $LAST di solito avrà l'output che stai cercando:

startide seth> fortune
Courtship to marriage, as a very witty prologue to a very dull play.
                -- William Congreve
startide seth> echo "$LAST"
Courtship to marriage, as a very witty prologue to a very dull play.
                -- William Congreve
70
Seth Robertson

Non conosco alcuna variabile che lo faccia automaticamente. Per fare qualcosa a parte semplicemente incollare il risultato, puoi rieseguire tutto ciò che hai appena fatto, ad es

vim $(!!)

Dove !! È l'espansione della cronologia che significa "il comando precedente".

Se ti aspetti che ci sia un singolo nome file con spazi o altri caratteri in esso che potrebbero impedire l'analisi corretta dell'argomento, cita il risultato (vim "$(!!)"). Se non viene quotato, sarà possibile aprire contemporaneamente più file purché non includano spazi o altri token di analisi Shell.

87
Daenyth

Bash è una specie di brutto linguaggio. Sì, è possibile assegnare l'output alla variabile

MY_VAR="$(find -name foo.txt)"
echo "$MY_VAR"

Ma è meglio sperare più duramente che find abbia restituito un solo risultato e che quel risultato non contenga caratteri "dispari", come ritorni a capo o avanzamenti di riga, poiché verranno silenziosamente modificati quando assegnati a un Bash variabile.

Ma fai attenzione a citare correttamente la tua variabile quando la usi!

È meglio agire direttamente sul file, ad es. con find 's -execdir (consultare il manuale).

find -name foo.txt -execdir vim '{}' ';'

o

find -name foo.txt -execdir rename 's/\.txt$/.xml/' '{}' ';'
20
rlibby

Esistono più modi per farlo. Un modo è usare v=$(command) che assegnerà l'output del comando a v. Per esempio:

v=$(date)
echo $v

E puoi anche usare i backquotes.

v=`date`
echo $v

Da Bash Beginners Guide ,

Quando viene utilizzata la forma di sostituzione retroquadrata in vecchio stile, la barra rovesciata conserva il suo significato letterale tranne quando è seguita da "$", "` "o"\". I primi backtick non preceduti da una barra rovesciata terminano la sostituzione del comando. Quando si utilizza il modulo "$ (COMANDO)", tutti i caratteri tra parentesi compongono il comando; nessuno è trattato in modo speciale.

EDIT: Dopo la modifica della domanda, sembra che questa non sia la cosa che l'OP sta cercando. Per quanto ne so, non esiste una variabile speciale come $_ per l'output dell'ultimo comando.

12
taskinoor

È abbastanza facile Usa le virgolette:

var=`find . -name foo.txt`

E poi puoi usarlo in qualsiasi momento in futuro

echo $var
mv $var /somewhere
10
Wes Hardaker

Penso che potresti essere in grado di hackerare una soluzione che comporta l'impostazione di Shell su uno script contenente:

#!/bin/sh
bash | tee /var/log/bash.out.log

Quindi se imposti $Prompt_COMMAND per generare un delimitatore, puoi scrivere una funzione di supporto (forse chiamata _) che ti dà l'ultimo pezzo di quel registro, quindi puoi usarlo come:

% find lots*of*files
...
% echo "$(_)"
... # same output, but doesn't run the command again
9
Jay Adkisson

Disclamers:

  • Questa risposta è in ritardo di sei mesi: D
  • Sono un utente tmux pesante
  • Devi far funzionare Shell in tmux affinché funzioni

Quando si esegue una shell interattiva in tmux, è possibile accedere facilmente ai dati attualmente visualizzati su un terminale. Diamo un'occhiata ad alcuni comandi interessanti:

  • tmux capture-pane: questo copia i dati visualizzati su uno dei buffer interni di tmux. Può copiare la cronologia che al momento non è visibile, ma non ci interessa ora
  • tmux list-buffers: visualizza le informazioni sui buffer acquisiti. Il più recente avrà il numero 0.
  • tmux show-buffer -b (buffer num): stampa il contenuto del buffer dato su un terminale
  • tmux paste-buffer -b (buffer num): questo incolla il contenuto del buffer dato come input

Sì, questo ci offre molte possibilità ora :) Per quanto mi riguarda, ho creato un semplice alias: alias L="tmux capture-pane; tmux showb -b 0 | tail -n 3 | head -n 1" E ora ogni volta che devo accedere all'ultima riga uso semplicemente $(L) capirlo.

Questo è indipendente dal flusso di output utilizzato dal programma (sia esso stdin o stderr), il metodo di stampa (ncurses, ecc.) E il codice di uscita del programma - i dati devono solo essere visualizzati.

8
Wiesław Herr

È possibile impostare il seguente alias nel profilo bash:

alias s='it=$($(history | tail -2 | head -1 | cut -d" " -f4-))'

Quindi, digitando 's' dopo un comando arbitrario, è possibile salvare il risultato in una variabile Shell 'it'.

Quindi un esempio di utilizzo sarebbe:

$ which python
/usr/bin/python
$ s
$ file $it
/usr/bin/python: symbolic link to `python2.6'
7
Joe Tallett

Ho appena distillato questa funzione bash dai suggerimenti qui:

grab() {     
  grab=$("[email protected]")
  echo $grab
}

Quindi, fai semplicemente:

> grab date
Do 16. Feb 13:05:04 CET 2012
> echo $grab
Do 16. Feb 13:05:04 CET 2012

Aggiornamento: un utente anonimo ha suggerito di sostituire echo con printf '%s\n' che ha il vantaggio di non elaborare opzioni come -e nel testo acquisito. Quindi, se ti aspetti o provi tali peculiarità, considera questo suggerimento. Un'altra opzione è utilizzare cat <<<$grab anziché.

6
Tilman Vogel

Cattura l'output con i backtick:

output=`program arguments`
echo $output
emacs $output
5
bandi

Dicendo "Mi piacerebbe poter usare il risultato dell'ultimo comando eseguito in un comando successivo", Presumo - intendi il risultato di qualsiasi comando, non solo di trovare.

In questo caso - xargs è quello che stai cercando.

find . -name foo.txt -print0 | xargs -0 -I{} mv {} /some/new/location/{}

O se sei interessato a vedere prima l'output:

find . -name foo.txt -print0

!! | xargs -0 -I{} mv {} /some/new/location/{}

Questo comando si occupa di più file e funziona come un incantesimo anche se il percorso e/o il nome file contiene spazi.

Notare la parte mv {}/some/new/location/{} del comando. Questo comando viene creato ed eseguito per ogni riga stampata dal comando precedente. Qui la riga stampata dal comando precedente viene sostituita al posto di {}.

Estratto dalla pagina man di xargs:

xargs - crea ed esegue le righe di comando dall'input standard

Per maggiori dettagli vedi la pagina man: man xargs

5
ssapkota

Di solito faccio quello che gli altri qui hanno suggerito ... senza il compito:

$find . -iname '*.cpp' -print
./foo.cpp
./bar.cpp
$vi `!!`
2 files to edit

Puoi diventare più elaborato se ti piace:

$grep -R "some variable" * | grep -v tags
./foo/bar/xxx
./bar/foo/yyy
$vi `!!`
4
halm

Se tutto ciò che vuoi è eseguire nuovamente il tuo ultimo comando e ottenere l'output, una semplice variabile bash funzionerebbe:

LAST=`!!`

Quindi puoi eseguire il tuo comando sull'output con:

yourCommand $LAST

Questo genererà un nuovo processo ed eseguirà nuovamente il tuo comando, quindi ti darà l'output. Sembra che quello che vorresti fosse un file di cronologia bash per l'output del comando. Questo significa che dovrai catturare l'output che bash invia al tuo terminale. Potresti scrivere qualcosa per guardare il/dev o/proc necessario, ma è disordinato. Puoi anche creare una "pipe speciale" tra il tuo termine e bash con un comando tee nel mezzo che reindirizza al tuo file di output.

Ma entrambi sono una sorta di soluzioni confuse. Penso che la cosa migliore sarebbe terminatore che è un terminale più moderno con registrazione dell'output. Basta controllare il file di registro per i risultati dell'ultimo comando. Una variabile bash simile a quanto sopra renderebbe questo ancora più semplice.

2
Spencer Rathbun

Ecco un modo per farlo dopo aver eseguito il comando e deciso di voler archiviare il risultato in una variabile:

$ find . -name foo.txt
./home/user/some/directory/foo.txt
$ OUTPUT=`!!`
$ echo $OUTPUT
./home/user/some/directory/foo.txt
$ mv $OUTPUT somewhere/else/

Oppure, se sai in anticipo che vorrai il risultato in una variabile, puoi usare i backtick:

$ OUTPUT=`find . -name foo.txt`
$ echo $OUTPUT
./home/user/some/directory/foo.txt
1
Nate W.

puoi usare !!: 1. Esempio:

~]$ ls *.~
class1.cpp~ class1.h~ main.cpp~ CMakeList.txt~ 

~]$ rm !!:1
rm class1.cpp~ class1.h~ main.cpp~ CMakeList.txt~ 


~]$ ls file_to_remove1 file_to_remove2
file_to_remove1 file_to_remove2

~]$ rm !!:1
rm file_to_remove1

~]$ rm !!:2
rm file_to_remove2
1
rkm

In alternativa alle risposte esistenti: utilizzare while se i nomi dei file possono contenere spazi vuoti come questo:

find . -name foo.txt | while IFS= read -r var; do
  echo "$var"
done

Come ho scritto, la differenza è rilevante solo se devi aspettarti spazi vuoti nei nomi dei file.

NB: l'unica roba incorporata non riguarda l'output ma lo stato dell'ultimo comando.

1
0xC0000022L

Avevo un bisogno simile, in cui volevo usare l'output dell'ultimo comando nel successivo. Proprio come un | (tubo). per esempio

$ which gradle 
/usr/bin/gradle
$ ls -alrt /usr/bin/gradle

a qualcosa del tipo -

$ which gradle |: ls -altr {}

Soluzione: creato questo tubo personalizzato. Davvero semplice, usando xargs -

$ alias :='xargs -I{}'

Fondamentalmente nulla creando una scorciatoia per xargs, funziona come un incantesimo ed è davvero utile. Ho appena aggiunto l'alias nel file .bash_profile.

1
eXc

Può essere fatto usando la magia dei descrittori di file e l'opzione lastpipe Shell.

Deve essere fatto con uno script: l'opzione "lastpipe" non funzionerà in modalità interattiva.

Ecco lo script con cui ho provato:

$ cat shorttest.sh 
#!/bin/bash
shopt -s lastpipe

exit_tests() {
    EXITMSG="$(cat /proc/self/fd/0)"
}

ls /bloop 2>&1 | exit_tests

echo "My output is \"$EXITMSG\""


$ bash shorttest.sh 
My output is "ls: cannot access '/bloop': No such file or directory"

Quello che sto facendo qui è:

  1. impostazione dell'opzione Shell shopt -s lastpipe. Non funzionerà senza questo poiché perderai il descrittore di file.

  2. assicurandomi che anche il mio stderr venga catturato con 2>&1

  3. reindirizzare l'output in una funzione in modo che sia possibile fare riferimento al descrittore del file stdin.

  4. impostando la variabile ottenendo il contenuto di /proc/self/fd/0 descrittore di file, che è stdin.

Sto usando questo per catturare errori in uno script, quindi se c'è un problema con un comando posso interrompere l'elaborazione dello script e uscire immediatamente.

shopt -s lastpipe

exit_tests() {
    MYSTUFF="$(cat /proc/self/fd/0)"
    BADLINE=$BASH_LINENO
}

error_msg () {
    echo -e "$0: line $BADLINE\n\t $MYSTUFF"
    exit 1
}

ls /bloop 2>&1 | exit_tests ; [[ "${PIPESTATUS[0]}" == "0" ]] || error_msg

In questo modo posso aggiungere 2>&1 | exit_tests ; [[ "${PIPESTATUS[0]}" == "0" ]] || error_msg dietro ogni comando che mi interessa verificare.

Ora puoi goderti il ​​tuo output!

1
montjoy
find . -name foo.txt 1> tmpfile && mv `cat tmpfile` /path/to/some/dir/

è ancora un altro modo, anche se sporco.

0
Kevin

Questa non è una soluzione bash ma è possibile utilizzare piping con sed per ottenere l'output dell'ultima riga dei comandi precedenti.

Per prima cosa vediamo cosa ho nella cartella "a"

[email protected]::~$ find a
a
a/foo
a/bar
a/bat
a/baz
[email protected]::~$ 

Quindi, il tuo esempio con ls e cd si trasformerebbe in sed & piping in qualcosa del genere:

[email protected]::~$ cd `find a |sed '$!d'`
[email protected]::~/a/baz$ pwd
/home/rasjani/a/baz
[email protected]::~/a/baz$

Quindi, la vera magia accade con sed, si inoltra qualsiasi output di qualsiasi comando in sed e sed stampa l'ultima riga che è possibile utilizzare come parametro con tick posteriori. Oppure puoi combinarlo anche con xargs. ("man xargs" in Shell è tuo amico)

0
rasjani

Shell non ha simboli speciali simili a Perl che memorizzano il risultato dell'eco dell'ultimo comando.

Impara a usare il simbolo della pipa con awk.

find . | awk '{ print "FILE:" $0 }'

Nell'esempio sopra puoi fare:

find . -name "foo.txt" | awk '{ print "mv "$0" ~/bar/" | "sh" }'
0
ascotan