Wie führe ich Shell-Befehle im Hintergrund aus?

70

:!<command>kann verwendet werden, um einen Befehl in der Shell auszuführen. Aber dies "übernimmt" mein Terminal und füllt es mit stdoutdiesem speziellen Befehl.

Wie führe ich einen Befehl im Hintergrund aus, der mich nur bei einem Exit-Code ungleich Null benachrichtigt?

OrangeTux
quelle
1
Wären Sie bereit, die + Python-Oberfläche oder eine der anderen Sprachoberflächen zu verwenden?
Joeytwiddle
1
@joeytwiddle Ja
OrangeTux

Antworten:

52

:silent exec "!command"

Beachten Sie, dass Ihre vim-Sitzung weiterhin belegt ist, während Ihr Befehl ausgeführt wird. Dies liegt an der Synchronität von Vim. Sie können immer noch zu Ihrer Shell zurückkehren, indem Sie STRG + z drücken (um Vim in den Hintergrund zu verschieben) und dann Vim mit dem Befehl fgwie gewohnt fortsetzen.

Um Dinge asynchron zu erledigen, werfen Sie einen Blick auf Tim Popes Plugin vim-dispatch oder das Projekt NeoVim, das native Unterstützung für die asynchrone Befehlsausführung bietet und das Sie mit dem Plugin NeoMake problemlos nutzen können. Die neueste Version von Vim unterstützt auch asynchrone Aufgaben.

Siehe : h: leise

OliverUv
quelle
5
Dies ist auch das erste, was ich ausprobiert habe, als ich die Frage sah :-) Aber wenn Sie einen Befehl verwenden, der stdout (oder stderr) ausgibt, wird er Ihr Hauptfenster von Vim "überfrachten" (try :silent !ls) ... Es funktioniert auch nicht Bei einem Exit-Code ungleich 0 ist die Ausgabe nicht korrekt ... Ich fürchte, es ist ein bisschen :silent
komplizierter
Oh, Entschuldigung. Während Sie Ihren Kommentar verfassten, wurden einige Hinweise zur asynchronen Ausführung hinzugefügt. Ich weiß nicht, wie ich das Problem mit dem Beendigungsstatus beheben soll. Vielleicht möchten Sie in #vim auf Freenode versuchen.
OliverUv
Ich möchte auch beachten , dass weder :silent exec "!ls"noch :silent !lszeigen überhaupt keine Ausgabe auf meiner Version von Vim, 7.4 mit Patches 1-213.
OliverUv
3
Hm, hier ist, was ich danach bekomme :silent !ls: i.stack.imgur.com/1XvS6.png ... Ich muss drücken ^L, um es wieder zu beheben ...
Martin Tournoij
vim-dispatch sieht auf jeden Fall wie eine Lösung für das gegebene Problem aus.
Joeytwiddle
30

So führen Sie einen Befehl aus, ohne die Eingabe-Nachricht auszulösen:

Drücken Sie die EINGABETASTE oder geben Sie den Befehl ein, um fortzufahren

Versuchen Sie folgendes einfaches Beispiel:

:silent !echo Hello

Drücken Sie dann Ctrl+ L(oder :redraw!), um den Bildschirm zu aktualisieren, wenn Sie zu Vim zurückkehren.

Um eine Aktualisierung zu vermeiden, können Sie Ihren eigenen benutzerdefinierten Befehl definieren, z.

:command! -nargs=1 Silent execute ':silent !'.<q-args> | execute ':redraw!'

Jetzt können Sie mit dem neuen Vim-Befehl einen Shell-Befehl ausführen (Anmerkung: ohne ! Und mit Großbuchstaben S ):

:Silent ps

Quelle: Vermeiden der Eingabeaufforderung "Drücken Sie die EINGABETASTE, um fortzufahren" auf der Vim Wikia-Site

Kenorb
quelle
1
Wie werden Sie über einen Exit-Code ungleich Null benachrichtigt?
Martin Tournoij
1
Dieser Silent-Befehl ist keine Lösung für den Exit-Code ungleich Null, lässt jedoch zu, dass Befehle gerne :Silent ctags -R . > /dev/null &im Hintergrund ausgeführt werden. Durch das Senden von stdout an / dev / null wird verhindert, dass die Ausgabe im vim-Puffer angezeigt wird.
ftvs
10

Eine schnelle und schmutzige Lösung (in reinem Vimscript)

Dies kann einen Prozess im Hintergrund starten:

:!slow_command_here > /tmp/output 2>&1 &

Vim muss jedoch einen Weg finden, um herauszufinden, wann und wie der Prozess abgeschlossen ist. Verwenden wir also eine Markierungsdatei:

:!(rm -f /tmp/finished; slow_command_here > /tmp/output 2>&1; echo "$?" > /tmp/finished) &

Jetzt können wir Vim bitten, von Zeit zu Zeit zu überprüfen, ob der Vorgang abgeschlossen ist:

