【问题标题】:bash command fails in tomcat/java Runtime.getRuntime.exec(), but works from command linebash 命令在 tomcat/java Runtime.getRuntime.exec() 中失败,但可以从命令行运行
【发布时间】:2016-10-13 13:16:58
【问题描述】:

我有一个在 shell 中运行进程的 tomcat webapp,因为一些实用程序在 java 中不可用。 这段代码在其他机器上完美运行,但是我的公共服务器上出现了一个神秘问题。

String[] textAnalysisPipeline = {
    "/bin/sh",
    "-c",
    "/bin/cat " + inputfileLoc +
    " | tee /tmp/debug1 | " + loadJar + " " + jarOptLookupLoc + " " + optHfstLoc +
    " 2>/dev/null | " + "tail -n+5" + // get rid of the header that hfst-ol.jar produces
    " | tee /tmp/debug2 | cut -f 1-2" + // get rid of the "0.0" weights
    " | tee /tmp/debug3 | " + cgConvLoc +
    " | tee /tmp/debug4 | " + vislcg3Loc + " -g " + vislcg3DisGrammarLoc +  // disambiguate with the constraint grammar
    " | tee /tmp/debug5 > " + outputfileLoc};
log.debug("Text analysis pipeline: "+textAnalysisPipeline[2]);
Process process = Runtime.getRuntime().exec(textAnalysisPipeline);
process.waitFor();

我将字符串打印到日志中,它看起来像这样:(path/to/ 不是实际路径)

/bin/cat /path/to/inputFile | tee /tmp/debug1 | java -jar /path/to/hfst-ol.jar /path/to/analyser.ohfst 2>/dev/null | tail -n+5 | tee /tmp/debug2 | cut -f 1-2 | tee /tmp/debug3 | /usr/local/bin/cg-conv | tee /tmp/debug4 | /path/to/vislcg3 -g /path/to/grammar.rlx | tee /tmp/debug5 > /path/to/outputFile

如果我从日志中复制此管道并从 bash 命令行运行它,我会一直得到所需的输出,直到管道结束。但是,当 tomcat 服务器运行该命令时,它会生成一个空文件。调试文件debug1debug2 符合预期,但debug3 及之后为空,这表明管道在cut -f 1-2 处失败(请参阅下面的更新1)。

操作系统 - Fedora 22
java - openjdk 1.8.0_77
tomcat - 7.0.39
sh --> bash - 4.3.42(1)-release

================================================ =================
更新 1:

