Come posso eseguire l'ultima riga di output in bash?


12

So di poter eseguire l'ultimo comando in bash, con !!, ma come posso eseguire l'ultima riga di output?

Sto pensando al caso d'uso di questo output:

The program 'git' is currently not installed. You can install it by typing:
sudo apt-get install git

Ma non so come posso farcela. Sto pensando a qualcosa di simile !!, forse @@o simile?


Anche Super User ha questa domanda.


1
Perché non lo copi e incolli?
Pilota

1
@Tim Volete modi per eseguire l'ultima riga dell'output di un programma, ma per questo particolare caso d'uso, c'è anche l'approccio di cambiare la command_not_found_handlefunzione shell o uno script che esegue (di solito solo /usr/lib/command-not-found). Penso che potresti farcela in modo che ti venga richiesto in modo interattivo con l'opzione di installare automaticamente il pacchetto o (probabilmente meglio) in modo che il nome del pacchetto sia memorizzato da qualche parte e possa essere installato eseguendo un breve comando. È abbastanza diverso da quello che hai chiesto qui, potresti considerare di pubblicare una domanda separata al riguardo.
Eliah Kagan,


2
Potrebbe anche essere pertinente: github.com/nvbn/thefuck (Ignora il nome) questo strumento corregge automaticamente i comandi git / apt / etc :)
bhathiya-perera,

Risposte:


16

Il comando $(!! |& tail -1)dovrebbe fare:

$ git something
The program 'git' is currently not installed. You can install it by typing:
sudo apt-get install git

$ $(!! |& tail -1)
$(git something |& tail -1)
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following extra packages will be installed:
git-man liberror-perl

Come puoi vedere, il sudo apt-get install gitcomando è in esecuzione.

EDIT: abbattimento$(!! |& tail -1)

  • $()è il bashmodello di sostituzione dei comandi

  • bashsi espande !!fino all'ultimo comando eseguito

  • |&la parte è difficile. Normalmente pipe |prenderà lo STDOUT del comando sul lato sinistro e lo passerà come STDIN al comando sul lato destro di |, nel tuo caso il comando precedente stampa il suo output su STDERR come messaggio di errore. Quindi, fare |non aiuterebbe, dobbiamo passare sia STDOUT che STDERR (o solo STDERR) al comando del lato destro. |&passerà STDOUT e STDERR entrambi come STDIN al comando sul lato destro. In alternativa, meglio puoi solo passare lo STDERR:

    $(!! 2>&1 >/dev/null | tail -1)
  • tail -1come al solito stamperà l'ultima riga dal suo input. Qui invece di stampare l'ultima riga puoi essere più preciso come stampare la riga che contiene il apt-get installcomando:

    $(!! 2>&1 >/dev/null | grep 'apt-get install')

1
Il tuo approccio presuppone che l'output sarà identico la seconda volta. Alcuni comandi possono produrre un output diverso la volta successiva, nel qual caso è molto difficile prevedere che cosa farà effettivamente l'esecuzione dell'ultima riga del suo output.
Kasperd,

1
@kasperd Hai ragione..anche per quanto riguarda questo esempio specifico, l'output dovrebbe rimanere lo stesso ..
heemayl

7

TL; DR: alias @@='$($(fc -ln -1) |& tail -1)'

Le strutture di interazione della cronologia di Bash non offrono alcun meccanismo per esaminare l' output dei comandi. La shell non lo memorizza e l'espansione della cronologia è specifica per i comandi eseguiti dall'utente o parti di tali comandi.

Questo lascia l'approccio di rieseguire l'ultimo comando e reindirizzare sia stdout che stderr ( |&) in una sostituzione di comando. La risposta di heemayl raggiunge questo obiettivo , ma non può essere utilizzata in un alias perché la shell esegue l'espansione della cronologia prima di espandere gli alias e non dopo.

Non riesco nemmeno a far espandere l'espansione della cronologia in una funzione shell, anche abilitandola nella funzione con set -H. Ho il sospetto che !!una funzione non verrà mai espansa e non sono sicuro di cosa si espanderebbe se fosse, ma in questo momento non sono sicuro del perché .

Pertanto, se si desidera impostare le cose in modo da poterlo fare con pochissima digitazione, è necessario utilizzare l' fcintegrato della shell anziché l'espansione della cronologia per estrarre l'ultimo comando dalla cronologia. Questo ha l'ulteriore vantaggio che funziona anche quando l'espansione della cronologia è disabilitata.