:augroup WaitForCompletion
:autocmd!
:autocmd CursorHold * if filereadable('/tmp/finished') | exec "augroup WaitForCompletion" | exec "au!" | exec "augroup END" | echo "Process completed with exit code ".readfile('/tmp/finished')[0] | end
:augroup END

Hey, es ist nicht schön, aber es funktioniert irgendwie!

Nachteile

Leider wird die obige automatische Bestätigung erst ausgelöst, wenn Sie den Cursor bewegen. Und wenn Sie mehr als einen Hintergrundprozess gleichzeitig ausführen möchten, müssen Sie diesen Dateien und dem Namen der autocmd-Gruppe eindeutige IDs hinzufügen.

Wenn Sie also mit einer zusätzlichen Abhängigkeit fertig werden, ist es möglicherweise besser, eine bewährte Lösung wie das in einer anderen Antwort erwähnte vim-dispatch-Plugin zu verwenden.

Ausgabe anzeigen

Wenn Sie die Ausgabe des Prozesses und nicht nur den Exit-Code sehen möchten , ersetzen Sie das echoobige Finale durch:

silent botright pedit /tmp/output

Das würde das Vorschaufenster öffnen. So verwenden Sie stattdessen die Quickfix-Fehlerliste:

silent cget /tmp/output | copen

Mit der Quickfix-Liste können Sie leicht zu Fehlern navigieren, indem Sie verwenden :cnext. copenBewegt jedoch den Cursor in das Quickfix-Fenster, wenn es geöffnet wird, was wahrscheinlich überraschend / ärgerlich sein wird.

(Eine Problemumgehung dafür wäre das Öffnen des QF-Fensters :copenbeim ersten Starten des Prozesses, sodass Sie es am Ende nicht aufrufen müssen.)

joeytwiddle
quelle
2
Dies ist die einzige Antwort hier, die tatsächlich die Frage beantwortet: "Führe einen Befehl im Hintergrund aus, der mich nur über einen Exit-Code ungleich Null benachrichtigt" ... Ich habe keine Ahnung, warum die anderen Antworten überhaupt Aufwertungen aufweisen ...
Martin Tournoij
2
Ich glaube, sie haben positive Stimmen, weil sie den Titel der Frage perfekt beantworten , was die Besucher auf diese Seite bringt. Msgstr "Wie werden Shell - Befehle still ausgeführt?" Ein häufiges SE-Phänomen! Die Besucher sind dankbar, aber vielleicht nicht diszipliniert.
Joeytwiddle
In einer idealen Welt hätten wir wahrscheinlich zwei getrennte Fragen: "Wie werden Shell-Befehle still ausgeführt?" und "Wie führe ich Shell-Befehle im Hintergrund aus?" damit googler finden, wonach sie suchen.
Joeytwiddle
6

Ausführen eines Befehls im Hintergrund

Ich habe einen Befehl ausgeführt, der für eine Weile blockiert war, und die Ausgabe hat mich nicht wirklich interessiert. Dies kann behoben werden, indem der Prozess gestartet wird, der nicht mit dem Terminal / Emulator, sondern mit dem System verbunden ist, und alle Ausgaben nach / dev / null umgeleitet werden. Zum Beispiel:

:silent exec "!(espeak 'speaking in background'&) > /dev/null"

Das (...&)läuft im Hintergrund und > /dev/nullleitet alle Ausgaben um /dev/null(nichts).

Die Klammer fängt die Ausgabe in einer Subshell (oder so ähnlich) ein, hat aber den Nebeneffekt, dass sie nicht an die aktuelle Shell angehängt wird (keine große Sache).

Ausführen eines Befehls im Hintergrund mit Zuordnung

Mir ist gerade klar geworden, dass Sie, wenn Sie es tatsächlich abbilden, so etwas tun können

nnoremap <silent> <leader>e :!$(espeak 'speaking in the background'&) > /dev/null

Das obige Fenster zeigt nichts in der Befehlsleiste an und zeigt außerdem nichts im Terminal außerhalb von vim an. Es wird zugeordnet <leader> e, was \ estandardmäßig der Fall ist .

Ausführen eines Befehls, Erfassen der Ausgabe und Anzeigen des Inhalts

(Ein weiterer Schnitt - und vielleicht der schönste). Dieser zeigt die Ausgabe eines Befehls an, wenn Sie dies auswählen:


IN EINEM NEUEN TAB:

silent exec "!(echo 'hello. I'm a process! :)') > /tmp/vim_process" | :tabedit /tmp/vim_process

INNERHALB EINES VERTIKALEN SPALTES:

silent exec "!(echo 'hello. I'm a process :)') > /tmp/vim_process" | :vs /tmp/vim_process

INNERHALB EINES HORIZONTALEN GETEILTEN:

