Groovy exécutant des commandes shell

178

Groovy ajoute la executeméthode pour Stringrendre l'exécution des shells assez facile;

println "ls".execute().text

mais si une erreur se produit, il n'y a pas de sortie résultante. Existe-t-il un moyen facile d'obtenir à la fois l'erreur standard et la norme? (autre que de créer un tas de code pour; créer deux threads pour lire les deux flux d'entrée, puis utiliser un flux parent pour attendre qu'ils se terminent, puis reconvertir les chaînes en texte?)

Ce serait bien d'avoir quelque chose comme;

 def x = shellDo("ls /tmp/NoFile")
 println "out: ${x.out} err:${x.err}"
Bob Herrmann
la source
Ce lien est utile. Montre comment exécuter la commande shell avec la démo cURL.
Aniket Thakur

Réponses:

207

Ok, je l'ai résolu moi-même;

def sout = new StringBuilder(), serr = new StringBuilder()
def proc = 'ls /badDir'.execute()
proc.consumeProcessOutput(sout, serr)
proc.waitForOrKill(1000)
println "out> $sout err> $serr"

affiche:

out> err> ls: cannot access /badDir: No such file or directory

Bob Herrmann
la source
13
Si vous devez également définir des variables d'environnement sur ce processus, assurez-vous d'encapsuler la commande dans le shell. Par exemple, exécuter une commande Perforce avec des variables d'environnement:envVars = ["P4PORT=p4server:2222", "P4USER=user", "P4PASSWD=pass", "P4CLIENT=p4workspace"]; workDir = new File("path"); cmd = "bash -c \"p4 change -o 1234\""; proc = cmd.execute(envVars, workDir);
Noam Manos
@paul_sns n'a aucun rapport avec la question OP, mais je pense que les JVM modernes gèrent très bien la synchronisation incontrôlée. Il est donc peu probable que StringBuffer dégrade les performances dans les scénarios confinés en thread ou en pile.
Pavel Grushetzky le
3
La documentation dit que nous devrions utiliser waitForProcessOutput () - "Pour attendre que la sortie soit complètement consommée, appelez waitForProcessOutput ()". Source: docs.groovy-lang.org/latest/html/groovy-jdk/java/lang/…
Srikanth
4
@srikanth la documentation de sortie de waitForProcess () dit également "Utilisez cette méthode si vous ne vous souciez pas de la sortie standard ou d'erreur et que vous voulez juste que le processus s'exécute en silence" - Je veux la sortie
Bob Herrmann
sout et serr peuvent ne pas être disponibles même après le waitForOrKill. Testé en utilisant une assert au lieu d'un println. La documentation dit: "Pour cela, deux threads sont démarrés, donc cette méthode retournera immédiatement. Les threads ne seront pas join () ed, même si waitFor () est appelé . Pour attendre que la sortie soit complètement consommée, appelez waitForProcessOutput () . "
solstice333
49

"ls".execute()renvoie un Processobjet, c'est pourquoi"ls".execute().text fonctionne. Vous devriez pouvoir simplement lire le flux d'erreurs pour déterminer s'il y a eu des erreurs.

Il existe une méthode supplémentaire sur Processqui vous permettent de passer un StringBufferpour récupérer le texte: consumeProcessErrorStream(StringBuffer error).

Exemple:

def proc = "ls".execute()
def b = new StringBuffer()
proc.consumeProcessErrorStream(b)

println proc.text
println b.toString()
Joshua
la source
Il ne fonctionne pas avec le script Bourn Again Shell! # / Bin / bash,
Rashmi Jain
1
Si vous travaillez avec des scripts bash, vous appelez probablement bash dans le cadre de la commande: "/ bin / bash script" .execute ()
Niels Bech Nielsen
32
// a wrapper closure around executing a string                                  
// can take either a string or a list of strings (for arguments with spaces)    
// prints all output, complains and halts on error                              
def runCommand = { strList ->
  assert ( strList instanceof String ||
           ( strList instanceof List && strList.each{ it instanceof String } ) \
)
  def proc = strList.execute()
  proc.in.eachLine { line -> println line }
  proc.out.close()
  proc.waitFor()

  print "[INFO] ( "
  if(strList instanceof List) {
    strList.each { print "${it} " }
  } else {
    print strList
  }
  println " )"

  if (proc.exitValue()) {
    println "gave the following error: "
    println "[ERROR] ${proc.getErrorStream()}"
  }
  assert !proc.exitValue()
}
mholm815
la source
10
+1 Cela montre la sortie de manière incrémentielle au fur et à mesure que la sortie est générée ... ce qui est extrêmement important pour un processus de longue durée
samarjit samanta
grande part là-bas @ mholm815
Jimmy Obonyo Abor
2
Pour utiliser cette solution, émettez la ligne suivante:runCommand("echo HELLO WORLD")
Miron V
@ mholm815 comment pouvons-nous approuver les scripts requis à partir du pipeline lui-même?
Ronak Patel
25