Come mostrato nella Gordon Davisson 's risposta alla creazione di un alias che contengono l'espansione della storia bash (su Super User ), $(fc -ln -1)Simula !!. Collegandolo a for !!nel comando di heemayl si $(!! |& tail -1) ottiene:

$($(fc -ln -1) |& tail -1)

Funziona come, $(!! |& tail -1)ma può andare in una definizione alias:

alias @@='$($(fc -ln -1) |& tail -1)'

Dopo aver eseguito quella definizione o averla inserita .bash_aliaseso aver .bashrcavviato una nuova shell, è possibile semplicemente digitare @@(o come si chiama l'alias) per tentare di eseguire l'ultima riga di output dall'ultimo comando.

ek@Io:~$ alias @@='$($(fc -ln -1) |& tail -1)'
ek@Io:~$ evolution
The program 'evolution' is currently not installed. You can install it by typing:
sudo apt-get install evolution
ek@Io:~$ @@
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following extra packages will be installed:
  evolution-common evolution-data-server evolution-data-server-online-accounts
....

C'è qualche opzione per spostarlo in uno script separato? Ci ho provato e alla fine non ci sono riuscito, perché fc non genera nulla ... Dato che fc segue la cronologia della sessione di shell corrente, quindi spostandola in uno script separato si apre una sessione diversa lì con cronologia vuota, ovviamente ... ne ho aggiunti alcuni comandi sopra la riga fc nello script e uno di essi è stato eseguito. Quindi, mi chiedo se ci sia qualche opzione per dire allo script, che il suo "contesto" è la sessione bash, che lo ha eseguito. Ad esempio, alcuni argomenti per #! / Bin / bash o qualcosa del genere ...
Exterminator13

@ Exterminator13 Se vuoi dire uno script separato che si esegue --like ./script, non che si sorgente con la .o sourceincorporato - sto incerto. Bash aggiunge un file all'uscita della shell o quando si esegue historycon -ao -w(vedere help history). In tal caso, i comandi in più shell interattive verrebbero interlacciati (visualizzati nell'ordine in cui sono stati eseguiti). Puoi farlo aggiungere immediatamente. . Ma penso che ci sia altro da fare per fclavorare in una sceneggiatura. Suggerisco di pubblicare una nuova domanda a riguardo. Se lo fai, non esitare a commentare qui con un link ad esso.
Eliah Kagan,

2

Se stai usando un emulatore di terminale sotto X, come gnome-terminal, konsoleo xterm, puoi usare la selezione X per qualcosa come copia e incolla:

Usa la selezione principale

Seleziona l' intera riga da eseguire con un triplo clic sinistro .

Poi incollarlo alla riga di comando tramite un pulsante centrale scatto .

Questo dovrebbe funzionare nella maggior parte degli emulatori terminali. Utilizza la selezione principale, senza toccare gli Appunti: sono separati.
Sta selezionando e quindi combinando copia e incolla in una sola operazione, tecnicamente.


1
@Tim È vero - lo chiarirò.
Volker Siegel,

1

Come menzionato da Eliah Kagan , la shell non memorizza l'output del comando. Tuttavia, esiste un modo per ottenere l'ultima riga di output dell'ultimo comando, senza eseguire nuovamente il comando , se si esegue screen .

Metti in te le seguenti righe ~/.screenrc:

register t "^a[k0y$ ^a]^a^L"
bind t process t

Quindi è possibile premere Ctrl+ atper inserire la riga precedente nel prompt corrente. Sarebbe possibile farlo anche premendo automaticamente invio, ma è probabilmente una buona idea controllarlo prima di eseguire e basta premere invio manualmente.

Come funziona:

  • register tregistra una stringa in un buffer denominato t. La stringa che segue è una sequenza di tasti.
  • bind tsi lega alla chiave t(ovvero esegue con Ctrl+ at), process tsignifica valutare il contenuto del buffer denominato t.
  • La sequenza stessa significa:
    • ^a[(= Ctrla[): entra in modalità copia
    • kvai su una riga, 0vai all'inizio della riga, y$copia fino alla fine della riga, (spazio) completa il comando
    • ^a](= Ctrla]): incolla il contenuto copiato
    • ^a^L(= CtrlaCtrlL) aggiorna lo schermo (altrimenti il ​​contenuto incollato non verrà visualizzato, perché non lo hai digitato tu stesso
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.