silent exec "!(echo 'hello. I'm a process :)') > /tmp/vim_process" | :sp /tmp/vim_process

... mach was du willst

dylnmc
quelle
5

Wenn Ihnen der Exit-Code egal ist, können Sie wie folgt vorgehen:

:call system('/usr/bin/zathura using-docker.pdf &')
hsnotebook
quelle
1
Wenn Sie sich für den Exit-Code interessieren, v:shell_errorsagt Ihnen die Variable
Jerome Dalbert
4

Ich bin mir nicht sicher, ob dies Ihren Anforderungen entspricht (behandelt Hintergrundprozesse nicht wie in foo & - ich bin mir nicht sicher, ob dies mit "im Hintergrund" gemeint ist ), aber ein benutzerdefinierter Befehl kann wie folgt verwendet werden:

fun! s:PraeceptumTacet(cmd)
    silent let f = systemlist(a:cmd)
    if v:shell_error
        echohl Error
        echom "ERROR #" . v:shell_error
        echohl WarningMsg
        for e in f
            echom e
        endfor
        echohl None
    endif
endfun

command! -nargs=+ PT call s:PraeceptumTacet(<q-args>)

Dann benutze als zB:

:PT ls -l
:PT foobar
ERROR #127
/bin/bash: foobar: command not found

Wenn Sie die Fehlermeldung nicht brauchen / wollen, system()könnte ein einfaches ausreichen, dann überprüfen Sie sie v:shell_errorund melden Sie sich zB bei echo Error!.

Aus der Hilfe v: shell_error :

                                        v:shell_error shell_error-variable
v:shell_error   Result of the last shell command.  When non-zero, the last
                shell command had an error.  When zero, there was no problem.
                This only works when the shell returns the error code to Vim.
                The value -1 is often used when the command could not be
                executed.  Read-only.
                Example: >
    :!mv foo bar
    :if v:shell_error
    :  echo 'could not rename "foo" to "bar"!'
    :endif
                "shell_error" also works, for backwards compatibility.
Runium
quelle
Dies läuft nicht wirklich im Hintergrund. Sie müssen noch warten, bis es fertig ist.
Martin Tournoij
@Carpetsmoker: Ja, daher mein Kommentar "... behandelt Hintergrundprozesse nicht wie in foo &..." . Aus dem gesamten Q-Text habe ich entweder oder interpretiert. Entweder " Als externes Kommando ausführen AKA Hintergrund" oder "Als externes Kommando ausführen _und_ als Hintergrundprozess" . Ich antwortete hauptsächlich auf der Grundlage des Titels von Q usw. - und fügte einen Kommentar zu „Nicht sicher…“ hinzu - und überließ es dem OP, zu klären, ob entweder oder.
Runium
3

Vim 8 führte die Jobunterstützung ein. Man kann externe Befehle im Hintergrund ausführen, ohne auf Plugins angewiesen zu sein. So führen Sie beispielsweise einen Markdown-Server ( markserv ) am aktuellen Standort aus und blockieren keine vim-Sitzung:

:call job_start('markserv .')

Dadurch wird der markserv-Prozess als Unterprozess des aktuellen vim-Prozesses gestartet. Sie können dies mit bestätigen pstree.

Siehe job_start

KFL
quelle
2

Ich benutze folgenden Code:

command! -nargs=1 Silent call SilentExec(<q-args>)

function! SilentExec(cmd)
  let cmd = substitute(a:cmd, '^!', '', '')
  let cmd = substitute(cmd, '%', shellescape(expand('%')), '')
  call system(cmd)
endfunction

Jetzt können Sie Folgendes eingeben

:Silent !your_command

Das sieht fast so aus, als wäre Vim ein silent !Kommando eingebaut , mit Ausnahme der Hauptstadt S. Es erlaubt sogar ein optionales !, um es noch ähnlicher aussehen zu lassen. Der Aufruf von system()macht den Shell-Befehl wirklich leise: Es blinkt kein Bildschirm und es wird nicht neu gezeichnet.

(Wenn Sie jemals einen Statuscode benötigen, können Sie die v:shell_errorVariable überprüfen. Weitere Informationen finden Sie in der Hilfe.)

Jerome Dalbert
quelle
1

Das AsyncRun- Plugin ermöglicht es Ihnen, Shell-Befehle im Hintergrund in Vim8 / NeoVim auszuführen und die Ausgabe im Quickfix-Fenster anzuzeigen.

Nur als !Befehlsersatz:

:AsyncRun ls -la /

Sie können die Ausgabe im Quickfix-Fenster auch vollständig ausblenden:

:AsyncRun -mode=3 ls -la /

Wenn der Job beendet ist, werden Sie von AsyncRun über eine Option benachrichtigt -post:

:AsyncRun -post=some_vim_script   ls -la /

Das von definierte vim-Skript -postwird nach Beendigung des Jobs ausgeführt.

skywind3000
quelle