获取使用Java的终端多参数命令的输出

huangapple go评论76阅读模式
英文:

Get output of terminal multi-arg command using Java

问题

我正试图在Java中解析来自shell命令的输出:(可以通过apt安装sox)

Process p = Runtime.getRuntime().exec(command);
BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));

String line = null;
while ((line = in.readLine()) != null) {
    System.out.println(line);
}

我尝试了以下命令:

String command1 = "sox inputfile.flac -n stat";   //没有输出
String command2 = "bash -c sox inputfile.flac -n stat";   //输出'sox'的无参数版本
String command3 = "sh -c sox inputfile.flac -n stat";   //输出'sox'的无参数版本
String command4 = "sh -c \"sox inputfile.flac -n stat\"";   //错误
String command5 = "bash -c \"sox inputfile.flac -n stat\"";   //错误

根据'sh'的文档:

-c选项使命令从字符串操作数中读取,而不是从标准输入读取。请记住,此选项仅接受单个字符串作为其参数,因此多字字符串必须用引号引起来。

所以我尝试了"command4"/"command5" -> 错误代码2。
然而,我在普通的Linux终端中检查了一下,它正常工作:

sh -c "sox inputfile.flac -n stat"
bash -c "sox inputfile.flac -n stat"
sox inputfile.flac -n stat

我漏掉了什么?

英文:

I'm trying to parse output in java from shell command: (sox can be installed from apt)

sox inputfile.flac -n stat

but I can't, few hours of searching and nothing.

code:

Process p = Runtime.getRuntime().exec(command);  
BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));  

String line = null;
while ((line = in.readLine()) != null) {  
    System.out.println(line);  
} 

I tried with commands:

String command1 = "sox inputfile.flac -n stat";   //no output
String command2 = "bash -c sox inputfile.flac -n stat";   //output of 'sox' without arguments
String command3 = "sh -c sox inputfile.flac -n stat";   //output of 'sox' without arguments
String command4 = "sh -c \"sox inputfile.flac -n stat\"";   //error
String command5 = "bash -c \"sox inputfile.flac -n stat\"";   //error

According to documentation od 'sh':
>The -c option causes the commands to be read from the string operand in-
stead of from the standard input. Keep in mind that this option only ac-
cepts a single string as its argument, hence multi-word strings must be
quoted.

So I tried "command4"/"command5" -> Error code 2.
However, I checked in normal linux Terminal and its working fine:

sh -c "sox inputfile.flac -n stat"
bash -c "sox inputfile.flac -n stat"
sox inputfile.flac -n stat

What am I missing?

答案1

得分: 1

你所忽略的是 Process/Runtime 并不是 bash。Bash 在实际运行命令之前会对命令行进行处理。在 ProcessBuilder 及其相关类(Runtime.exec、Process 和 ProcessBuilder)中无法进行这些处理。

*.txt 转换为所有文件的列表?那是 bash 做的事情。

hello foo bar "baz boz" 转换为以下概念:应用名为 hello,有 3 个参数,分别是 foobarbaz boz这是 bash 做的事情。Bash 会对引号进行处理,将它们组合在一起。

作为一般规则,不要使用单字符串版本。使用 java.lang.ProcessBuilder

同时注意,将 bash 翻译为 /bin/bash 也是 bash 的特性。

最后,注意根据平台和月相,Runtime.exec 和相关功能会执行一些 bash 特性。也许。问题在于,它不够可靠,因此不要依赖它(例如,在 Windows 上,*.txt 通常有效,而在所有非 Windows 操作系统上几乎不起作用,并且 Runtime.exec 对 $PATH 进行了一些处理,但不一定与 bash 的处理方式相同)。

// 总是使用绝对路径来指定可执行文件。
// 总是分别传递参数。
ProcessBuilder pb = new ProcessBuilder("/bin/bash", "-c", "sox inputfile.flac -n stat");
Process p = pb.start();

注意第二个字符串(带有 sox...被传递给 bash。因此,如果你想在其中使用字符串转义和星号,随意操作。Bash 会处理它,而不是 ProcessBuilder:

// 这样不起作用:
ProcessBuilder pb = new ProcessBuilder("ls", "*.txt");

// 但这样会起作用:
ProcessBuilder pb = new ProcessBuilder("/bin/bash", "-c", "ls *.txt");

*) 这是常见的说法,但是当我说“bash 特性”时,可以自由替换为你选择的 shell,或者“shell 特性”。重点是,它是一个在你在命令行上输入的内容并在实际传递给操作系统之前进行处理的 shell。Runtime.exec 等功能也需要进行这种处理;它的处理要限制得多,你应尽量避免使用它:使用绝对路径,并单独列出参数。

英文:

What you're missing is that Process/Runtime isn't bash. Bash does things to a command line before actually running it. You can't do those things in ProcessBuilder and friends (Runtime.exec, Process, and ProcessBuilder).

Turning *.txt into a list of all files? That's bash.

Turning hello foo bar "baz boz" into the concept of: the app is called hello, and it gets 3 args, foo, bar, and baz boz? That is bash. Bash does the 'oh, quotes, those need to be bundled up'.

As a general rule, don't use the single-string version. Use java.lang.ProcessBuilder.

Note also that translating bash to /bin/bash is also a bashism.

Finally, note that depending on platform and phase of the moon, Runtime.exec and friends will do some bashisms. Maybe. The problem is, it's not reliable, so don't rely on it (for example, on windows, *.txt usually DOES work whereas on all non-windows OSes it almost never will, and Runtime.exec does do some application of $PATH, but not necessarily in the same way bash would).

// Always absolute-path your executables.
// Always pass args separately.
ProcessBuilder pb = new ProcessBuilder("/bin/bash", "-c", "sox inputfile.flac -n stat");
Process p = pb.start();

Note that the second string (the one with sox...) is passed to bash. So, if you want to get fancy with string escapes and stars in there, feel free. Bash will process it, not ProcessBuilder:

// This won't work:
ProcessBuilder pb = new ProcessBuilder("ls", "*.txt");

// but this will:
ProcessBuilder pb = new ProcessBuilder("/bin/bash", "-c", "ls *.txt");

*) It's rather common parlance, but where I say 'bashism', feel free to insert your shell of choice, or 'shellism'. The point is, it's a shell taking in what you type on the command line and processing it before actually passing it on to the OS. Runtime.exec and co also need to do this processing; its processing is FAR more limited, and you should avoid it as much as you can: Absolute paths, and list args separately.

huangapple
  • 本文由 发表于 2020年9月30日 20:13:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/64137347.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定