Groovy ejecutando comandos de shell

178

Groovy agrega el executemétodo para Stringhacer que la ejecución de shells sea bastante fácil;

println "ls".execute().text

pero si ocurre un error, entonces no hay salida resultante. ¿Hay una manera fácil de obtener tanto el error estándar como el estándar? (¿aparte de crear un montón de código para crear dos hilos para leer ambos flujos de entrada, luego usar un flujo principal para esperar a que se completen y luego convertir las cadenas de nuevo en texto?)

Sería bueno tener algo así;

 def x = shellDo("ls /tmp/NoFile")
 println "out: ${x.out} err:${x.err}"
Bob Herrmann
fuente
Este enlace es útil. Muestra cómo ejecutar el comando de shell con la demostración de cURL.
Aniket Thakur

Respuestas:

207

Ok, lo resolví yo mismo;

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

muestra:

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

Bob Herrmann
fuente
13
En caso de que también necesite establecer Variables de entorno para este proceso, asegúrese de ajustar el comando en el shell. Por ejemplo, ejecutar un comando Perforce con env vars: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 no está relacionado con la pregunta de OP, pero creo que las JVM modernas manejan bien la sincronización no controlada. Por lo tanto, es poco probable que StringBuffer reduzca el rendimiento en escenarios confinados a subprocesos o pila.
Pavel Grushetzky
3
Los documentos dicen que deberíamos usar waitForProcessOutput () - "Para esperar a que la salida se consuma por completo, llame a waitForProcessOutput ()". Fuente: docs.groovy-lang.org/latest/html/groovy-jdk/java/lang/…
Srikanth
44
@srikanth los documentos de salida waitForProcess () también dicen "Use este método si no le importa la salida estándar o de error y solo quiere que el proceso se ejecute en silencio" - Quiero la salida
Bob Herrmann
sout y serr pueden no estar disponibles incluso después de waitForOrKill. Probado utilizando una afirmación en lugar de una println. Los documentos dicen: "Para esto, se inician dos subprocesos , por lo que este método volverá inmediatamente. Los subprocesos no se unirán () ed, incluso si se llama a waitFor () . Para esperar a que la salida se consuma completamente, llame a waitForProcessOutput () ".
solstice333
49

"ls".execute()devuelve un Processobjeto por lo que "ls".execute().textfunciona. Debería poder simplemente leer la secuencia de errores para determinar si hubo algún error.

Existe un método adicional en Processque permiten pasar de una StringBuffera recuperar el texto: consumeProcessErrorStream(StringBuffer error).

Ejemplo:

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

println proc.text
println b.toString()
Joshua
fuente
¡No funciona con el script Bourn Again Shell! # / Bin / bash,
Rashmi Jain
1
Si trabaja con secuencias de comandos bash, probablemente invoque bash como parte del comando: "/ 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
fuente
10
+1 Esto muestra la salida de forma incremental a medida que se genera la salida ... lo cual es extremadamente importante para un proceso de larga ejecución
samarjit samanta
gran compartir allí @ mholm815
Jimmy Obonyo Abor
2
Para usar esta solución, emita la siguiente línea:runCommand("echo HELLO WORLD")
Miron V
@ mholm815 ¿cómo podemos aprobar los scripts requeridos de la tubería misma?
Ronak Patel
25

Esto me parece más idiomático:

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"

Como menciona otra publicación, estas son llamadas de bloqueo, pero dado que queremos trabajar con la salida, esto puede ser necesario.

solsticio333
fuente
24

Para agregar una información más importante a las respuestas proporcionadas anteriormente:

Para un proceso

def proc = command.execute();

siempre trata de usar

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

más bien que

def output = proc.in.text;

para capturar las salidas después de ejecutar comandos en groovy ya que esta última es una llamada de bloqueo ( pregunta SO por razón ).

Aniket Thakur
fuente
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
fuente
3
Estoy realmente molesto porque una persona se tomó el tiempo para dar una respuesta y alguien la rechazó sin razón aparente. Si se trata de una comunidad, uno debería sentirse obligado a agregar un comentario (a menos que sea una razón muy obvia que cualquier programador competente vería de inmediato) explicando el voto negativo.
Amos Bordowitz
66
@AmosBordowitz Muchas respuestas reciben votos negativos. Está bien, es un voto negativo. Dicho esto, podría deberse a que es un código sin palabras de explicación, no siempre bien recibido.
Chris Baker
@ChrisBaker, ¿por qué no señalarlo? Usted mismo no está seguro de que esta sea la razón ..
Amos Bordowitz 05 de
55
@AmosBordowitz No soy el explicador oficial de voto negativo, no puedo decir por qué no, y es comprensible que no estoy seguro ya que estamos hablando de una acción tomada por otra persona. Ofrecí una posibilidad. ¿Por qué no explicar el voto negativo, claro, por qué no explicar el código en la respuesta? En cualquier caso, estoy seguro de que todos estaremos bien.
Chris Baker
1
@ChrisBaker Nunca hice tal reclamo ("pero supongo que lo sabes mejor"). Es una cosa de decencia, no una cosa de conocimiento ..
Amos Bordowitz
-3
command = "ls *"

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

pero si el comando falla, el proceso terminará

舒何伟
fuente
De donde shviene
styl3r
3
shes parte del maravilloso DSL de Jenkins. Probablemente no sea útil aquí
Gi0rgi0s
44
Jenkins Groovy DSL! = Groovy
Skeeve
como han dicho otros, esto es parte del DSL de Jenkins
jonypony3
Esta respuesta no es aplicable a la pregunta que se hizo.
Brandon