Je trouve cela plus idiomatique:

def proc = "ls foo.txt doesnotexist.txt".execute()
assert proc.in.text == "foo.txt\n"
assert proc.err.text == "ls: doesnotexist.txt: No such file or directory\n"

Comme le mentionne un autre article, il s'agit d'appels bloquants, mais puisque nous voulons travailler avec la sortie, cela peut être nécessaire.

solstice333
la source
24

Pour ajouter une autre information importante aux réponses fournies ci-dessus -

Pour un processus

def proc = command.execute();

essayez toujours d'utiliser

def outputStream = new StringBuffer();
proc.waitForProcessOutput(outputStream, System.err)
//proc.waitForProcessOutput(System.out, System.err)

plutôt que

def output = proc.in.text;

pour capturer les sorties après avoir exécuté des commandes en groovy car ce dernier est un appel bloquant ( question SO pour raison ).

Aniket Thakur
la source
6
def exec = { encoding, execPath, execStr, execCommands ->

def outputCatcher = new ByteArrayOutputStream()
def errorCatcher = new ByteArrayOutputStream()

def proc = execStr.execute(null, new File(execPath))
def inputCatcher = proc.outputStream

execCommands.each { cm ->
    inputCatcher.write(cm.getBytes(encoding))
    inputCatcher.flush()
}

proc.consumeProcessOutput(outputCatcher, errorCatcher)
proc.waitFor()

return [new String(outputCatcher.toByteArray(), encoding), new String(errorCatcher.toByteArray(), encoding)]

}

def out = exec("cp866", "C:\\Test", "cmd", ["cd..\n", "dir\n", "exit\n"])

println "OUT:\n" + out[0]
println "ERR:\n" + out[1]
emles-kz
la source
3
Je suis vraiment ennuyé qu'une personne ait pris le temps de donner une réponse et que quelqu'un l'ait simplement rejetée sans raison apparente. s'il s'agit d'une communauté, on devrait se sentir obligé d'ajouter un commentaire (à moins que ce ne soit une raison très évidente que tout programmeur compétent verrait immédiatement) expliquant le vote défavorable.
Amos Bordowitz
6
@AmosBordowitz Beaucoup de réponses reçoivent des votes négatifs. C'est bon, c'est un vote contre. Cela dit, cela pourrait être parce que c'est un code sans mot d'explication - pas toujours bien reçu.
Chris Baker
@ChrisBaker alors pourquoi ne pas le signaler? Vous-même n'êtes pas
sûr
5
@AmosBordowitz Je ne suis pas l'explicateur officiel du vote négatif, je ne peux pas vous dire pourquoi, et il est compréhensible que je ne sois pas certain puisque nous parlons d'une action entreprise par une autre personne. J'ai offert une possibilité. Pourquoi ne pas expliquer le vote négatif, bien sûr, pourquoi ne pas expliquer le code dans la réponse? Quoi qu'il en soit, je suis sûr que tout ira bien.
Chris Baker
1
@ChrisBaker Je n'ai jamais fait une telle affirmation ("mais je suppose que vous savez mieux"). C'est une chose de décence, pas une chose de connaissance ..
Amos Bordowitz
-3
command = "ls *"

def execute_state=sh(returnStdout: true, script: command)

mais si la commande échoue, le processus se terminera

舒何伟
la source
D'où shvient-il?
styl3r
3
shfait partie de la DSL groovy de Jenkins. Probablement pas utile ici
Gi0rgi0s
4
Jenkins Groovy DSL! = Groovy
Skeeve
comme d'autres l'ont déclaré, cela fait partie de la DSL Jenkins
jonypony3
Cette réponse ne s'applique pas à la question qui a été posée.
Brandon