这似乎不是cut 的问题。我写了一个简短的python脚本cut.py来实现与cut -f 1-2相同的功能(从每一行的末尾删除'\t0.0'

import re, sys
myRE = re.compile( r'\s+0\.0$' )
for line in sys.stdin :
    sys.stdout.write( myRE.sub( '', line ) )

使用cut.py 代替cut,我遇到了同样的问题。服务器 debug3 及以后是空的,但如果我从日志复制粘贴到交互式 shell,一切正常。

================================================ =================
更新 2:

我还编写了一个简单的 bash 脚本来运行管道,以便 tomcat/java 只运行带有一个输入/输出文件名参数的 bash 脚本。如果我从交互式 shell 运行脚本,它可以工作,但结果在 tomcat 中没有什么不同,在 shell 脚本中使用 cutcut.py

【问题讨论】:

  • 我建议实际上回显从字符串连接构建的命令行。验证打印的确切命令在终端中是否有效。很多时候,我们在通过字符串 concat 生成命令行时会遗漏一些引号。问这个,因为没有逻辑原因cut 应该在脚本中失败并通过命令行传递。
  • @anishsane 这实际上是我所做的。它将字符串打印到日志中,log.debug("Text analysis pipeline: "+textAnalysisPipeline[2]);,然后我将其复制粘贴到命令行。
  • 您是否尝试过直接从 bash 命令行运行命令 /bin/sh -c "/bin/cat ..."(而不仅仅是 /bin/cat ...)?
  • 对于调试,使用"/bin/sh", "-xc", ... 可能会产生有用的提示。
  • waitFor 返回一个整数,它是您的进程的退出值

标签: java linux bash shell tomcat


【解决方案1】:

在大多数系统上默认安装时,Tomcat 与您的用户没有相同的环境。例如,它不会运行您的 .bashrc .profile 或登录脚本中的任何内容,因此在您的用户 shell 环境中设置的所有变量都是不同的。

您可以通过将env 命令与两个用户(您的用户)进行比较来检查这一点,并由您的 java 程序调用它,例如:

String[] textAnalysisPipeline = {"/bin/sh","-c","/usr/bin/env > /tmp/env.txt"}; //or wherever the 'env' command is in your system
Process process = Runtime.getRuntime().exec(textAnalysisPipeline);
...

然后将/tmp/env.txt 的内容与env 执行与您的用户进行比较......它们可能非常不同。

查找以下变量:

  • 路径
  • 类路径
  • JAVA_HOME

我过去已经遇到过同样的问题。我建议以下方法:

  1. 使用绝对路径,包括对“java”、“tee”、“tail”和 libs(jar 文件)的调用...您的命令中有;

  2. 更改运行 Tomcat 的环境配置以访问您在命令中调用的所有应用程序(通常通过调用配置所有必要的 PATH 变量的脚本(不要忘记 JAVA_HOME 和 @ 987654328@ 到您的 jar 文件中!)。检查 Startup.sh 和 Catalina.sh 以找到合适的位置来包含您的内容;

  3. 更改您的命令以将错误输出重定向到您的消息中的/dev/null,而不是您系统中某个您的应用程序可以写入的某个日志文件(通常/tmp/exec.log 就可以了)所以你可以确定shell执行问题。我敢打赌它会类似于:sh: ****: command not foundError: Unable to access jarfile 或您的应用程序无法找到对象的某些消息,因此您将确定您在脚本中调用的应用程序不在 PATH 环境中变量或你没有的库......通常都是

有关更多信息,请查看https://www.mulesoft.com/tcat/tomcat-classpath

希望这会有所帮助...

【讨论】:

  • 第 3 点很有帮助:cg-convvislcg3 都给出了错误 error while loading shared libraries: libcg3.so.0: cannot open shared object file: No such file or directory。这似乎表明在 tomcat 的环境中 PATH 存在问题。它在/usr/local/lib/libcg3.so.0
  • 从 tomcat 调用env 得到以下PATHPATH=/sbin:/bin:/usr/sbin:/usr/bin。我在startup.shcatalina.sh 中找不到如何更改它。是取自运行tomcat的用户吗?
  • 比较env的输出并在每一步添加2>/tmp/...的组合帮助我找到了问题。正如我在对此答案的第一条评论中所写,访问共享库时出现问题。我在自己的用户环境中找到了LD_LIBRARY_PATH=/usr/local/lib,但是在tomcat的env中没有这样的东西。我编写了一个简短的 shell 脚本,让 tomcat 用户在启动服务器之前声明该变量,现在它可以完美运行了! :-)
【解决方案2】:

这主要是一种替代方法,也可能是一种解决方法,但您的管道可以大大简化。 catuselesstailcut 可以替换为单个 awk 脚本。

tee /tmp/debug1 <path/to/inputFile |
java -jar path/to/hfst-ol.jar path/to/analyser.ohfst 2>/dev/null |
tee /tmp/debug2 |
awk -F '\t' 'NR>5 { print $1 FS $2 }' |
tee /tmp/debug3 |
/usr/local/bin/cg-conv |
tee /tmp/debug4 |
path/to/vislcg3 -g path/to/grammar.rlx |
tee /tmp/debug5 > path/to/outputFile

【讨论】:

  • 好点。这不是第一次有人让我相信awk 值得学习。也许有一天我会这样做.... ;)
【解决方案3】:

您可以使用ProcessBuilder。使用Runtime.exec 的单字符串形式不是一个好的样式。更好的选择是使用 ProcessBuilder,并自己拆分参数,而不是依赖 Java 为您拆分它们,这非常幼稚。

ProcessBuilder pb = new ProcessBuilder("/bin/bash", "-c", /*...*/);
pb.redirectErrorStream(true);
Process p = pb.start();

【讨论】:

    【解决方案4】:

    在脚本和(有时尤其是)crontab 中有一种模式,您用于脚本的 $PATH 变量与您用于交互式 shell 的变量不同。这会导致命令在一个环境中神秘地失败,但在其他环境中起作用。

    我强烈建议将您正在使用的所有工具的完整路径嵌入到您正在构建的调用中。 cut 可能位于运行该进程时未拾取的目录中。放入像/usr/bin/cut 这样的完整路径将确定两件事:没有人将意外版本的cut 偷偷带入您的路径,而且您所在的环境是否没有@ 也没关系987654325@$PATH.

    【讨论】:

    • 即使在严重受限的环境中,/usr/bin 似乎也不太可能从PATH 中丢失;但这肯定是 OP 调查并希望在问题中澄清的另一件事。
    • 看起来这可能是PATH 的问题,但不是cut。请参阅我对@FJunqueira 答案的评论。
    【解决方案5】:

    当你从日志中复制命令并从命令行运行时,你是否以与运行Tomcat的用户相同的用户执行它?

    Tomcat 用户可能没有足够的权限来读取您命令中的某个文件。

    在我的 Ubuntu 系统上,默认的 Tomcat 7 用户名为 tomcat7。可以使用sudo 命令以其他用户身份运行命令:

    sudo -u tomcat7 /bin/sh -c 'myCommand'
    

    来源:How do you process an entire command as sudo that involves redirecting or piping?

    【讨论】:

      猜你喜欢
      • 2019-05-28
      • 1970-01-01
      • 2017-07-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多