AnyMath 文档

运行外部程序

Julia借用了shell、Perl和Ruby命令的反引号。 然而,在朱莉娅,写作

julia> `echo hello`
`echo hello`

在几个方面不同于各种shell,Perl或Ruby中的行为:

*反引号不是立即运行命令,而是创建一个 Cmd公司对象来表示命令。 您可以使用此对象通过管道将命令连接到其他人, 它,和 讀!给它。 *当命令运行时,Julia不会捕获其输出,除非您特别安排它。 相反,默认情况下,命令的输出转到 xref:base/io-network.adoc#Base.stdout[标准输出 就像它使用的那样 利伯克'的 系统 打电话。 *该命令永远不会与shell一起运行。 相反,Julia直接解析命令语法,适当地插入变量并像shell一样分割单词,尊重shell引用语法。 该命令以如下方式运行 朱莉娅直接子过程,使用 叉子,叉子执行董事 电话。

注意以下假设在Linux或MacOS上使用Posix环境。 在Windows上,许多类似的命令,例如 回声署长,不是外部程序,而是内置在shell中 cmd。exe文件 本身。 运行这些命令的一个选项是调用 cmd。exe文件,例如 cmd/c echo你好. 或者,Julia可以在Posix环境中运行,例如Cygwin。

下面是一个运行外部程序的简单示例:

julia> mycommand = `echo hello`
`echo hello`

julia> typeof(mycommand)
Cmd

julia> run(mycommand);
hello

你好! 是输出的 回声 命令,发送到 标准输出. 如果外部命令未能成功运行,则run方法将抛出 ProcessFailedException异常.

如果要读取外部命令的输出, 讀!阅读,阅读可以代替使用:

julia> read(`echo hello`, String)
"hello\n"

julia> readchomp(`echo hello`)
"hello"

更一般地,可以使用 打开读取或写入外部命令。

julia> open(`less`, "w", stdout) do io
           for i = 1:3
               println(io, i)
           end
       end
1
2
3

可以访问和迭代命令中的程序名和单个参数,就像命令是一个字符串数组一样:

julia> collect(`echo "foo bar"`)
2-element Vector{String}:
 "echo"
 "foo bar"

julia> `echo "foo bar"`[2]
"foo bar"

你也可以通过一个 IOBuffer的,后来从中读:

julia> io = PipeBuffer(); # PipeBuffer is a type of IOBuffer

julia> run(`echo world`, devnull, io, stderr);

julia> readlines(io)
1-element Vector{String}:
 "world"

插值法

假设你想做一些更复杂的事情,并在变量中使用文件的名称 档案 作为命令的参数。 您可以使用 $ 对于插值,就像在字符串字面量中一样(请参阅 字符串):

julia> file = "/etc/passwd"
"/etc/passwd"

julia> `sort $file`
`sort /etc/passwd`

通过shell运行外部程序时,一个常见的缺陷是,如果文件名包含shell特殊的字符,它们可能会导致不良行为。 假设,例如,而不是 /等/密码,我们想对文件的内容进行排序 /卷/外部HD/数据。csv档案源. 让我们试试吧:

julia> file = "/Volumes/External HD/data.csv"
"/Volumes/External HD/data.csv"

julia> `sort $file`
`sort '/Volumes/External HD/data.csv'`

文件名是如何被引用的? 茱莉亚知道 档案 是为了作为一个单一的参数进行插值,所以它为你引用这个词。 实际上,这并不完全准确: 档案 从不由shell解释,因此不需要实际引用;引号仅用于向用户展示。 如果您将值作为shell单词的一部分进行插值,这甚至可以工作:

julia> path = "/Volumes/External HD"
"/Volumes/External HD"

julia>名称="数据"
"数据"

朱莉娅>ext="csv"
"csv"

朱莉娅> `排序path path/name name。$ext`
`排序'/Volumes/External HD/data。csv'`

正如你所看到的, 路径 变量进行适当转义。 但是,如果你_want_插入多个词呢? 在这种情况下,只需使用数组(或任何其他可迭代容器):

julia> files = ["/etc/passwd","/Volumes/External HD/data.csv"]
2-element Vector{String}:
 "/etc/passwd"
 "/Volumes/External HD/data.csv"

julia> `grep foo $files`
`grep foo /etc/passwd '/Volumes/External HD/data.csv'`

如果将数组作为shell字的一部分进行插值,Julia会模拟shell的 {a,b,c} 参数生成:

julia> names = ["foo","bar","baz"]
3-element Vector{String}:
 "foo"
 "bar"
 "baz"

julia> `grep xylophone $names.txt`
`grep xylophone foo.txt bar.txt baz.txt`

此外,如果将多个数组插值到同一个单词中,则会模拟shell的笛卡尔积生成行为:

julia> names = ["foo","bar","baz"]
3-element Vector{String}:
 "foo"
 "bar"
 "baz"

julia> exts = ["aux","log"]
2-element Vector{String}:
 "aux"
 "log"

julia> `rm -f $names.$exts`
`rm -f foo.aux foo.log bar.aux bar.log baz.aux baz.log`

由于您可以内插字面量数组,因此您可以使用此生成功能,而无需先创建临时数组对象:

julia> `rm -rf $["foo","bar","baz","qux"].$["aux","log","pdf"]`
`rm -rf foo.aux foo.log foo.pdf bar.aux bar.log bar.pdf baz.aux baz.log baz.pdf qux.aux qux.log qux.pdf`

引用

不可避免地,人们想要编写不那么简单的命令,并且有必要使用引号。 下面是一个在shell提示符下的Perl单行的简单示例:

sh$ perl -le '$|=1; for (0..3) { print }'
0
1
2
3

Perl表达式需要使用单引号,原因有两个:这样空格就不会把表达式分成多个shell单词,这样Perl变量的使用就像 $| (是的,这是Perl中变量的名称),不要引起插值。 在其他情况下,您可能希望使用双引号,以便发生插值_does_:

sh$ first="A"
sh$ second="B"
sh$ perl -le '$|=1; print for @ARGV' "1: $first" "2: $second"
1: A
2: B

一般来说,Julia backtick语法是精心设计的,这样你就可以把shell命令按原样剪切粘贴到backtick中,它们就可以工作了:转义、引用和插值行为与shell的行为相同。唯一的区别是插值是集成的,并且知道Julia关于什么是单个字符串值的概念,什么是多个值的容器。 让我们在Julia中尝试上述两个例子:

julia> A = `perl -le '$|=1; for (0..3) { print }'`
`perl -le '$|=1; for (0..3) { print }'`

julia> run(A);
0
1
2
3

julia> first = "A"; second = "B";

julia> B = `perl -le 'print for @ARGV' "1: $first" "2: $second"`
`perl -le 'print for @ARGV' '1: A' '2: B'`

julia> run(B);
1: A
2: B

结果是相同的,并且Julia的插值行为模仿了shell的一些改进,因为Julia支持一流的可迭代对象,而大多数shell使用空格分割的字符串,这会引入歧义。 当尝试将shell命令移植到Julia时,请先尝试剪切和粘贴。 由于Julia在运行命令之前向您显示命令,因此您可以轻松安全地检查其解释而不会造成任何损害。

管道

Shell元字符,例如 |, &,而 >,需要在Julia的反引号中引用(或转义):

julia> run(`echo hello '|' sort`);
hello | sort

julia> run(`echo hello \| sort`);
hello | sort

此表达式调用 回声 以三个词作为参数的命令: 你好!, |,而 排序. 结果是打印了一行: 你好/排序. 那么,如何构建管道呢? 而不是使用 '|' 在反引号内,人们使用 管道:

julia> run(pipeline(`echo hello`, `sort`));
hello

这管道的输出 回声 命令 排序 命令。 当然,这并不是非常有趣,因为只有一行可以排序,但我们当然可以做更有趣的事情:

julia> run(pipeline(`cut -d: -f3 /etc/passwd`, `sort -n`, `tail -n5`))
210
211
212
213
214

这将打印UNIX系统上最高的五个用户Id。 该 , 排序尾巴 命令都作为当前的直接子级生成 朱莉娅 过程,没有介入外壳过程。 Julia本身负责设置管道和连接通常由shell完成的文件描述符。 由于Julia本身就是这样做的,它保留了更好的控制,并且可以做一些shell不能做的事情。

Julia可以并行运行多个命令:

julia> run(`echo hello` & `echo world`);
world
hello

这里输出的顺序是非确定性的,因为两者 回声 进程几乎同时启动,并争先恐后地写入 标准输出描述符它们彼此共享和 朱莉娅 父进程。 Julia允许您将这两个进程的输出通过管道传输到另一个程序:

julia> run(pipeline(`echo world` & `echo hello`, `sort`));
hello
world

在UNIX管道方面,这里发生的事情是由两者创建和写入单个UNIX管道对象 回声 过程,而管道的另一端则由 排序 命令。

IO重定向可以通过传递关键字参数来完成 标准普尔, 标准输出,而 斯德尔管道 功能:

pipeline(`do_work`, stdout=pipeline(`sort`, "out.txt"), stderr="errs.txt")

避免管道中的死锁

从单个进程读取和写入管道的两端时,避免强制内核缓冲所有数据非常重要。

例如,当读取命令的所有输出时,调用 读(出,字符串),不 等待(进程),因为前者将主动消耗进程写入的所有数据,而后者将尝试在等待读取器连接时将数据存储在内核的缓冲区中。

另一种常见的解决方案是将管道的读取器和写入器分离为单独的 任务s:

writer = Threads.@spawn write(process, "data")
reader = Threads.@spawn do_compute(read(process, String))
wait(writer)
fetch(reader)

(通常情况下,读者不是一个单独的任务,因为我们立即 取货/取货 无论如何)。

复杂的例子

高级编程语言、一流的命令抽象和进程之间管道的自动设置的组合是一个强大的组合。 为了给出一些可以轻松创建的复杂管道的感觉,这里有一些更复杂的例子,并为过度使用Perl单行表示歉意:

julia> prefixer(prefix, sleep) = `perl -nle '$|=1; print "'$prefix' ", $_; sleep '$sleep';'`;

julia> run(pipeline(`perl -le '$|=1; for(0..5){ print; sleep 1 }'`, prefixer("A",2) & prefixer("B",2)));
B 0
A 1
B 2
A 3
B 4
A 5

这是一个典型的例子,一个生产者喂养两个并发的消费者:一个 perl的 进程生成带有数字0到5的行,而两个并行进程消耗该输出,一个以字母"A"为前缀,另一个以字母"B"为前缀。 哪个消费者获得第一条线是非确定性的,但是一旦赢得了比赛,这些线被一个进程交替消耗,然后被另一个进程消耗。 (设置 $|=1 在Perl中,每个打印语句都会刷新 标准输出句柄,这是本例工作所必需的。 否则,所有输出都被缓冲并一次打印到管道中,只需一个消费者进程即可读取。)

这是一个更复杂的多阶段生产者-消费者示例:

julia> run(pipeline(`perl -le '$|=1; for(0..5){ print; sleep 1 }'`,
           prefixer("X",3) & prefixer("Y",3) & prefixer("Z",3),
           prefixer("A",2) & prefixer("B",2)));
A X 0
B Y 1
A Z 2
B X 3
A Y 4
B Z 5

此示例与前一个示例类似,只是有两个阶段的使用者,并且这些阶段具有不同的延迟,因此它们使用不同数量的并行工作线程来保持饱和吞吐量。

我们强烈建议您尝试所有这些示例,看看它们是如何工作的。

Cmd公司 对象

Backtick语法创建一个类型的对象 Cmd公司. 这样的对象也可以直接从现有的 Cmd公司 或参数列表:

run(Cmd(`pwd`, dir=".."))
run(Cmd(["pwd"], detach=true, ignorestatus=true))

这允许您指定 Cmd公司通过关键字参数的执行环境。 例如, 署长 关键字提供对 Cmd公司工作目录:

julia> run(Cmd(`pwd`, dir="/"));
/

env 关键字允许您设置执行环境变量:

julia> run(Cmd(`sh -c "echo foo \$HOWLONG"`, env=("HOWLONG" => "ever!",)));
foo ever!

Cmd公司用于附加关键字参数。 该 setenv增补,增补命令提供了另一种方法来替换或添加到 .adoc[] 执行环境变量,分别:

julia> run(setenv(`sh -c "echo foo \$HOWLONG"`, ("HOWLONG" => "ever!",)));
foo ever!

julia> run(addenv(`sh -c "echo foo \$HOWLONG"`, "HOWLONG" => "ever!"));
foo ever!